@agentdeck/bridge 1.0.19 → 1.0.21
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.
- package/dist/adapters/openclaw.d.ts +55 -1
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +278 -45
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adb-reverse.d.ts +26 -3
- package/dist/adb-reverse.d.ts.map +1 -1
- package/dist/adb-reverse.js +26 -5
- package/dist/adb-reverse.js.map +1 -1
- package/dist/apme/adapters/codex-turn-manager.js +6 -1
- package/dist/apme/adapters/codex-turn-manager.js.map +1 -1
- package/dist/apme/adapters/openclaw-hook.d.ts +45 -1
- package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -1
- package/dist/apme/adapters/openclaw-hook.js +84 -4
- package/dist/apme/adapters/openclaw-hook.js.map +1 -1
- package/dist/apme/claude-transcript-reader.d.ts +76 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
- package/dist/apme/claude-transcript-reader.js +152 -1
- package/dist/apme/claude-transcript-reader.js.map +1 -1
- package/dist/apme/collector.d.ts +63 -5
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +147 -10
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/store.d.ts +23 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +81 -2
- package/dist/apme/store.js.map +1 -1
- package/dist/apme/types.d.ts +1 -1
- package/dist/apme/types.d.ts.map +1 -1
- package/dist/apme/types.js.map +1 -1
- package/dist/auth.d.ts +37 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +23 -0
- package/dist/auth.js.map +1 -1
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +6 -1
- package/dist/bridge-core.js.map +1 -1
- package/dist/claude-interrupt-marker.d.ts +31 -0
- package/dist/claude-interrupt-marker.d.ts.map +1 -0
- package/dist/claude-interrupt-marker.js +58 -0
- package/dist/claude-interrupt-marker.js.map +1 -0
- package/dist/claude-turn-watchdog.d.ts +93 -0
- package/dist/claude-turn-watchdog.d.ts.map +1 -0
- package/dist/claude-turn-watchdog.js +173 -0
- package/dist/claude-turn-watchdog.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +399 -115
- package/dist/cli.js.map +1 -1
- package/dist/codex-hook-silence.d.ts +52 -0
- package/dist/codex-hook-silence.d.ts.map +1 -0
- package/dist/codex-hook-silence.js +80 -0
- package/dist/codex-hook-silence.js.map +1 -0
- package/dist/daemon-port.d.ts +90 -0
- package/dist/daemon-port.d.ts.map +1 -0
- package/dist/daemon-port.js +119 -0
- package/dist/daemon-port.js.map +1 -0
- package/dist/daemon-server.d.ts +14 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +297 -69
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-settings.d.ts +46 -0
- package/dist/daemon-settings.d.ts.map +1 -0
- package/dist/daemon-settings.js +85 -0
- package/dist/daemon-settings.js.map +1 -0
- package/dist/daemon-takeover.d.ts +75 -0
- package/dist/daemon-takeover.d.ts.map +1 -0
- package/dist/daemon-takeover.js +137 -0
- package/dist/daemon-takeover.js.map +1 -0
- package/dist/daemon.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -12
- package/dist/index.js.map +1 -1
- package/dist/kiro-diagnostics.d.ts +90 -0
- package/dist/kiro-diagnostics.d.ts.map +1 -0
- package/dist/kiro-diagnostics.js +278 -0
- package/dist/kiro-diagnostics.js.map +1 -0
- package/dist/kiro-session.d.ts +113 -0
- package/dist/kiro-session.d.ts.map +1 -0
- package/dist/kiro-session.js +688 -0
- package/dist/kiro-session.js.map +1 -0
- package/dist/kiro-timeline-feed.d.ts +45 -0
- package/dist/kiro-timeline-feed.d.ts.map +1 -0
- package/dist/kiro-timeline-feed.js +116 -0
- package/dist/kiro-timeline-feed.js.map +1 -0
- package/dist/kiro-transcript-timeline.d.ts +57 -0
- package/dist/kiro-transcript-timeline.d.ts.map +1 -0
- package/dist/kiro-transcript-timeline.js +183 -0
- package/dist/kiro-transcript-timeline.js.map +1 -0
- package/dist/linux-service.d.ts +2 -1
- package/dist/linux-service.d.ts.map +1 -1
- package/dist/linux-service.js +8 -3
- package/dist/linux-service.js.map +1 -1
- package/dist/mdns-discover.d.ts +27 -0
- package/dist/mdns-discover.d.ts.map +1 -1
- package/dist/mdns-discover.js +60 -7
- package/dist/mdns-discover.js.map +1 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +16 -3
- package/dist/mdns.js.map +1 -1
- package/dist/modules/adb-module.d.ts.map +1 -1
- package/dist/modules/adb-module.js +5 -2
- package/dist/modules/adb-module.js.map +1 -1
- package/dist/modules/index.d.ts +2 -1
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -0
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/types.d.ts +34 -9
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js +34 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/network-posture.d.ts +67 -0
- package/dist/network-posture.d.ts.map +1 -0
- package/dist/network-posture.js +100 -0
- package/dist/network-posture.js.map +1 -0
- package/dist/observed-steering.d.ts +14 -0
- package/dist/observed-steering.d.ts.map +1 -1
- package/dist/observed-steering.js +18 -0
- package/dist/observed-steering.js.map +1 -1
- package/dist/observed-turn-watchdogs.d.ts +84 -0
- package/dist/observed-turn-watchdogs.d.ts.map +1 -0
- package/dist/observed-turn-watchdogs.js +134 -0
- package/dist/observed-turn-watchdogs.js.map +1 -0
- package/dist/openclaw-session.d.ts +10 -0
- package/dist/openclaw-session.d.ts.map +1 -1
- package/dist/openclaw-session.js +12 -0
- package/dist/openclaw-session.js.map +1 -1
- package/dist/passive-observer.d.ts +19 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +208 -24
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/micro-glyphs.d.ts +1 -1
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +2 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.js +47 -0
- package/dist/pixoo/official-dot-glyphs.generated.js.map +1 -1
- package/dist/pixoo/pixoo-discover.d.ts +8 -1
- package/dist/pixoo/pixoo-discover.d.ts.map +1 -1
- package/dist/pixoo/pixoo-discover.js +15 -8
- package/dist/pixoo/pixoo-discover.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +4 -2
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +105 -36
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pixoo/pixoo-settings.d.ts +19 -2
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
- package/dist/pixoo/pixoo-settings.js +22 -4
- package/dist/pixoo/pixoo-settings.js.map +1 -1
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
- package/dist/pixoo/pixoo-sprites.js +2 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -1
- package/dist/session-aggregator.d.ts +9 -1
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +1 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-registry.d.ts +71 -3
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +126 -19
- package/dist/session-registry.js.map +1 -1
- package/dist/state-machine.d.ts +38 -1
- package/dist/state-machine.d.ts.map +1 -1
- package/dist/state-machine.js +112 -8
- package/dist/state-machine.js.map +1 -1
- package/dist/subagent-timeline.d.ts +38 -7
- package/dist/subagent-timeline.d.ts.map +1 -1
- package/dist/subagent-timeline.js +154 -23
- package/dist/subagent-timeline.js.map +1 -1
- package/dist/timeline-store.d.ts.map +1 -1
- package/dist/timeline-store.js +26 -1
- package/dist/timeline-store.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +13 -1
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/terrarium.d.ts.map +1 -1
- package/dist/tui/terrarium.js +10 -5
- package/dist/tui/terrarium.js.map +1 -1
- package/dist/utils/redact-secrets.d.ts +3 -0
- package/dist/utils/redact-secrets.d.ts.map +1 -0
- package/dist/utils/redact-secrets.js +29 -0
- package/dist/utils/redact-secrets.js.map +1 -0
- package/dist/version-check.d.ts +8 -0
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +27 -0
- package/dist/version-check.js.map +1 -1
- package/dist/windows-service.d.ts +2 -1
- package/dist/windows-service.d.ts.map +1 -1
- package/dist/windows-service.js +6 -3
- package/dist/windows-service.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import { open, readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { stripUnsafeText } from '@agentdeck/shared';
|
|
6
|
+
import { redactSecrets } from './utils/redact-secrets.js';
|
|
7
|
+
const MAX_SESSION_FILES = 200;
|
|
8
|
+
const MAX_TRANSCRIPT_SAMPLE_BYTES = 1024 * 1024;
|
|
9
|
+
const KIRO_PENDING_TURN_MAX_AGE_MS = 10 * 60 * 1000;
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
/** Reuses parsed snapshots across the passive observer's five-second polls. */
|
|
12
|
+
export class KiroSessionCache {
|
|
13
|
+
entries = new Map();
|
|
14
|
+
get(path, signature) {
|
|
15
|
+
const entry = this.entries.get(path);
|
|
16
|
+
return entry &&
|
|
17
|
+
entry.transcriptMtimeMs === signature.transcriptMtimeMs &&
|
|
18
|
+
entry.transcriptSize === signature.transcriptSize &&
|
|
19
|
+
entry.metadataMtimeMs === signature.metadataMtimeMs &&
|
|
20
|
+
entry.metadataSize === signature.metadataSize
|
|
21
|
+
? entry.snapshot
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
set(path, entry) {
|
|
25
|
+
this.entries.set(path, entry);
|
|
26
|
+
}
|
|
27
|
+
retain(paths) {
|
|
28
|
+
for (const path of this.entries.keys()) {
|
|
29
|
+
if (!paths.has(path))
|
|
30
|
+
this.entries.delete(path);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.entries.size;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Kiro's relocatable config root. `KIRO_HOME` names the root itself. */
|
|
38
|
+
export function kiroConfigRoot() {
|
|
39
|
+
const configured = process.env.KIRO_HOME?.trim();
|
|
40
|
+
return configured || join(homedir(), '.kiro');
|
|
41
|
+
}
|
|
42
|
+
export function kiroSessionsDir() {
|
|
43
|
+
return join(kiroConfigRoot(), 'sessions', 'cli');
|
|
44
|
+
}
|
|
45
|
+
export function kiroV3SessionsRoot() {
|
|
46
|
+
return join(kiroConfigRoot(), 'sessions');
|
|
47
|
+
}
|
|
48
|
+
/** Measured Kiro CLI 2.18.1 v2 desktop store. */
|
|
49
|
+
export function kiroDataDbPath() {
|
|
50
|
+
if (process.platform === 'darwin') {
|
|
51
|
+
return join(homedir(), 'Library', 'Application Support', 'kiro-cli', 'data.sqlite3');
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
const localAppData = process.env.LOCALAPPDATA?.trim()
|
|
55
|
+
|| join(homedir(), 'AppData', 'Local');
|
|
56
|
+
return join(localAppData, 'kiro-cli', 'data.sqlite3');
|
|
57
|
+
}
|
|
58
|
+
const dataHome = process.env.XDG_DATA_HOME?.trim() || join(homedir(), '.local', 'share');
|
|
59
|
+
return join(dataHome, 'kiro-cli', 'data.sqlite3');
|
|
60
|
+
}
|
|
61
|
+
export function kiroPromptHistoryPath() {
|
|
62
|
+
return join(kiroConfigRoot(), '.cli_bash_history');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the stable subset of Kiro's sibling `<session>.json` metadata. Kiro 2.x
|
|
66
|
+
* stores the model under `session_state.rts_model_state.model_info.model_id`;
|
|
67
|
+
* the flatter alternatives keep this reader useful for the incompatible 3.x
|
|
68
|
+
* session format without pretending the undocumented remainder is stable.
|
|
69
|
+
*/
|
|
70
|
+
export function parseKiroSessionMetadata(raw, fallbackSessionId) {
|
|
71
|
+
let root;
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (!isRecord(parsed))
|
|
75
|
+
return { sessionId: fallbackSessionId };
|
|
76
|
+
root = parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { sessionId: fallbackSessionId };
|
|
80
|
+
}
|
|
81
|
+
const sessionState = objectAt(root, 'session_state');
|
|
82
|
+
const rtsState = sessionState ? objectAt(sessionState, 'rts_model_state') : null;
|
|
83
|
+
const modelInfo = rtsState ? objectAt(rtsState, 'model_info') : null;
|
|
84
|
+
const sessionId = firstString(root, ['session_id', 'sessionId', 'id']) ?? fallbackSessionId;
|
|
85
|
+
const cwd = firstString(root, ['cwd', 'working_directory', 'workspace_root']);
|
|
86
|
+
const title = firstString(root, ['title', 'name', 'summary']);
|
|
87
|
+
const workspacePaths = arrayAt(root, 'workspacePaths') ?? arrayAt(root, 'rootPaths') ?? [];
|
|
88
|
+
const workspacePath = workspacePaths.find((value) => typeof value === 'string' && Boolean(value));
|
|
89
|
+
const modelName = (modelInfo ? firstString(modelInfo, ['model_id', 'modelId', 'name']) : undefined) ??
|
|
90
|
+
firstString(root, ['model', 'model_id', 'modelId']);
|
|
91
|
+
return {
|
|
92
|
+
...(sessionId ? { sessionId: sessionId.slice(0, 256) } : {}),
|
|
93
|
+
...((cwd ?? workspacePath) ? { cwd: (cwd ?? workspacePath).slice(0, 4096) } : {}),
|
|
94
|
+
...(title ? { title: title.trim().slice(0, 160) } : {}),
|
|
95
|
+
...(modelName ? { modelName: modelName.slice(0, 160) } : {}),
|
|
96
|
+
...(timestampMs(firstString(root, ['created_at', 'createdAt', 'started_at'])) !== undefined
|
|
97
|
+
? { createdAt: timestampMs(firstString(root, ['created_at', 'createdAt', 'started_at'])) }
|
|
98
|
+
: {}),
|
|
99
|
+
...(timestampMs(firstString(root, ['updated_at', 'updatedAt', 'last_activity_at', 'lastModifiedAt'])) !== undefined
|
|
100
|
+
? { updatedAt: timestampMs(firstString(root, ['updated_at', 'updatedAt', 'last_activity_at', 'lastModifiedAt'])) }
|
|
101
|
+
: {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Summarize measured Kiro 2.x records, v3 `payload.type` envelopes, and ACP
|
|
106
|
+
* notifications. Unknown records are ignored: a Kiro
|
|
107
|
+
* upgrade may reduce detail, but must never make daemon observation fail.
|
|
108
|
+
*/
|
|
109
|
+
export function parseKiroTranscript(raw) {
|
|
110
|
+
let turnOpen = false;
|
|
111
|
+
let goal;
|
|
112
|
+
let currentTask;
|
|
113
|
+
let response;
|
|
114
|
+
const notePrompt = (text) => {
|
|
115
|
+
const cleaned = cleanKiroGoal(text);
|
|
116
|
+
if (!goal && cleaned)
|
|
117
|
+
goal = cleaned;
|
|
118
|
+
turnOpen = true;
|
|
119
|
+
currentTask = undefined;
|
|
120
|
+
};
|
|
121
|
+
const noteAssistant = (text) => {
|
|
122
|
+
const cleaned = text.replace(/\s+/g, ' ').trim();
|
|
123
|
+
if (cleaned)
|
|
124
|
+
response = cleaned.slice(-1000);
|
|
125
|
+
};
|
|
126
|
+
for (const value of parseJsonl(raw)) {
|
|
127
|
+
const envelope = objectAt(value, 'payload') ?? value;
|
|
128
|
+
const kind = firstString(envelope, ['kind', 'type']);
|
|
129
|
+
const data = objectAt(envelope, 'data') ?? envelope;
|
|
130
|
+
if (kind === 'user') {
|
|
131
|
+
notePrompt(contentText(data));
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (kind === 'turn_start') {
|
|
135
|
+
turnOpen = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (kind === 'assistant') {
|
|
139
|
+
// Kiro writes one `assistant` record per operation, and the LAST one in a
|
|
140
|
+
// v3 turn is routinely `operationType: "Reasoning"` — a chain-of-thought
|
|
141
|
+
// block whose content Kiro has already redacted to a literal "...". Since
|
|
142
|
+
// the newest assistant record wins, taking it unconditionally captured
|
|
143
|
+
// that placeholder as the reply for every v3 session (measured against 6
|
|
144
|
+
// real transcripts: all six came out with response === "...", discarding
|
|
145
|
+
// the `operationType: "Say"` record that held the actual answer).
|
|
146
|
+
//
|
|
147
|
+
// Excluded by operation name, not by matching the "..." text: that string
|
|
148
|
+
// is a display artifact free to change, while the operation is the
|
|
149
|
+
// structural fact. Reasoning content must stay out of the timeline and
|
|
150
|
+
// APME on its own merits anyway.
|
|
151
|
+
if (firstString(envelope, ['operationType']) !== 'Reasoning') {
|
|
152
|
+
noteAssistant(contentText(data));
|
|
153
|
+
}
|
|
154
|
+
turnOpen = true;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (kind === 'Prompt') {
|
|
158
|
+
notePrompt(contentText(data));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (kind === 'AssistantMessage') {
|
|
162
|
+
const blocks = arrayAt(data, 'content') ?? [];
|
|
163
|
+
let hasToolUse = false;
|
|
164
|
+
for (const block of blocks) {
|
|
165
|
+
if (!isRecord(block))
|
|
166
|
+
continue;
|
|
167
|
+
const blockKind = firstString(block, ['kind', 'type']);
|
|
168
|
+
if (blockKind === 'text')
|
|
169
|
+
noteAssistant(valueText(block.data) ?? firstString(block, ['text']) ?? '');
|
|
170
|
+
if (blockKind === 'toolUse' || blockKind === 'tool_use') {
|
|
171
|
+
hasToolUse = true;
|
|
172
|
+
currentTask = kiroToolTask(block);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// A final text-only AssistantMessage closes a 2.x turn. Tool-use
|
|
176
|
+
// messages remain open until a later assistant message completes it.
|
|
177
|
+
turnOpen = hasToolUse;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (kind === 'ToolResults' || kind === 'ToolResult') {
|
|
181
|
+
turnOpen = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (kind === 'TurnEnd' || kind === 'turn_end') {
|
|
185
|
+
turnOpen = false;
|
|
186
|
+
currentTask = undefined;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const method = firstString(value, ['method']);
|
|
190
|
+
const params = objectAt(value, 'params');
|
|
191
|
+
if (method === 'session/prompt' && params) {
|
|
192
|
+
notePrompt(contentText(params));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (method !== 'session/notification' || !params)
|
|
196
|
+
continue;
|
|
197
|
+
const update = objectAt(params, 'update') ?? params;
|
|
198
|
+
const updateType = normalizeUpdateType(firstString(update, ['sessionUpdate', 'session_update', 'type', 'kind']) ?? '');
|
|
199
|
+
if (updateType === 'agentmessagechunk') {
|
|
200
|
+
noteAssistant(contentText(update));
|
|
201
|
+
turnOpen = true;
|
|
202
|
+
}
|
|
203
|
+
else if (updateType === 'toolcall') {
|
|
204
|
+
turnOpen = true;
|
|
205
|
+
currentTask = kiroToolTask(update);
|
|
206
|
+
}
|
|
207
|
+
else if (updateType === 'toolcallupdate') {
|
|
208
|
+
turnOpen = true;
|
|
209
|
+
currentTask = kiroToolTask(update) ?? currentTask;
|
|
210
|
+
}
|
|
211
|
+
else if (updateType === 'turnend') {
|
|
212
|
+
turnOpen = false;
|
|
213
|
+
currentTask = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
state: turnOpen ? 'processing' : 'idle',
|
|
218
|
+
...(goal ? { goal } : {}),
|
|
219
|
+
...(currentTask ? { currentTask } : {}),
|
|
220
|
+
...(response ? { response } : {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Return schema markers only. Safe for diagnostics: values, prompt text,
|
|
225
|
+
* tool input, response text, and message ids never leave this function.
|
|
226
|
+
*/
|
|
227
|
+
/** Non-blank lines in a transcript sample — the evidence that something WAS
|
|
228
|
+
* written, independent of whether this build could interpret it. */
|
|
229
|
+
export function kiroTranscriptLineCount(raw) {
|
|
230
|
+
let count = 0;
|
|
231
|
+
for (const line of raw.split('\n')) {
|
|
232
|
+
if (line.trim())
|
|
233
|
+
count++;
|
|
234
|
+
}
|
|
235
|
+
return count;
|
|
236
|
+
}
|
|
237
|
+
export function kiroTranscriptRecordKinds(raw) {
|
|
238
|
+
const kinds = new Set();
|
|
239
|
+
for (const value of parseJsonl(raw)) {
|
|
240
|
+
const envelope = objectAt(value, 'payload') ?? value;
|
|
241
|
+
const kind = firstString(envelope, ['kind', 'type']);
|
|
242
|
+
if (kind)
|
|
243
|
+
kinds.add(safeKiroRecordKind(kind));
|
|
244
|
+
const method = firstString(value, ['method']);
|
|
245
|
+
if (method)
|
|
246
|
+
kinds.add(safeKiroMethod(method));
|
|
247
|
+
if (method === 'session/notification') {
|
|
248
|
+
const params = objectAt(value, 'params');
|
|
249
|
+
const update = params ? (objectAt(params, 'update') ?? params) : null;
|
|
250
|
+
const updateType = update ? firstString(update, ['sessionUpdate', 'session_update', 'type', 'kind']) : undefined;
|
|
251
|
+
if (updateType)
|
|
252
|
+
kinds.add(`session/notification:${safeKiroUpdateType(updateType)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return [...kinds].sort();
|
|
256
|
+
}
|
|
257
|
+
function safeKiroRecordKind(value) {
|
|
258
|
+
return [
|
|
259
|
+
'Prompt', 'AssistantMessage', 'ToolResults', 'ToolResult', 'TurnEnd',
|
|
260
|
+
'user', 'assistant', 'turn_start', 'turn_end', 'session_start',
|
|
261
|
+
'session_event', 'session_metadata', 'usage_summary', 'ContextualHookInvoked',
|
|
262
|
+
].includes(value)
|
|
263
|
+
? value
|
|
264
|
+
: 'other';
|
|
265
|
+
}
|
|
266
|
+
function safeKiroMethod(value) {
|
|
267
|
+
return ['session/prompt', 'session/notification'].includes(value) ? value : 'other';
|
|
268
|
+
}
|
|
269
|
+
function safeKiroUpdateType(value) {
|
|
270
|
+
const normalized = normalizeUpdateType(value);
|
|
271
|
+
const known = {
|
|
272
|
+
agentmessagechunk: 'AgentMessageChunk',
|
|
273
|
+
toolcall: 'ToolCall',
|
|
274
|
+
toolcallupdate: 'ToolCallUpdate',
|
|
275
|
+
turnend: 'TurnEnd',
|
|
276
|
+
};
|
|
277
|
+
return known[normalized] ?? 'other';
|
|
278
|
+
}
|
|
279
|
+
function cleanKiroGoal(raw) {
|
|
280
|
+
const value = stripUnsafeText(raw)
|
|
281
|
+
.replace(/<[^>]+>/g, ' ')
|
|
282
|
+
.replace(/\s+/g, ' ')
|
|
283
|
+
.trim();
|
|
284
|
+
return /^\/[a-z][\w-]*\s*$/i.test(value) ? '' : value.slice(0, 120);
|
|
285
|
+
}
|
|
286
|
+
/** Read the newest bounded set of Kiro CLI session pairs. */
|
|
287
|
+
export async function readKiroSessionSnapshots(sessionsDir = kiroSessionsDir(), maxSessions = MAX_SESSION_FILES, cache) {
|
|
288
|
+
let names;
|
|
289
|
+
try {
|
|
290
|
+
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
291
|
+
names = entries
|
|
292
|
+
.filter((entry) => entry.isFile() && !entry.isSymbolicLink() && entry.name.endsWith('.jsonl'))
|
|
293
|
+
.map((entry) => entry.name)
|
|
294
|
+
.slice(0, 2000);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
const files = [];
|
|
300
|
+
for (const name of names) {
|
|
301
|
+
try {
|
|
302
|
+
const info = await stat(join(sessionsDir, name));
|
|
303
|
+
if (info.isFile())
|
|
304
|
+
files.push({ name, mtimeMs: info.mtimeMs, size: info.size });
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
/* file disappeared during the scan */
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
311
|
+
const snapshots = [];
|
|
312
|
+
const retainedPaths = new Set();
|
|
313
|
+
for (const file of files.slice(0, Math.max(0, maxSessions))) {
|
|
314
|
+
const sessionId = basename(file.name, '.jsonl');
|
|
315
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/.test(sessionId))
|
|
316
|
+
continue;
|
|
317
|
+
const transcriptPath = join(sessionsDir, file.name);
|
|
318
|
+
const metadataPath = join(sessionsDir, `${sessionId}.json`);
|
|
319
|
+
const metadataInfo = await stat(metadataPath).catch(() => undefined);
|
|
320
|
+
const signature = {
|
|
321
|
+
transcriptMtimeMs: file.mtimeMs,
|
|
322
|
+
transcriptSize: file.size,
|
|
323
|
+
metadataMtimeMs: metadataInfo?.mtimeMs ?? 0,
|
|
324
|
+
metadataSize: metadataInfo?.size ?? 0,
|
|
325
|
+
};
|
|
326
|
+
retainedPaths.add(transcriptPath);
|
|
327
|
+
const cached = cache?.get(transcriptPath, signature);
|
|
328
|
+
if (cached) {
|
|
329
|
+
snapshots.push(cached);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const [metadataRaw, transcriptRaw] = await Promise.all([
|
|
333
|
+
readFile(metadataPath, 'utf8').catch(() => ''),
|
|
334
|
+
readHeadAndTail(transcriptPath, 64 * 1024, MAX_TRANSCRIPT_SAMPLE_BYTES).catch(() => ''),
|
|
335
|
+
]);
|
|
336
|
+
const metadata = parseKiroSessionMetadata(metadataRaw, sessionId);
|
|
337
|
+
const summary = parseKiroTranscript(transcriptRaw);
|
|
338
|
+
const snapshot = {
|
|
339
|
+
...metadata,
|
|
340
|
+
...summary,
|
|
341
|
+
sessionId,
|
|
342
|
+
transcriptPath,
|
|
343
|
+
lastActivityAt: metadata.updatedAt ?? file.mtimeMs,
|
|
344
|
+
goal: summary.goal ?? metadata.title,
|
|
345
|
+
recordKinds: kiroTranscriptRecordKinds(transcriptRaw),
|
|
346
|
+
transcriptLineCount: kiroTranscriptLineCount(transcriptRaw),
|
|
347
|
+
};
|
|
348
|
+
snapshots.push(snapshot);
|
|
349
|
+
cache?.set(transcriptPath, { ...signature, snapshot });
|
|
350
|
+
}
|
|
351
|
+
cache?.retain(retainedPaths);
|
|
352
|
+
return snapshots;
|
|
353
|
+
}
|
|
354
|
+
/** Read Kiro CLI 3.0's measured `<workspace>/<session>/messages.jsonl` store. */
|
|
355
|
+
export async function readKiroV3SessionSnapshots(sessionsRoot = kiroV3SessionsRoot(), maxSessions = MAX_SESSION_FILES, cache) {
|
|
356
|
+
const candidates = [];
|
|
357
|
+
try {
|
|
358
|
+
const workspaces = await readdir(sessionsRoot, { withFileTypes: true });
|
|
359
|
+
for (const workspace of workspaces.slice(0, 2000)) {
|
|
360
|
+
if (!workspace.isDirectory() || workspace.isSymbolicLink() || workspace.name === 'cli')
|
|
361
|
+
continue;
|
|
362
|
+
const workspacePath = join(sessionsRoot, workspace.name);
|
|
363
|
+
const sessions = await readdir(workspacePath, { withFileTypes: true }).catch(() => []);
|
|
364
|
+
for (const session of sessions.slice(0, 2000)) {
|
|
365
|
+
if (!session.isDirectory() || session.isSymbolicLink() || !/^[A-Za-z0-9_-]{1,256}$/.test(session.name))
|
|
366
|
+
continue;
|
|
367
|
+
const transcriptPath = join(workspacePath, session.name, 'messages.jsonl');
|
|
368
|
+
const metadataPath = join(workspacePath, session.name, 'session.json');
|
|
369
|
+
const [transcriptInfo, metadataInfo] = await Promise.all([
|
|
370
|
+
stat(transcriptPath).catch(() => undefined),
|
|
371
|
+
stat(metadataPath).catch(() => undefined),
|
|
372
|
+
]);
|
|
373
|
+
if (!transcriptInfo?.isFile() || !metadataInfo?.isFile())
|
|
374
|
+
continue;
|
|
375
|
+
candidates.push({
|
|
376
|
+
sessionId: session.name,
|
|
377
|
+
transcriptPath,
|
|
378
|
+
metadataPath,
|
|
379
|
+
transcriptMtimeMs: transcriptInfo.mtimeMs,
|
|
380
|
+
transcriptSize: transcriptInfo.size,
|
|
381
|
+
metadataMtimeMs: metadataInfo.mtimeMs,
|
|
382
|
+
metadataSize: metadataInfo.size,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
candidates.sort((a, b) => Math.max(b.transcriptMtimeMs, b.metadataMtimeMs)
|
|
391
|
+
- Math.max(a.transcriptMtimeMs, a.metadataMtimeMs));
|
|
392
|
+
const snapshots = [];
|
|
393
|
+
const retainedPaths = new Set();
|
|
394
|
+
for (const candidate of candidates.slice(0, Math.max(0, maxSessions))) {
|
|
395
|
+
const signature = {
|
|
396
|
+
transcriptMtimeMs: candidate.transcriptMtimeMs,
|
|
397
|
+
transcriptSize: candidate.transcriptSize,
|
|
398
|
+
metadataMtimeMs: candidate.metadataMtimeMs,
|
|
399
|
+
metadataSize: candidate.metadataSize,
|
|
400
|
+
};
|
|
401
|
+
retainedPaths.add(candidate.transcriptPath);
|
|
402
|
+
const cached = cache?.get(candidate.transcriptPath, signature);
|
|
403
|
+
if (cached) {
|
|
404
|
+
snapshots.push(cached);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const [metadataRaw, transcriptRaw] = await Promise.all([
|
|
408
|
+
readFile(candidate.metadataPath, 'utf8').catch(() => ''),
|
|
409
|
+
readHeadAndTail(candidate.transcriptPath, 64 * 1024, MAX_TRANSCRIPT_SAMPLE_BYTES).catch(() => ''),
|
|
410
|
+
]);
|
|
411
|
+
const metadata = parseKiroSessionMetadata(metadataRaw, candidate.sessionId);
|
|
412
|
+
const summary = parseKiroTranscript(transcriptRaw);
|
|
413
|
+
const snapshot = {
|
|
414
|
+
...metadata,
|
|
415
|
+
...summary,
|
|
416
|
+
sessionId: metadata.sessionId ?? candidate.sessionId,
|
|
417
|
+
transcriptPath: candidate.transcriptPath,
|
|
418
|
+
lastActivityAt: metadata.updatedAt
|
|
419
|
+
?? Math.max(candidate.transcriptMtimeMs, candidate.metadataMtimeMs),
|
|
420
|
+
goal: summary.goal ?? metadata.title,
|
|
421
|
+
recordKinds: kiroTranscriptRecordKinds(transcriptRaw),
|
|
422
|
+
transcriptLineCount: kiroTranscriptLineCount(transcriptRaw),
|
|
423
|
+
};
|
|
424
|
+
snapshots.push(snapshot);
|
|
425
|
+
cache?.set(candidate.transcriptPath, { ...signature, snapshot });
|
|
426
|
+
}
|
|
427
|
+
cache?.retain(retainedPaths);
|
|
428
|
+
return snapshots;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Read every currently-known native Kiro store without changing how Kiro is
|
|
432
|
+
* launched. Kiro CLI 2.18.1 on macOS persists `conversations_v2` in its app
|
|
433
|
+
* SQLite database; v3 writes nested `session.json` + `messages.jsonl` pairs.
|
|
434
|
+
* Legacy flat JSONL remains a fallback. Sources are merged by session id.
|
|
435
|
+
*/
|
|
436
|
+
export async function readKiroNativeSessionSnapshots(maxSessions = MAX_SESSION_FILES, cache) {
|
|
437
|
+
const jsonlDir = kiroSessionsDir();
|
|
438
|
+
const v3Root = kiroV3SessionsRoot();
|
|
439
|
+
const dbPath = kiroDataDbPath();
|
|
440
|
+
const [jsonlInfo, v3Info, dbInfo, promptHistoryInfo] = await Promise.all([
|
|
441
|
+
stat(jsonlDir).catch(() => undefined),
|
|
442
|
+
stat(v3Root).catch(() => undefined),
|
|
443
|
+
stat(dbPath).catch(() => undefined),
|
|
444
|
+
stat(kiroPromptHistoryPath()).catch(() => undefined),
|
|
445
|
+
]);
|
|
446
|
+
const jsonlDirectoryPresent = Boolean(jsonlInfo?.isDirectory());
|
|
447
|
+
const v3DirectoryPresent = Boolean(v3Info?.isDirectory());
|
|
448
|
+
const sqliteDatabasePresent = Boolean(dbInfo?.isFile());
|
|
449
|
+
const jsonl = jsonlDirectoryPresent
|
|
450
|
+
? await readKiroSessionSnapshots(jsonlDir, maxSessions, v3DirectoryPresent ? undefined : cache)
|
|
451
|
+
: [];
|
|
452
|
+
const v3 = v3DirectoryPresent
|
|
453
|
+
? await readKiroV3SessionSnapshots(v3Root, maxSessions, cache)
|
|
454
|
+
: [];
|
|
455
|
+
const sqlite = sqliteDatabasePresent
|
|
456
|
+
? readKiroSqliteSessionSnapshots(dbPath, maxSessions)
|
|
457
|
+
: { snapshots: [], readable: false };
|
|
458
|
+
const byId = new Map();
|
|
459
|
+
const promptHistoryMtimeMs = promptHistoryInfo?.isFile()
|
|
460
|
+
? promptHistoryInfo.mtimeMs
|
|
461
|
+
: undefined;
|
|
462
|
+
const sqliteSnapshots = applyKiroPendingTurnMarker(sqlite.snapshots, promptHistoryMtimeMs);
|
|
463
|
+
for (const snapshot of [...jsonl, ...sqliteSnapshots, ...v3]) {
|
|
464
|
+
const previous = byId.get(snapshot.sessionId);
|
|
465
|
+
if (!previous || snapshot.lastActivityAt >= previous.lastActivityAt) {
|
|
466
|
+
byId.set(snapshot.sessionId, snapshot);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
let snapshots = [...byId.values()]
|
|
470
|
+
.sort((a, b) => b.lastActivityAt - a.lastActivityAt)
|
|
471
|
+
.slice(0, Math.max(0, maxSessions));
|
|
472
|
+
return {
|
|
473
|
+
snapshots,
|
|
474
|
+
jsonlDirectoryPresent,
|
|
475
|
+
v3DirectoryPresent,
|
|
476
|
+
sqliteDatabasePresent,
|
|
477
|
+
sqliteReadable: sqlite.readable,
|
|
478
|
+
...(promptHistoryMtimeMs !== undefined ? { promptHistoryMtimeMs } : {}),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Kiro appends the submitted prompt to `.cli_bash_history` immediately, but
|
|
483
|
+
* only commits `conversations_v2` after the response completes. A newer prompt
|
|
484
|
+
* history mtime therefore brackets the current turn without reading its text.
|
|
485
|
+
* The marker is global, so only the newest persisted conversation is promoted;
|
|
486
|
+
* simultaneous Kiro chats remain an explicitly ambiguous best effort.
|
|
487
|
+
*/
|
|
488
|
+
export function applyKiroPendingTurnMarker(snapshots, promptHistoryMtimeMs, now = Date.now()) {
|
|
489
|
+
if (snapshots.length === 0
|
|
490
|
+
|| promptHistoryMtimeMs === undefined
|
|
491
|
+
|| promptHistoryMtimeMs <= snapshots[0].lastActivityAt
|
|
492
|
+
|| now - promptHistoryMtimeMs > KIRO_PENDING_TURN_MAX_AGE_MS) {
|
|
493
|
+
return snapshots;
|
|
494
|
+
}
|
|
495
|
+
return snapshots.map((snapshot, index) => index === 0
|
|
496
|
+
? { ...snapshot, state: 'processing', lastActivityAt: promptHistoryMtimeMs }
|
|
497
|
+
: snapshot);
|
|
498
|
+
}
|
|
499
|
+
function readKiroSqliteSessionSnapshots(dbPath, maxSessions) {
|
|
500
|
+
let db;
|
|
501
|
+
try {
|
|
502
|
+
const Ctor = require('better-sqlite3');
|
|
503
|
+
db = new Ctor(dbPath, { readonly: true, fileMustExist: true });
|
|
504
|
+
db.pragma('query_only = ON');
|
|
505
|
+
const rows = db.prepare(`
|
|
506
|
+
SELECT
|
|
507
|
+
key AS cwd,
|
|
508
|
+
conversation_id AS sessionId,
|
|
509
|
+
created_at AS createdAt,
|
|
510
|
+
updated_at AS updatedAt,
|
|
511
|
+
json_extract(value, '$.model_info.model_id') AS modelName,
|
|
512
|
+
json_extract(value, '$.history[0].user.content.Prompt.prompt') AS goal,
|
|
513
|
+
json_extract(value, '$.history[#-1].assistant.Response.content') AS response,
|
|
514
|
+
json_array_length(json_extract(value, '$.history')) AS historyCount,
|
|
515
|
+
CASE WHEN json_type(value, '$.history[#-1].assistant') = 'object' THEN 1 ELSE 0 END AS hasAssistant
|
|
516
|
+
FROM conversations_v2
|
|
517
|
+
WHERE json_valid(value)
|
|
518
|
+
ORDER BY updated_at DESC
|
|
519
|
+
LIMIT ?
|
|
520
|
+
`).all(Math.max(0, maxSessions));
|
|
521
|
+
return { snapshots: parseKiroSqliteRows(rows, dbPath), readable: true };
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return { snapshots: [], readable: false };
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
try {
|
|
528
|
+
db?.close();
|
|
529
|
+
}
|
|
530
|
+
catch { /* best effort read-only handle */ }
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/** Convert only the allowlisted conversation columns selected above. */
|
|
534
|
+
export function parseKiroSqliteRows(rows, dbPath) {
|
|
535
|
+
const snapshots = [];
|
|
536
|
+
for (const row of rows) {
|
|
537
|
+
if (typeof row.sessionId !== 'string'
|
|
538
|
+
|| !/^[A-Za-z0-9_-]{1,256}$/.test(row.sessionId)
|
|
539
|
+
|| typeof row.cwd !== 'string'
|
|
540
|
+
|| typeof row.createdAt !== 'number'
|
|
541
|
+
|| typeof row.updatedAt !== 'number')
|
|
542
|
+
continue;
|
|
543
|
+
const goal = typeof row.goal === 'string' ? cleanKiroGoal(row.goal) : '';
|
|
544
|
+
const response = typeof row.response === 'string'
|
|
545
|
+
? stripUnsafeText(row.response).replace(/\s+/g, ' ').trim().slice(-1000)
|
|
546
|
+
: '';
|
|
547
|
+
snapshots.push({
|
|
548
|
+
sessionId: row.sessionId,
|
|
549
|
+
transcriptPath: dbPath,
|
|
550
|
+
cwd: row.cwd.slice(0, 4096),
|
|
551
|
+
createdAt: row.createdAt,
|
|
552
|
+
updatedAt: row.updatedAt,
|
|
553
|
+
lastActivityAt: row.updatedAt,
|
|
554
|
+
...(typeof row.modelName === 'string' ? { modelName: row.modelName.slice(0, 160) } : {}),
|
|
555
|
+
state: 'idle',
|
|
556
|
+
...(goal ? { goal } : {}),
|
|
557
|
+
...(response ? { response } : {}),
|
|
558
|
+
// Row-backed, not line-backed: there is no transcript text to count, and
|
|
559
|
+
// 'ConversationV2' is always present, so the empty-kinds branch that uses
|
|
560
|
+
// this is unreachable here.
|
|
561
|
+
transcriptLineCount: 0,
|
|
562
|
+
recordKinds: [
|
|
563
|
+
'ConversationV2',
|
|
564
|
+
...(row.historyCount > 0 ? ['Prompt'] : []),
|
|
565
|
+
...(row.hasAssistant ? ['AssistantMessage'] : []),
|
|
566
|
+
],
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return snapshots;
|
|
570
|
+
}
|
|
571
|
+
async function readHeadAndTail(path, headBytes, tailBytes) {
|
|
572
|
+
const file = await open(path, 'r');
|
|
573
|
+
try {
|
|
574
|
+
const { size } = await file.stat();
|
|
575
|
+
if (size <= headBytes + tailBytes) {
|
|
576
|
+
const buffer = Buffer.alloc(size);
|
|
577
|
+
await file.read(buffer, 0, size, 0);
|
|
578
|
+
return buffer.toString('utf8');
|
|
579
|
+
}
|
|
580
|
+
const head = Buffer.alloc(headBytes);
|
|
581
|
+
const tail = Buffer.alloc(tailBytes);
|
|
582
|
+
await file.read(head, 0, headBytes, 0);
|
|
583
|
+
await file.read(tail, 0, tailBytes, size - tailBytes);
|
|
584
|
+
const tailText = tail.toString('utf8');
|
|
585
|
+
const firstNewline = tailText.indexOf('\n');
|
|
586
|
+
return `${head.toString('utf8')}\n${firstNewline >= 0 ? tailText.slice(firstNewline + 1) : ''}`;
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
await file.close();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function kiroToolTask(record) {
|
|
593
|
+
const raw = isRecord(record.data) ? record.data : record;
|
|
594
|
+
const name = firstString(raw, ['name', 'tool_name', 'toolName', 'title']) ?? 'tool';
|
|
595
|
+
const input = objectAt(raw, 'input') ?? objectAt(raw, 'tool_input') ?? objectAt(raw, 'rawInput');
|
|
596
|
+
if (!input)
|
|
597
|
+
return name;
|
|
598
|
+
// Kiro's `read` puts its subject one level down, in `input.operations[]`
|
|
599
|
+
// (measured: `{name:"read", input:{operations:[{mode:"Directory",
|
|
600
|
+
// path:"/…", depth:1}]}}`), while `execute`-style tools carry `command`
|
|
601
|
+
// directly on `input`. Scanning only the top level yielded a bare "read"
|
|
602
|
+
// where Kiro's own UI shows `Read /Users/…/AgentDeck` — a task label with
|
|
603
|
+
// the subject stripped out, which is the part that makes it a label.
|
|
604
|
+
const operations = Array.isArray(input.operations) ? input.operations : [];
|
|
605
|
+
const scopes = [input, ...operations.filter(isRecord)];
|
|
606
|
+
for (const scope of scopes) {
|
|
607
|
+
for (const key of ['path', 'file_path', 'command', 'cmd', 'query', 'url']) {
|
|
608
|
+
const value = valueText(scope[key]);
|
|
609
|
+
if (value)
|
|
610
|
+
return `${name} ${redactSecrets(value).replace(/\s+/g, ' ').slice(0, 80)}`;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return name;
|
|
614
|
+
}
|
|
615
|
+
function contentText(record) {
|
|
616
|
+
const direct = firstString(record, ['text', 'message', 'prompt']);
|
|
617
|
+
if (direct)
|
|
618
|
+
return direct;
|
|
619
|
+
const content = record.content;
|
|
620
|
+
if (typeof content === 'string')
|
|
621
|
+
return content;
|
|
622
|
+
if (!Array.isArray(content))
|
|
623
|
+
return '';
|
|
624
|
+
return content
|
|
625
|
+
.map((block) => {
|
|
626
|
+
if (typeof block === 'string')
|
|
627
|
+
return block;
|
|
628
|
+
if (!isRecord(block))
|
|
629
|
+
return '';
|
|
630
|
+
const kind = firstString(block, ['kind', 'type']);
|
|
631
|
+
return kind === 'text' ? (valueText(block.data) ?? firstString(block, ['text']) ?? '') : '';
|
|
632
|
+
})
|
|
633
|
+
.filter(Boolean)
|
|
634
|
+
.join(' ');
|
|
635
|
+
}
|
|
636
|
+
function parseJsonl(raw) {
|
|
637
|
+
const values = [];
|
|
638
|
+
for (const line of raw.split('\n')) {
|
|
639
|
+
const trimmed = line.trim();
|
|
640
|
+
if (!trimmed || trimmed.length > 10 * 1024 * 1024)
|
|
641
|
+
continue;
|
|
642
|
+
try {
|
|
643
|
+
const parsed = JSON.parse(trimmed);
|
|
644
|
+
if (isRecord(parsed))
|
|
645
|
+
values.push(parsed);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
/* partial tail line */
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return values;
|
|
652
|
+
}
|
|
653
|
+
function normalizeUpdateType(value) {
|
|
654
|
+
return value.replace(/[^a-z]/gi, '').toLowerCase();
|
|
655
|
+
}
|
|
656
|
+
function timestampMs(value) {
|
|
657
|
+
if (!value)
|
|
658
|
+
return undefined;
|
|
659
|
+
const parsed = Date.parse(value);
|
|
660
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
661
|
+
}
|
|
662
|
+
function firstString(record, keys) {
|
|
663
|
+
for (const key of keys) {
|
|
664
|
+
const value = record[key];
|
|
665
|
+
if (typeof value === 'string' && value)
|
|
666
|
+
return value;
|
|
667
|
+
}
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
function valueText(value) {
|
|
671
|
+
if (typeof value === 'string')
|
|
672
|
+
return value;
|
|
673
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
674
|
+
return String(value);
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
function objectAt(record, key) {
|
|
678
|
+
const value = record[key];
|
|
679
|
+
return isRecord(value) ? value : null;
|
|
680
|
+
}
|
|
681
|
+
function arrayAt(record, key) {
|
|
682
|
+
const value = record[key];
|
|
683
|
+
return Array.isArray(value) ? value : null;
|
|
684
|
+
}
|
|
685
|
+
function isRecord(value) {
|
|
686
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
687
|
+
}
|
|
688
|
+
//# sourceMappingURL=kiro-session.js.map
|