Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ gh project item-list 2 --owner traverse-framework --format json --limit 300 \
- **Phase 3 embedded runtime** ([#109](https://github.com/traverse-framework/reference-apps/issues/109)–[#118](https://github.com/traverse-framework/reference-apps/issues/118)) — see `docs/embedded-runtime-plan.md`; blocked on a **consumable platform embedder SDK** (Traverse [#553](https://github.com/traverse-framework/Traverse/issues/553) closed via [#578](https://github.com/traverse-framework/Traverse/pull/578) with manifest `execution_mode` only — no web/Swift/etc. embedder package yet). Traverse [#615](https://github.com/traverse-framework/Traverse/pull/615) added wasm32 core build only — not a platform embedder package.
- **doc-approval multi-capability showcase** ([#111](https://github.com/traverse-framework/reference-apps/issues/111), [#112](https://github.com/traverse-framework/reference-apps/issues/112)) — blocked on Traverse [#538](https://github.com/traverse-framework/Traverse/issues/538) / [#555](https://github.com/traverse-framework/Traverse/issues/555) (`extract` / `recommend` agents)

Ready: [#110](https://github.com/traverse-framework/reference-apps/issues/110) traverse-starter.pipeline; [#73](https://github.com/traverse-framework/reference-apps/issues/73) doc-approval-core-rs ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59)/[#72](https://github.com/traverse-framework/reference-apps/issues/72) Done).
Ready: (none — shared packages shipped; [#110](https://github.com/traverse-framework/reference-apps/issues/110) pipeline showcase in progress).

Update this section when a PR changes platform status (see PR template checklist).

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,6 @@ Active blockers on [Project 2](https://github.com/orgs/traverse-framework/projec

- **Phase 3 embedded runtime** ([#109](https://github.com/traverse-framework/reference-apps/issues/109)–[#118](https://github.com/traverse-framework/reference-apps/issues/118)) — all platform clients must bundle the WASM runtime host; blocked on a **consumable platform embedder SDK** (Traverse [#553](https://github.com/traverse-framework/Traverse/issues/553) closed via [#578](https://github.com/traverse-framework/Traverse/pull/578) with manifest `execution_mode` only; [#615](https://github.com/traverse-framework/Traverse/pull/615) is wasm32 core build only). HTTP sidecar is dev-only.
- **doc-approval multi-capability showcase** ([#111](https://github.com/traverse-framework/reference-apps/issues/111), [#112](https://github.com/traverse-framework/reference-apps/issues/112)) — blocked on Traverse [#538](https://github.com/traverse-framework/Traverse/issues/538) / [#555](https://github.com/traverse-framework/Traverse/issues/555) (`extract` / `recommend` agents).
- **traverse-starter.pipeline showcase** ([#110](https://github.com/traverse-framework/reference-apps/issues/110)) — Ready (Traverse [#620](https://github.com/traverse-framework/Traverse/issues/620) closed; pipeline on Traverse `main`).
- **traverse-starter.pipeline showcase** ([#110](https://github.com/traverse-framework/reference-apps/issues/110)) — In Progress (Traverse [#620](https://github.com/traverse-framework/Traverse/issues/620)/[#554](https://github.com/traverse-framework/Traverse/issues/554) closed).

Ready on Project 2: [#110](https://github.com/traverse-framework/reference-apps/issues/110) pipeline showcase ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59)/[#72](https://github.com/traverse-framework/reference-apps/issues/72) Done; [#73](https://github.com/traverse-framework/reference-apps/issues/73) in progress).
Ready on Project 2: shared HTTP/SSE packages Done ([#58](https://github.com/traverse-framework/reference-apps/issues/58)–[#73](https://github.com/traverse-framework/reference-apps/issues/73)).
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import Foundation

public struct TraverseStarterOutput: Equatable, Sendable, Codable {
public struct ValidateOutput: Equatable, Sendable, Codable {
public let valid: Bool
public let issues: [String]

public init(valid: Bool, issues: [String]) {
self.valid = valid
self.issues = issues
}
}

public struct ProcessOutput: Equatable, Sendable, Codable {
public let title: String
public let tags: [String]
public let noteType: String
Expand All @@ -22,6 +32,41 @@ public struct TraverseStarterOutput: Equatable, Sendable, Codable {
}
}

public struct SummarizeOutput: Equatable, Sendable, Codable {
public let summary: String
public let wordCount: Int

public init(summary: String, wordCount: Int) {
self.summary = summary
self.wordCount = wordCount
}
}

/// Combined pipeline final output (validate → process → summarize).
public struct TraverseStarterOutput: Equatable, Sendable, Codable {
public let validate: ValidateOutput
public let process: ProcessOutput
public let summarize: SummarizeOutput

public init(validate: ValidateOutput, process: ProcessOutput, summarize: SummarizeOutput) {
self.validate = validate
self.process = process
self.summarize = summarize
}

public static let empty = TraverseStarterOutput(
validate: ValidateOutput(valid: false, issues: []),
process: ProcessOutput(
title: "",
tags: [],
noteType: "",
suggestedNextAction: "",
status: ""
),
summarize: SummarizeOutput(summary: "", wordCount: 0)
)
}

public struct TraceEvent: Equatable, Sendable, Codable {
public let event_type: String
public let timestamp: String
Expand Down Expand Up @@ -136,20 +181,12 @@ public enum JSONValue: Equatable, Sendable, Codable {
public enum TraverseOutputParser {
public static func parse(_ raw: Any?) -> TraverseStarterOutput? {
guard let dict = raw as? [String: Any],
let title = dict["title"] as? String,
let tags = dict["tags"] as? [String],
let noteType = dict["noteType"] as? String,
let suggestedNextAction = dict["suggestedNextAction"] as? String,
let status = dict["status"] as? String else {
let validate = parseValidate(dict["validate"]),
let process = parseProcess(dict["process"]),
let summarize = parseSummarize(dict["summarize"]) else {
return nil
}
return TraverseStarterOutput(
title: title,
tags: tags,
noteType: noteType,
suggestedNextAction: suggestedNextAction,
status: status
)
return TraverseStarterOutput(validate: validate, process: process, summarize: summarize)
}

public static func parseEventPayload(_ raw: Any?) -> AppStateEventPayload? {
Expand All @@ -173,4 +210,47 @@ public enum TraverseOutputParser {
errorMessage: errorMessage
)
}

private static func parseValidate(_ raw: Any?) -> ValidateOutput? {
guard let dict = raw as? [String: Any],
let valid = dict["valid"] as? Bool,
let issues = dict["issues"] as? [String] else {
return nil
}
return ValidateOutput(valid: valid, issues: issues)
}

private static func parseProcess(_ raw: Any?) -> ProcessOutput? {
guard let dict = raw as? [String: Any],
let title = dict["title"] as? String,
let tags = dict["tags"] as? [String],
let noteType = dict["noteType"] as? String,
let suggestedNextAction = dict["suggestedNextAction"] as? String,
let status = dict["status"] as? String else {
return nil
}
return ProcessOutput(
title: title,
tags: tags,
noteType: noteType,
suggestedNextAction: suggestedNextAction,
status: status
)
}

private static func parseSummarize(_ raw: Any?) -> SummarizeOutput? {
guard let dict = raw as? [String: Any],
let summary = dict["summary"] as? String,
let wordCount = parseWordCount(dict["wordCount"]) else {
return nil
}
return SummarizeOutput(summary: summary, wordCount: wordCount)
}

private static func parseWordCount(_ raw: Any?) -> Int? {
if let value = raw as? Int { return value }
if let value = raw as? Double { return Int(value) }
if let value = raw as? String, let parsed = Int(value) { return parsed }
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,33 @@ final class TraverseClientTests: XCTestCase {

final class TraverseOutputTests: XCTestCase {
func testParseOutput() {
let raw: [String: Any] = [
"validate": ["valid": true, "issues": [] as [String]],
"process": [
"title": "T",
"tags": ["a"],
"noteType": "n",
"suggestedNextAction": "x",
"status": "done",
],
"summarize": ["summary": "A short summary", "wordCount": 3],
]
let output = TraverseOutputParser.parse(raw)
XCTAssertEqual(output?.process.title, "T")
XCTAssertEqual(output?.process.tags, ["a"])
XCTAssertEqual(output?.validate.valid, true)
XCTAssertEqual(output?.summarize.wordCount, 3)
}

func testParseRejectsFlatLegacyOutput() {
let raw: [String: Any] = [
"title": "T",
"tags": ["a"],
"noteType": "n",
"suggestedNextAction": "x",
"status": "done",
]
let output = TraverseOutputParser.parse(raw)
XCTAssertEqual(output?.title, "T")
XCTAssertEqual(output?.tags, ["a"])
XCTAssertNil(TraverseOutputParser.parse(raw))
}

func testParseEventPayload() {
Expand All @@ -99,16 +116,20 @@ final class TraverseOutputTests: XCTestCase {
"session_id": "sess-1",
"execution_id": "exec-1",
"output": [
"title": "T",
"tags": [] as [String],
"noteType": "n",
"suggestedNextAction": "x",
"status": "done",
"validate": ["valid": true, "issues": [] as [String]],
"process": [
"title": "T",
"tags": [] as [String],
"noteType": "n",
"suggestedNextAction": "x",
"status": "done",
],
"summarize": ["summary": "Summary", "wordCount": 1],
],
]
let payload = TraverseOutputParser.parseEventPayload(raw)
XCTAssertEqual(payload?.state, "results")
XCTAssertEqual(payload?.output?.title, "T")
XCTAssertEqual(payload?.output?.process.title, "T")
}
}

Expand Down Expand Up @@ -177,16 +198,20 @@ final class AppStateViewModelTests: XCTestCase {
sessionId: "sess-1",
executionId: "exec-1",
output: TraverseStarterOutput(
title: "T",
tags: [],
noteType: "n",
suggestedNextAction: "x",
status: "done"
validate: ValidateOutput(valid: true, issues: []),
process: ProcessOutput(
title: "T",
tags: [],
noteType: "n",
suggestedNextAction: "x",
status: "done"
),
summarize: SummarizeOutput(summary: "Summary", wordCount: 1)
)
)
)
XCTAssertEqual(vm.currentState, "results")
XCTAssertEqual(vm.output?.title, "T")
XCTAssertEqual(vm.output?.process.title, "T")
XCTAssertEqual(vm.executionId, "exec-1")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,7 @@ class ExecutionViewModel(
} catch (_: Exception) {
emptyList()
}
val output = result.output ?: TraverseStarterOutput(
title = "",
tags = emptyList(),
noteType = "",
suggestedNextAction = "",
status = "",
)
val output = result.output ?: TraverseStarterOutput.EMPTY
_uiState.update { it.copy(phase = ExecutionPhase.Succeeded(output, trace)) }
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,48 @@ package com.traverseframework.starter
import kotlinx.serialization.json.JsonElement

@kotlinx.serialization.Serializable
data class TraverseStarterOutput(
data class ValidateOutput(
val valid: Boolean,
val issues: List<String>,
)

@kotlinx.serialization.Serializable
data class ProcessOutput(
val title: String,
val tags: List<String>,
val noteType: String,
val suggestedNextAction: String,
val status: String,
)

@kotlinx.serialization.Serializable
data class SummarizeOutput(
val summary: String,
val wordCount: Int,
)

/** Combined pipeline final output (validate → process → summarize). */
@kotlinx.serialization.Serializable
data class TraverseStarterOutput(
val validate: ValidateOutput,
val process: ProcessOutput,
val summarize: SummarizeOutput,
) {
companion object {
val EMPTY = TraverseStarterOutput(
validate = ValidateOutput(valid = false, issues = emptyList()),
process = ProcessOutput(
title = "",
tags = emptyList(),
noteType = "",
suggestedNextAction = "",
status = "",
),
summarize = SummarizeOutput(summary = "", wordCount = 0),
)
}
}

@kotlinx.serialization.Serializable
data class TraceEvent(
val event_type: String,
Expand Down Expand Up @@ -39,7 +73,7 @@ data class ExecutionPollResult(
)

object AppConstants {
const val CAPABILITY_ID = "traverse-starter.process"
const val CAPABILITY_ID = "traverse-starter.pipeline"
const val DEFAULT_BASE_URL = "http://10.0.2.2:8787"
const val DEFAULT_WORKSPACE = "local-default"
const val NOTE_MAX_LENGTH = 2000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,15 @@ private fun OutputCard(

@Composable
private fun OutputFields(output: TraverseStarterOutput) {
Field("Title", output.title)
Field("Tags", output.tags.joinToString(", "))
Field("Note type", output.noteType)
Field("Next action", output.suggestedNextAction)
Field("Status", output.status)
Field("Valid", if (output.validate.valid) "yes" else "no")
Field("Issues", output.validate.issues.joinToString(", ").ifEmpty { "None" })
Field("Title", output.process.title)
Field("Note type", output.process.noteType)
Field("Status", output.process.status)
Field("Next action", output.process.suggestedNextAction)
Field("Tags", output.process.tags.joinToString(", "))
Field("Summary", output.summarize.summary)
Field("Word count", output.summarize.wordCount.toString())
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class TraverseClientTest {
val engine = MockEngine {
respond(
content = """
{"status":"succeeded","output":{"title":"T","tags":["a"],"noteType":"n","suggestedNextAction":"x","status":"done"}}
{"status":"succeeded","output":{"validate":{"valid":true,"issues":[]},"process":{"title":"T","tags":["a"],"noteType":"n","suggestedNextAction":"x","status":"done"},"summarize":{"summary":"Summary","wordCount":1}}}
""".trimIndent(),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json"),
Expand All @@ -53,6 +53,8 @@ class TraverseClientTest {
val client = TraverseClient(engine)
val result = client.pollExecution("http://10.0.2.2:8787", "local-default", "exec_abc")
assertEquals("succeeded", result.status)
assertEquals("T", result.output?.title)
assertEquals("T", result.output?.process?.title)
assertEquals(true, result.output?.validate?.valid)
assertEquals(1, result.output?.summarize?.wordCount)
}
}
8 changes: 1 addition & 7 deletions apps/traverse-starter/cli-rust/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,7 @@ pub fn execute(base_url: &str, workspace: &str, note: &str, json: bool) -> i32 {
.execution_id
.or(accepted.execution_id.clone())
.unwrap_or_default();
let output = event.output.unwrap_or(TraverseStarterOutput {
title: String::new(),
tags: vec![],
note_type: String::new(),
suggested_next_action: String::new(),
status: String::new(),
});
let output = event.output.unwrap_or_else(TraverseStarterOutput::empty);
let trace = if execution_id.is_empty() {
Vec::new()
} else {
Expand Down
2 changes: 1 addition & 1 deletion apps/traverse-starter/cli-rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct Cli {

#[derive(Subcommand)]
enum Commands {
/// Execute traverse-starter.process with a note
/// Execute traverse-starter.pipeline with a note
Run {
#[arg(long)]
note: String,
Expand Down
Loading
Loading