@agentdeck/shared 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/adapter.d.ts +3 -1
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adapter.js +24 -0
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/command-builders.d.ts +8 -2
  6. package/dist/command-builders.d.ts.map +1 -1
  7. package/dist/command-builders.js +20 -2
  8. package/dist/command-builders.js.map +1 -1
  9. package/dist/connection-status.d.ts +42 -0
  10. package/dist/connection-status.d.ts.map +1 -0
  11. package/dist/connection-status.js +52 -0
  12. package/dist/connection-status.js.map +1 -0
  13. package/dist/creature-layout.d.ts +18 -0
  14. package/dist/creature-layout.d.ts.map +1 -0
  15. package/dist/creature-layout.js +131 -0
  16. package/dist/creature-layout.js.map +1 -0
  17. package/dist/d200h-layout.d.ts +146 -0
  18. package/dist/d200h-layout.d.ts.map +1 -0
  19. package/dist/d200h-layout.js +808 -0
  20. package/dist/d200h-layout.js.map +1 -0
  21. package/dist/design-tokens.d.ts +261 -0
  22. package/dist/design-tokens.d.ts.map +1 -0
  23. package/dist/design-tokens.js +157 -0
  24. package/dist/design-tokens.js.map +1 -0
  25. package/dist/eval-schema.d.ts +32 -2
  26. package/dist/eval-schema.d.ts.map +1 -1
  27. package/dist/eval-schema.js.map +1 -1
  28. package/dist/format-utils.d.ts +38 -0
  29. package/dist/format-utils.d.ts.map +1 -1
  30. package/dist/format-utils.js +70 -0
  31. package/dist/format-utils.js.map +1 -1
  32. package/dist/gateway-protocol.d.ts +1 -1
  33. package/dist/gateway-protocol.js +1 -1
  34. package/dist/index.d.ts +11 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +11 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/llm-settings.d.ts +3 -3
  39. package/dist/llm-settings.d.ts.map +1 -1
  40. package/dist/llm-settings.js +3 -3
  41. package/dist/llm-settings.js.map +1 -1
  42. package/dist/net-utils.d.ts +2 -1
  43. package/dist/net-utils.d.ts.map +1 -1
  44. package/dist/net-utils.js +100 -3
  45. package/dist/net-utils.js.map +1 -1
  46. package/dist/pricing.d.ts +45 -0
  47. package/dist/pricing.d.ts.map +1 -0
  48. package/dist/pricing.js +93 -0
  49. package/dist/pricing.js.map +1 -0
  50. package/dist/protocol.d.ts +220 -5
  51. package/dist/protocol.d.ts.map +1 -1
  52. package/dist/protocol.js +11 -1
  53. package/dist/protocol.js.map +1 -1
  54. package/dist/sample.d.ts +152 -0
  55. package/dist/sample.d.ts.map +1 -0
  56. package/dist/sample.js +22 -0
  57. package/dist/sample.js.map +1 -0
  58. package/dist/session-utils.d.ts +82 -2
  59. package/dist/session-utils.d.ts.map +1 -1
  60. package/dist/session-utils.js +205 -10
  61. package/dist/session-utils.js.map +1 -1
  62. package/dist/state-colors.d.ts.map +1 -1
  63. package/dist/state-colors.js +2 -0
  64. package/dist/state-colors.js.map +1 -1
  65. package/dist/states.d.ts +1 -0
  66. package/dist/states.d.ts.map +1 -1
  67. package/dist/states.js +6 -1
  68. package/dist/states.js.map +1 -1
  69. package/dist/svg-renderers/agent-logos.d.ts +20 -12
  70. package/dist/svg-renderers/agent-logos.d.ts.map +1 -1
  71. package/dist/svg-renderers/agent-logos.js +154 -46
  72. package/dist/svg-renderers/agent-logos.js.map +1 -1
  73. package/dist/svg-renderers/session-slot-renderer.d.ts +71 -6
  74. package/dist/svg-renderers/session-slot-renderer.d.ts.map +1 -1
  75. package/dist/svg-renderers/session-slot-renderer.js +429 -209
  76. package/dist/svg-renderers/session-slot-renderer.js.map +1 -1
  77. package/dist/svg-renderers/text-utils.d.ts +14 -0
  78. package/dist/svg-renderers/text-utils.d.ts.map +1 -1
  79. package/dist/svg-renderers/text-utils.js +34 -0
  80. package/dist/svg-renderers/text-utils.js.map +1 -1
  81. package/dist/telemetry-envelope.d.ts +1 -1
  82. package/dist/telemetry-envelope.d.ts.map +1 -1
  83. package/dist/terrarium-rules.d.ts +46 -0
  84. package/dist/terrarium-rules.d.ts.map +1 -0
  85. package/dist/terrarium-rules.js +63 -0
  86. package/dist/terrarium-rules.js.map +1 -0
  87. package/dist/timeline-icons.d.ts +80 -0
  88. package/dist/timeline-icons.d.ts.map +1 -0
  89. package/dist/timeline-icons.js +174 -0
  90. package/dist/timeline-icons.js.map +1 -0
  91. package/dist/timeline-label.d.ts +38 -0
  92. package/dist/timeline-label.d.ts.map +1 -0
  93. package/dist/timeline-label.js +95 -0
  94. package/dist/timeline-label.js.map +1 -0
  95. package/dist/timeline-markdown.d.ts +80 -0
  96. package/dist/timeline-markdown.d.ts.map +1 -0
  97. package/dist/timeline-markdown.js +221 -0
  98. package/dist/timeline-markdown.js.map +1 -0
  99. package/dist/timeline-summarizer.d.ts +24 -0
  100. package/dist/timeline-summarizer.d.ts.map +1 -1
  101. package/dist/timeline-summarizer.js +55 -13
  102. package/dist/timeline-summarizer.js.map +1 -1
  103. package/dist/timeline-task-display.d.ts +66 -0
  104. package/dist/timeline-task-display.d.ts.map +1 -0
  105. package/dist/timeline-task-display.js +94 -0
  106. package/dist/timeline-task-display.js.map +1 -0
  107. package/dist/timeline.d.ts +101 -2
  108. package/dist/timeline.d.ts.map +1 -1
  109. package/dist/timeline.js +249 -11
  110. package/dist/timeline.js.map +1 -1
  111. package/dist/trmnl-layout.d.ts.map +1 -0
  112. package/dist/trmnl-layout.js.map +1 -0
  113. package/package.json +2 -1
@@ -0,0 +1,66 @@
1
+ /**
2
+ * timeline-task-display.ts — the SINGLE source of truth for how task
3
+ * hierarchy rows (`task_start` / `task_end`) are RENDERED.
4
+ *
5
+ * Contract (2026-07-19, "one row per task"):
6
+ * - `task_end` is a DATA-ONLY closure record. It stays on the wire and in
7
+ * every store — it stops the in-flight spinner (`isInFlightTask`), is the
8
+ * judge-result upsert vehicle (merge by type='task_end' + taskId), and is
9
+ * what the orphan reaper synthesizes — but renderers NEVER show it as a
10
+ * standalone row. Before this contract the visible timeline was dominated
11
+ * by reaper-synthesized "Interrupted · ~Xh" rows (no judge ever runs for
12
+ * those), while the judged closures were suppressed as internal
13
+ * boundaries: the noise showed and the signal hid.
14
+ * - A task renders as at most ONE row: its `task_start` header, which FOLDS
15
+ * IN the matching closure's fields — closure label (`task_end.raw`, e.g.
16
+ * "Session end · 2 turns · 6m 5s"), judge summary as the title when the
17
+ * header's own title is a bare "Task N", and the score/outcome badge.
18
+ * - Header visibility: meaningful title OR eval payload (own or closure).
19
+ * `_empty` category always hides. Bare unjudged tasks — including every
20
+ * interrupted reaper closure — render nothing; the timeline stays an
21
+ * activity log of actual turns.
22
+ *
23
+ * Mirrors (update in the same commit):
24
+ * - Apple `timelineShouldShowTaskMarker` / `timelineTaskClosure` /
25
+ * `timelineTaskHeaderDisplay` (apple/.../UI/Monitor/TimelineStripView.swift)
26
+ * - Android `shouldShowTaskMarker` / `taskClosure` / `taskHeaderDisplay`
27
+ * (android/.../state/TimelineDisplay.kt)
28
+ * - ESP32 boards exclude task rows from milestone/ticker selection
29
+ * (esp32/src/ui/eink/eink_display.cpp, esp32/src/ui/widgets/hud_bar.cpp)
30
+ * — no closure fold there; glance surfaces show turn rows only.
31
+ */
32
+ import type { TimelineEntry } from './timeline.js';
33
+ /** Fields a task header needs from its row or its closure. */
34
+ type TaskRowLike = Pick<TimelineEntry, 'type' | 'taskId' | 'raw' | 'ts' | 'endedAt' | 'taskScore' | 'taskOutcome' | 'taskCategory' | 'taskSummary' | 'boundarySignal'>;
35
+ /** False for empty titles and the auto-minted "Task N" / "작업 N" labels —
36
+ * those carry no information a reader can act on. */
37
+ export declare function timelineIsMeaningfulTaskTitle(raw: string | undefined | null): boolean;
38
+ /** The matching `task_end` closure record for a `task_start` header, if it
39
+ * has arrived among `siblings`. Undefined for non-headers and open tasks. */
40
+ export declare function timelineTaskClosure<T extends Pick<TimelineEntry, 'type' | 'taskId'>>(entry: Pick<TimelineEntry, 'type' | 'taskId'>, siblings: ReadonlyArray<T>): T | undefined;
41
+ /**
42
+ * Canonical render predicate for task hierarchy rows. Non-task rows pass
43
+ * through (`true`); callers keep their own rules for those.
44
+ */
45
+ export declare function timelineShouldRenderTaskRow(entry: TaskRowLike, siblings: ReadonlyArray<TaskRowLike>): boolean;
46
+ export interface TaskHeaderDisplay {
47
+ /** Header title: the row's own title when meaningful, else the judge's
48
+ * one-line summary from the closure, else the raw title as-is. */
49
+ title: string;
50
+ /** Closure label to render as a trailing chip ("Session end · 2 turns ·
51
+ * 6m 5s"). Undefined while the task is open. */
52
+ closureText?: string;
53
+ /** True once the matching `task_end` exists (spinner should be static). */
54
+ closed: boolean;
55
+ /** Badge inputs — closure fields win over the header's own (the judge
56
+ * upserts onto the closure record). */
57
+ taskScore?: number;
58
+ taskOutcome?: string;
59
+ /** Epoch ms the task closed at, for pending→unscored badge timing. */
60
+ closedAtMs?: number;
61
+ }
62
+ /** Decompose a `task_start` header + its closure into the displayed pieces.
63
+ * Callers must have already passed `timelineShouldRenderTaskRow`. */
64
+ export declare function timelineTaskHeaderDisplay(entry: TaskRowLike, siblings: ReadonlyArray<TaskRowLike>): TaskHeaderDisplay;
65
+ export {};
66
+ //# sourceMappingURL=timeline-task-display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-task-display.d.ts","sourceRoot":"","sources":["../src/timeline-task-display.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,8DAA8D;AAC9D,KAAK,WAAW,GAAG,IAAI,CACrB,aAAa,EACb,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,GAC5C,WAAW,GAAG,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,gBAAgB,CAChF,CAAC;AAIF;sDACsD;AACtD,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAIrF;AAED;8EAC8E;AAC9E,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC,EAClF,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC,EAC7C,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GACzB,CAAC,GAAG,SAAS,CAGf;AAUD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,aAAa,CAAC,WAAW,CAAC,GACnC,OAAO,CAQT;AAED,MAAM,WAAW,iBAAiB;IAChC;uEACmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd;qDACiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,OAAO,CAAC;IAChB;4CACwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;sEACsE;AACtE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,aAAa,CAAC,WAAW,CAAC,GACnC,iBAAiB,CAiBnB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * timeline-task-display.ts — the SINGLE source of truth for how task
3
+ * hierarchy rows (`task_start` / `task_end`) are RENDERED.
4
+ *
5
+ * Contract (2026-07-19, "one row per task"):
6
+ * - `task_end` is a DATA-ONLY closure record. It stays on the wire and in
7
+ * every store — it stops the in-flight spinner (`isInFlightTask`), is the
8
+ * judge-result upsert vehicle (merge by type='task_end' + taskId), and is
9
+ * what the orphan reaper synthesizes — but renderers NEVER show it as a
10
+ * standalone row. Before this contract the visible timeline was dominated
11
+ * by reaper-synthesized "Interrupted · ~Xh" rows (no judge ever runs for
12
+ * those), while the judged closures were suppressed as internal
13
+ * boundaries: the noise showed and the signal hid.
14
+ * - A task renders as at most ONE row: its `task_start` header, which FOLDS
15
+ * IN the matching closure's fields — closure label (`task_end.raw`, e.g.
16
+ * "Session end · 2 turns · 6m 5s"), judge summary as the title when the
17
+ * header's own title is a bare "Task N", and the score/outcome badge.
18
+ * - Header visibility: meaningful title OR eval payload (own or closure).
19
+ * `_empty` category always hides. Bare unjudged tasks — including every
20
+ * interrupted reaper closure — render nothing; the timeline stays an
21
+ * activity log of actual turns.
22
+ *
23
+ * Mirrors (update in the same commit):
24
+ * - Apple `timelineShouldShowTaskMarker` / `timelineTaskClosure` /
25
+ * `timelineTaskHeaderDisplay` (apple/.../UI/Monitor/TimelineStripView.swift)
26
+ * - Android `shouldShowTaskMarker` / `taskClosure` / `taskHeaderDisplay`
27
+ * (android/.../state/TimelineDisplay.kt)
28
+ * - ESP32 boards exclude task rows from milestone/ticker selection
29
+ * (esp32/src/ui/eink/eink_display.cpp, esp32/src/ui/widgets/hud_bar.cpp)
30
+ * — no closure fold there; glance surfaces show turn rows only.
31
+ */
32
+ const TASK_NUMBER_TITLES = [/^task\s+\d+$/i, /^작업\s*\d+$/];
33
+ /** False for empty titles and the auto-minted "Task N" / "작업 N" labels —
34
+ * those carry no information a reader can act on. */
35
+ export function timelineIsMeaningfulTaskTitle(raw) {
36
+ const title = (raw ?? '').trim();
37
+ if (!title)
38
+ return false;
39
+ return !TASK_NUMBER_TITLES.some((rx) => rx.test(title));
40
+ }
41
+ /** The matching `task_end` closure record for a `task_start` header, if it
42
+ * has arrived among `siblings`. Undefined for non-headers and open tasks. */
43
+ export function timelineTaskClosure(entry, siblings) {
44
+ if (entry.type !== 'task_start' || !entry.taskId)
45
+ return undefined;
46
+ return siblings.find((s) => s.type === 'task_end' && s.taskId === entry.taskId);
47
+ }
48
+ function hasEvalPayload(e) {
49
+ if (!e)
50
+ return false;
51
+ return e.taskScore != null ||
52
+ !!e.taskOutcome?.trim() ||
53
+ (!!e.taskCategory?.trim() && e.taskCategory !== '_empty') ||
54
+ !!e.taskSummary?.trim();
55
+ }
56
+ /**
57
+ * Canonical render predicate for task hierarchy rows. Non-task rows pass
58
+ * through (`true`); callers keep their own rules for those.
59
+ */
60
+ export function timelineShouldRenderTaskRow(entry, siblings) {
61
+ if (entry.type === 'task_end')
62
+ return false;
63
+ if (entry.type !== 'task_start')
64
+ return true;
65
+ if (entry.taskCategory === '_empty')
66
+ return false;
67
+ const closure = timelineTaskClosure(entry, siblings);
68
+ if (closure?.taskCategory === '_empty')
69
+ return false;
70
+ if (timelineIsMeaningfulTaskTitle(entry.raw))
71
+ return true;
72
+ return hasEvalPayload(entry) || hasEvalPayload(closure);
73
+ }
74
+ /** Decompose a `task_start` header + its closure into the displayed pieces.
75
+ * Callers must have already passed `timelineShouldRenderTaskRow`. */
76
+ export function timelineTaskHeaderDisplay(entry, siblings) {
77
+ const closure = timelineTaskClosure(entry, siblings);
78
+ const ownTitle = (entry.raw ?? '').trim();
79
+ const summary = (closure?.taskSummary ?? entry.taskSummary ?? '').trim();
80
+ const title = timelineIsMeaningfulTaskTitle(ownTitle) ? ownTitle : (summary || ownTitle);
81
+ const closureText = closure?.raw?.trim() || undefined;
82
+ const score = closure?.taskScore ?? entry.taskScore;
83
+ const outcome = (closure?.taskOutcome ?? entry.taskOutcome)?.trim() || undefined;
84
+ const closedAtMs = closure ? (closure.endedAt ?? closure.ts) : undefined;
85
+ return {
86
+ title,
87
+ ...(closureText ? { closureText } : {}),
88
+ closed: !!closure,
89
+ ...(score != null ? { taskScore: score } : {}),
90
+ ...(outcome ? { taskOutcome: outcome } : {}),
91
+ ...(closedAtMs != null ? { closedAtMs } : {}),
92
+ };
93
+ }
94
+ //# sourceMappingURL=timeline-task-display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-task-display.js","sourceRoot":"","sources":["../src/timeline-task-display.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,9 @@
2
2
  * Shared timeline types and log parser for OpenClaw mode.
3
3
  * Used by both bridge (BridgeLogStream) and plugin (LogStream).
4
4
  */
5
- export type TimelineEntryType = 'tool_request' | 'tool_resolved' | 'chat_start' | 'chat_end' | 'chat_response' | 'error' | 'scheduled' | 'user_action' | 'model_call' | 'model_response' | 'memory_recall' | 'tool_exec' | 'eval_result';
5
+ export type TimelineEntryType = 'tool_request' | 'tool_resolved' | 'chat_start' | 'chat_end' | 'chat_response' | 'error' | 'scheduled' | 'user_action' | 'model_call' | 'model_response' | 'memory_recall' | 'tool_exec' | 'eval_result' | 'task_start' | 'task_end' | 'task_milestone';
6
+ import type { TaskBoundarySignal } from './eval-schema.js';
7
+ export type { TaskBoundarySignal } from './eval-schema.js';
6
8
  export interface TimelineEntry {
7
9
  ts: number;
8
10
  type: TimelineEntryType;
@@ -11,12 +13,71 @@ export interface TimelineEntry {
11
13
  approvalId?: string;
12
14
  status?: 'pending' | 'approved' | 'denied';
13
15
  agentType?: string;
16
+ projectName?: string;
17
+ sessionId?: string;
18
+ runId?: string;
19
+ startedAt?: number;
20
+ endedAt?: number;
14
21
  repeatCount?: number;
15
22
  automated?: boolean;
23
+ /** APME task id. Set on task_start/task_end and on every turn entry inside the task scope. */
24
+ taskId?: string;
25
+ /** Only on task_end. Why the task closed. */
26
+ boundarySignal?: TaskBoundarySignal;
27
+ /**
28
+ * Per-task evaluation result, attached when the task_judge resolves
29
+ * (always AFTER the initial task_end emit — clients upsert the existing
30
+ * row by (type='task_end', taskId) and merge these four fields).
31
+ *
32
+ * - `taskScore` : 0..1 composite (matches `tasks.composite_score`)
33
+ * - `taskOutcome` : terminal outcome string from `bridge/src/apme/outcome.ts`
34
+ * (`'committed' | 'abandoned' | 'iterated' | 'ab_winner' | 'ab_loser'
35
+ * | 'interrupted' | 'exploratory' | 'pending'`). UIs collapse into
36
+ * three visual classes — success / fail / partial / pending — and pick
37
+ * the badge color via design-system status tokens.
38
+ * - `taskCategory`: task_rollup category ('general' | 'conversation' | …)
39
+ * - `taskSummary` : one-line judge summary
40
+ */
41
+ taskScore?: number;
42
+ taskOutcome?: string;
43
+ taskCategory?: string;
44
+ taskSummary?: string;
45
+ /**
46
+ * How the row's `raw` summary was produced. Lets clients decide whether
47
+ * the detail pane is worth showing — when `'none'`, detail is just the
48
+ * unfiltered response that the heuristic couldn't summarize, and showing
49
+ * it duplicates content rather than adding value.
50
+ * - `'llm'` : LLM-summarized (clean, short, distinct from detail)
51
+ * - `'heuristic'`: topic-hint extracted from response or prompt
52
+ * - `'none'` : last-resort fallback (literal "Completed", bare tool name, etc.)
53
+ * - `'progress'`: non-terminal assistant status update (work still running)
54
+ */
55
+ summaryKind?: 'llm' | 'heuristic' | 'none' | 'progress';
16
56
  }
57
+ /**
58
+ * Prepare a chat-response body for storage as `entry.detail` while keeping
59
+ * markdown intact. Use this on chat_response / chat_end emit paths so the
60
+ * client renderer can apply heading / bullet / table / inline span styles —
61
+ * `cleanDetailText` would have stripped all of that before it ever reached
62
+ * the dashboard.
63
+ *
64
+ * Filters and caps:
65
+ * - System JSON blobs (`{"connectionId": ...}` etc.) → empty
66
+ * - Error JSON (`{"error": ...}`) → the error string
67
+ * - Other JSON object → compact stringified form (200 char cap)
68
+ * - Otherwise → original text with 3+ blank lines collapsed to 2, trimmed
69
+ *
70
+ * Markdown markers (`**`, `##`, `|`, `` ` ``, `[text](url)`, etc.) are
71
+ * preserved verbatim so the client `parseTimelineMarkdown` /
72
+ * `parseInlineSpans` can do their job.
73
+ */
74
+ export declare function prepareMarkdownDetail(text: string): string;
17
75
  /**
18
76
  * Clean detail text — strip markdown artifacts, JSON blobs, and system noise.
19
- * Applied to timeline entry `detail` fields before storage.
77
+ * Applied to timeline entry `detail` fields before storage on **non-chat**
78
+ * paths (tool errors, system log lines). Chat response detail should go
79
+ * through `prepareMarkdownDetail` instead so the dashboard can render the
80
+ * markdown.
20
81
  */
21
82
  export declare function cleanDetailText(text: string): string;
22
83
  /**
@@ -29,6 +90,44 @@ export declare function cleanRawText(text: string): string;
29
90
  * Applied to both raw and detail fields.
30
91
  */
31
92
  export declare function cleanNopMarkers(text: string): string;
93
+ export declare function isAssistantProgressUpdate(text?: string): boolean;
94
+ export declare function detailHasRealSignal(detail?: string): boolean;
95
+ export declare function isOpenClawPlaceholderRaw(raw: string): boolean;
96
+ /**
97
+ * A user prompt that invokes a slash command ("/merge", "/session-end args").
98
+ *
99
+ * Two wire shapes reach the daemons' UserPromptSubmit paths:
100
+ * 1. Plain text — the user's literal keystrokes: "/merge --squash"
101
+ * 2. Claude Code's command XML envelope (what the transcript records and
102
+ * some hook versions forward):
103
+ * <command-message>merge</command-message>
104
+ * <command-name>/merge</command-name>
105
+ * <command-args>--squash</command-args>
106
+ *
107
+ * `parseSlashCommandPrompt` recognizes both and returns the normalized
108
+ * invocation, so emit paths can store a clean "/merge --squash" row and
109
+ * renderers can style command turns distinctly (terminal glyph instead of a
110
+ * chat bubble). Returns null for ordinary prompts — including absolute paths
111
+ * ("/Users/x/file" never matches because path segments contain further "/").
112
+ *
113
+ * Mirror: TimelineStripView.swift `timelineSlashCommand` must match.
114
+ */
115
+ export interface SlashCommandInvocation {
116
+ /** Command including the leading slash, e.g. "/merge". */
117
+ command: string;
118
+ /** Argument string after the command, if any. */
119
+ args?: string;
120
+ }
121
+ export declare function parseSlashCommandPrompt(text?: string): SlashCommandInvocation | null;
122
+ /** Normalize a prompt for timeline display: command XML envelopes collapse to
123
+ * their "/name args" form; everything else passes through unchanged. */
124
+ export declare function normalizeCommandPrompt(text: string): string;
125
+ export declare function isTaskNotificationChatStart(entry: TimelineEntry): boolean;
126
+ export declare function isOpenClawLowSignalResponse(entry: TimelineEntry): boolean;
127
+ export declare function isOpenClawCronPrompt(text?: string): boolean;
128
+ export declare function summarizeOpenClawCronPrompt(text?: string): string;
129
+ export declare function shouldDropLowSignalTimelineEntry(entry: TimelineEntry): boolean;
130
+ export declare function normalizeTimelineEntryForStorage(entry: TimelineEntry): TimelineEntry | null;
32
131
  /**
33
132
  * Extract the semantic core of a timeline entry for dedup comparison.
34
133
  * For chat_end: strip duration/tool suffix (everything after first ' · ').
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,iBAAiB,GACzB,cAAc,GAAG,eAAe,GAAG,YAAY,GAAG,UAAU,GAC5D,eAAe,GAAG,OAAO,GAAG,WAAW,GAAG,aAAa,GACvD,YAAY,GAAG,gBAAgB,GAAG,eAAe,GAAG,WAAW,GAC/D,aAAa,CAAC;AAElB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAuDD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA8CpD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAMhF;AA2DD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,aAAa,EACpB,aAAa,EAAE,SAAS,aAAa,EAAE,EACvC,QAAQ,SAAY,GACnB,MAAM,CA4BR;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAE5C,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,SAAS,aAAa,EAAE,GAChC,iBAAiB,CAgCnB;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA2MhE"}
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
package/dist/timeline.js CHANGED
@@ -56,9 +56,49 @@ function extractReadableMessage(message) {
56
56
  cleaned = cleaned.slice(0, 497) + '...';
57
57
  return cleaned || message.slice(0, 500);
58
58
  }
59
+ /**
60
+ * Prepare a chat-response body for storage as `entry.detail` while keeping
61
+ * markdown intact. Use this on chat_response / chat_end emit paths so the
62
+ * client renderer can apply heading / bullet / table / inline span styles —
63
+ * `cleanDetailText` would have stripped all of that before it ever reached
64
+ * the dashboard.
65
+ *
66
+ * Filters and caps:
67
+ * - System JSON blobs (`{"connectionId": ...}` etc.) → empty
68
+ * - Error JSON (`{"error": ...}`) → the error string
69
+ * - Other JSON object → compact stringified form (200 char cap)
70
+ * - Otherwise → original text with 3+ blank lines collapsed to 2, trimmed
71
+ *
72
+ * Markdown markers (`**`, `##`, `|`, `` ` ``, `[text](url)`, etc.) are
73
+ * preserved verbatim so the client `parseTimelineMarkdown` /
74
+ * `parseInlineSpans` can do their job.
75
+ */
76
+ export function prepareMarkdownDetail(text) {
77
+ if (!text)
78
+ return text;
79
+ if (typeof text !== 'string')
80
+ return '';
81
+ const trimmed = text.trim();
82
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
83
+ try {
84
+ const parsed = JSON.parse(trimmed);
85
+ if (parsed.connectionId || parsed.stateVersion || parsed.seq)
86
+ return '';
87
+ if (parsed.error)
88
+ return String(parsed.error);
89
+ return JSON.stringify(parsed).slice(0, 200);
90
+ }
91
+ catch { /* not valid JSON, fall through */ }
92
+ }
93
+ // Preserve markdown — only collapse runs of blank lines + trim ends.
94
+ return text.replace(/\n{3,}/g, '\n\n').trim();
95
+ }
59
96
  /**
60
97
  * Clean detail text — strip markdown artifacts, JSON blobs, and system noise.
61
- * Applied to timeline entry `detail` fields before storage.
98
+ * Applied to timeline entry `detail` fields before storage on **non-chat**
99
+ * paths (tool errors, system log lines). Chat response detail should go
100
+ * through `prepareMarkdownDetail` instead so the dashboard can render the
101
+ * markdown.
62
102
  */
63
103
  export function cleanDetailText(text) {
64
104
  if (!text)
@@ -129,6 +169,189 @@ export function cleanNopMarkers(text) {
129
169
  .replace(/\n{3,}/g, '\n\n')
130
170
  .trim();
131
171
  }
172
+ export function isAssistantProgressUpdate(text) {
173
+ if (!text)
174
+ return false;
175
+ const trimmed = text.trim();
176
+ if (!trimmed)
177
+ return false;
178
+ const head = trimmed.slice(0, 800);
179
+ const lower = head.toLowerCase();
180
+ const explicitProgress = /\b(still|currently|continues? to|is|are)\s+(running|building|installing|executing|processing|waiting)\b/.test(lower) ||
181
+ /\b(still running|still building|build is running|is still running|are still running)\b/.test(lower) ||
182
+ /\b(waiting for|wait until|once (?:the )?.*(?:finishes|completes|arrives)|continue once|will continue once|i.ll continue once)\b/.test(lower) ||
183
+ /\b(no interim lines|buffers? output until completion|tail buffers output)\b/.test(lower) ||
184
+ /(아직|계속)\s*(실행|진행|빌드|설치)\s*중/.test(head) ||
185
+ /(완료|끝나|도착)면\s*(계속|이어)/.test(head) ||
186
+ /(기다리는 중|대기 중)/.test(head);
187
+ if (!explicitProgress)
188
+ return false;
189
+ const startsAsFinal = /^(done|completed|complete|fixed|merged|verified|all done)\b/i.test(trimmed) ||
190
+ /^(완료|수정 완료|검증 완료|반영 완료|머지 완료)/.test(trimmed);
191
+ return !startsAsFinal;
192
+ }
193
+ export function detailHasRealSignal(detail) {
194
+ if (!detail)
195
+ return false;
196
+ for (const line of detail.split(/\r?\n/)) {
197
+ const trimmed = line.trim();
198
+ if (!trimmed)
199
+ continue;
200
+ if (!trimmed.toLowerCase().startsWith('status:'))
201
+ return true;
202
+ }
203
+ return false;
204
+ }
205
+ export function isOpenClawPlaceholderRaw(raw) {
206
+ const normalized = raw.trim().toLowerCase();
207
+ return normalized === 'tool' || normalized.startsWith('tool \u00b7 ');
208
+ }
209
+ export function parseSlashCommandPrompt(text) {
210
+ if (!text)
211
+ return null;
212
+ const trimmed = text.trim();
213
+ if (!trimmed)
214
+ return null;
215
+ // Shape 2: command XML envelope.
216
+ if (trimmed.includes('<command-name>')) {
217
+ const name = /<command-name>\s*(\/?[A-Za-z][\w:-]*)\s*<\/command-name>/.exec(trimmed)?.[1];
218
+ if (!name)
219
+ return null;
220
+ const args = /<command-args>([\s\S]*?)<\/command-args>/.exec(trimmed)?.[1]?.trim();
221
+ return {
222
+ command: name.startsWith('/') ? name : `/${name}`,
223
+ ...(args ? { args } : {}),
224
+ };
225
+ }
226
+ // Shape 1: plain "/command [args...]" — the command token must be the whole
227
+ // first word (a "/" inside the token means it's a filesystem path, not a
228
+ // command).
229
+ const m = /^\/([A-Za-z][\w:-]*)(?:\s+([\s\S]+))?$/.exec(trimmed);
230
+ if (!m)
231
+ return null;
232
+ const args = m[2]?.trim();
233
+ return { command: `/${m[1]}`, ...(args ? { args } : {}) };
234
+ }
235
+ /** Normalize a prompt for timeline display: command XML envelopes collapse to
236
+ * their "/name args" form; everything else passes through unchanged. */
237
+ export function normalizeCommandPrompt(text) {
238
+ if (!text.includes('<command-name>'))
239
+ return text;
240
+ const cmd = parseSlashCommandPrompt(text);
241
+ if (!cmd)
242
+ return text;
243
+ return cmd.args ? `${cmd.command} ${cmd.args}` : cmd.command;
244
+ }
245
+ export function isTaskNotificationChatStart(entry) {
246
+ if (entry.type !== 'chat_start')
247
+ return false;
248
+ const raw = entry.raw.trim().toLowerCase();
249
+ const detail = entry.detail?.trim().toLowerCase() ?? '';
250
+ return raw.startsWith('<task-notification>') || detail.startsWith('<task-notification>');
251
+ }
252
+ function hasOpenClawNotificationFailureSignal(text) {
253
+ return /\b(line|notification|userid|target id|target issue)\b/i.test(text) &&
254
+ /\b(fail(?:ed|ure)?|missing|unconfigured|notified|needed|pending)\b/i.test(text) ||
255
+ /(LINE|알림|userId|사용자 ID|대상 ID).*(실패|미등록|미설정|구성되지|필요|대기)/i.test(text);
256
+ }
257
+ export function isOpenClawLowSignalResponse(entry) {
258
+ if (entry.agentType !== 'openclaw')
259
+ return false;
260
+ const isResponse = entry.type === 'chat_response' || entry.type === 'model_response';
261
+ const isAutomatedStart = entry.type === 'chat_start' && entry.automated === true;
262
+ if (!isResponse && !isAutomatedStart)
263
+ return false;
264
+ const text = `${entry.raw}\n${entry.detail ?? ''}`.trim();
265
+ if (!text)
266
+ return false;
267
+ if (hasOpenClawNotificationFailureSignal(text))
268
+ return false;
269
+ const lower = text.toLowerCase();
270
+ const hasNoReply = /\bno_reply\b/.test(lower);
271
+ const looksLikePolling = /\bstill translating\b/.test(lower) ||
272
+ /\btranslation still in progress\b/.test(lower) ||
273
+ /\bnot all .*?(terminal|published|failed|complete|completed)\b/.test(lower) ||
274
+ /\b(in progress|still active|no action needed|nothing to notify yet)\b/.test(lower) ||
275
+ /\bcron job (?:stays|retained|active)\b/.test(lower) ||
276
+ /\bpipeline still active\b/.test(lower) ||
277
+ /(아직|여전히|계속).*(번역|진행)\s*중/.test(text) ||
278
+ /알릴 필요 없음|수행할 작업이 없음|대기합니다/.test(text);
279
+ if (isAutomatedStart)
280
+ return looksLikePolling;
281
+ return looksLikePolling && (hasNoReply || /no action needed|nothing to notify yet|알릴 필요 없음/.test(lower));
282
+ }
283
+ export function isOpenClawCronPrompt(text) {
284
+ return !!text && text.trimStart().startsWith('[cron:');
285
+ }
286
+ export function summarizeOpenClawCronPrompt(text) {
287
+ if (!text)
288
+ return '자동 작업';
289
+ const trimmed = text.trimStart();
290
+ const m = trimmed.match(/^\[cron:[^\s\]]+\s+([^\]]+)\]/);
291
+ if (!m)
292
+ return '자동 작업';
293
+ const job = m[1]
294
+ .replace(/[-_]+/g, ' ')
295
+ .replace(/\s+/g, ' ')
296
+ .trim();
297
+ if (!job)
298
+ return '자동 작업';
299
+ const capped = job.length > 64 ? `${job.slice(0, 61)}...` : job;
300
+ return `자동 작업 \u00b7 ${capped}`;
301
+ }
302
+ export function shouldDropLowSignalTimelineEntry(entry) {
303
+ if (isTaskNotificationChatStart(entry))
304
+ return true;
305
+ if (isOpenClawLowSignalResponse(entry))
306
+ return true;
307
+ const lowSignalTypes = new Set(['tool_exec', 'tool_request', 'tool_resolved']);
308
+ if (!lowSignalTypes.has(entry.type))
309
+ return false;
310
+ // Observed-agent tool hooks are extremely high volume (Bash/MCP/read/
311
+ // todowrite start+complete for every internal action). Keep them available
312
+ // to APME ingestion, but do not persist them into the bounded user-facing
313
+ // timeline buffer — otherwise a tool-heavy observed turn drowns its own
314
+ // prompt/response rows (OpenCode had no suppression and flooded the strip
315
+ // with one row per tool while Claude/Codex read clean). Antigravity is
316
+ // included forward-compat: `classifyObservedHookEvent` already accepts
317
+ // `antigravity_*` hooks, so if an AGY observer producer lands its tool rows
318
+ // must not flood the strip either.
319
+ if ((entry.agentType === 'codex-cli' ||
320
+ entry.agentType === 'codex-app' ||
321
+ entry.agentType === 'opencode' ||
322
+ entry.agentType === 'antigravity') &&
323
+ entry.type === 'tool_exec') {
324
+ return true;
325
+ }
326
+ if (detailHasRealSignal(entry.detail))
327
+ return false;
328
+ const raw = entry.raw.trim().toLowerCase();
329
+ if ((entry.agentType === 'codex-cli' || entry.agentType === 'codex-app') && entry.sessionId === 'codex:otel-active') {
330
+ return new Set(['tool', 'tool completed', 'unknown', 'unknown completed', 'exec', 'exec completed']).has(raw);
331
+ }
332
+ if (entry.agentType === 'openclaw') {
333
+ return isOpenClawPlaceholderRaw(raw);
334
+ }
335
+ return false;
336
+ }
337
+ export function normalizeTimelineEntryForStorage(entry) {
338
+ if (shouldDropLowSignalTimelineEntry(entry))
339
+ return null;
340
+ if (entry.agentType === 'openclaw' &&
341
+ entry.type === 'model_call' &&
342
+ (entry.automated || isOpenClawCronPrompt(entry.raw) || isOpenClawCronPrompt(entry.detail)) &&
343
+ (isOpenClawCronPrompt(entry.raw) || isOpenClawCronPrompt(entry.detail))) {
344
+ const source = isOpenClawCronPrompt(entry.raw) ? entry.raw : entry.detail;
345
+ return {
346
+ ...entry,
347
+ raw: summarizeOpenClawCronPrompt(source),
348
+ detail: undefined,
349
+ automated: true,
350
+ summaryKind: entry.summaryKind ?? 'heuristic',
351
+ };
352
+ }
353
+ return entry;
354
+ }
132
355
  /**
133
356
  * Extract the semantic core of a timeline entry for dedup comparison.
134
357
  * For chat_end: strip duration/tool suffix (everything after first ' · ').
@@ -213,6 +436,14 @@ export function isRepetitiveEntry(entry, recentEntries, windowMs = 3_600_000) {
213
436
  break;
214
437
  if (e.type !== entry.type)
215
438
  continue;
439
+ // Never merge across sessions. Turn rows are structural (a chat_end pairs
440
+ // with ITS session's chat_start); merging session B's "Interrupted"/
441
+ // "Completed" close into session A's row moves the close out of B — and
442
+ // the merge path reassigns sessionId, stealing A's close too. Only applies
443
+ // when both sides carry a sessionId (legacy unattributed rows keep the
444
+ // old behavior).
445
+ if (entry.sessionId && e.sessionId && entry.sessionId !== e.sessionId)
446
+ continue;
216
447
  // Automated entries: content-agnostic dedup (any two automated chats collapse)
217
448
  if (entry.automated && e.automated)
218
449
  return i;
@@ -228,6 +459,13 @@ export function deduplicateEntry(entry, entries) {
228
459
  entry = { ...entry, raw: cleanNopMarkers(cleanRawText(entry.raw)) };
229
460
  if (entry.detail)
230
461
  entry = { ...entry, detail: cleanNopMarkers(entry.detail) };
462
+ // Task hierarchy entries bypass dedup — they're hierarchy markers keyed by
463
+ // taskId, not content. Two adjacent task_starts are legitimately distinct,
464
+ // and two `task_milestone` rows carrying identical raw ("Todos done") but
465
+ // different taskIds within the 8s exact-dedup window must not collapse.
466
+ if (entry.type === 'task_start' || entry.type === 'task_end' || entry.type === 'task_milestone') {
467
+ return { action: 'add', entry };
468
+ }
231
469
  // 2. Exact dedup: skip if same type + raw within 8 seconds
232
470
  for (let i = entries.length - 1; i >= 0; i--) {
233
471
  const e = entries[i];
@@ -236,7 +474,14 @@ export function deduplicateEntry(entry, entries) {
236
474
  if (e.type === entry.type && e.raw === entry.raw)
237
475
  return { action: 'skip' };
238
476
  }
239
- // 3. Repetitive entry dedup (1h window)
477
+ // 3. Repetitive entry dedup (1h window). Interrupted turn-closes are exempt:
478
+ // they are structural pair-closers for a specific turn (their semantic core
479
+ // collapses to just "Interrupted", so two interrupted turns in the window
480
+ // would merge — silently reopening the earlier turn's spinner). The exact
481
+ // 8s dedup above still absorbs duplicate emits of the same close.
482
+ if (entry.type === 'chat_end' && /^Interrupted\b/.test(entry.raw)) {
483
+ return { action: 'add', entry };
484
+ }
240
485
  const repIdx = isRepetitiveEntry(entry, entries);
241
486
  if (repIdx >= 0) {
242
487
  let removeChatStartIndex;
@@ -442,15 +687,8 @@ export function parseLogLine(json) {
442
687
  if (/\b(inference|model|llm)\b.*\b(start|request|call|complet|done|response|finish)\b/i.test(message)) {
443
688
  return null;
444
689
  }
445
- // Memory patterns in message text
446
- if (/\b(memory|recall|search)\b/i.test(message)) {
447
- return { ts, type: 'memory_recall', raw: extractReadableMessage(message) };
448
- }
449
- // Tool/exec patterns in message text
450
- if (/\b(tool|exec|execute|command)\b/i.test(message)) {
451
- return { ts, type: 'tool_exec', raw: extractReadableMessage(message) };
452
- }
453
- // Unrecognized — skip
690
+ // Anything else skip. Real chat/tool/error activity flows through the
691
+ // OpenClaw Gateway adapter's RPC events, not heuristic log parsing.
454
692
  return null;
455
693
  }
456
694
  //# sourceMappingURL=timeline.js.map