@songsid/agend 2.2.0-alpha.2 → 2.2.0-alpha.4
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/README.md +4 -4
- package/README.zh-TW.md +4 -4
- package/dist/agent-endpoint.d.ts +4 -1
- package/dist/agent-endpoint.js +14 -2
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/atomic-write.d.ts +21 -0
- package/dist/atomic-write.js +65 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex.d.ts +7 -0
- package/dist/backend/codex.js +7 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/muse.d.ts +7 -0
- package/dist/backend/muse.js +7 -0
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/binary-probe.d.ts +80 -0
- package/dist/binary-probe.js +182 -0
- package/dist/binary-probe.js.map +1 -0
- package/dist/cache-service.d.ts +17 -1
- package/dist/cache-service.js +39 -4
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +5 -1
- package/dist/channel/adapters/discord.js +56 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +38 -1
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +8 -1
- package/dist/channel/tool-router.js +4 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/cli.js +133 -38
- package/dist/cli.js.map +1 -1
- package/dist/connection-secrets.d.ts +5 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/cpu-profile.js +50 -0
- package/dist/cpu-profile.js.map +1 -1
- package/dist/daemon.d.ts +12 -0
- package/dist/daemon.js +166 -26
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +1 -0
- package/dist/delivery-outbox.js +12 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/discord-general.d.ts +15 -0
- package/dist/discord-general.js +24 -0
- package/dist/discord-general.js.map +1 -0
- package/dist/discord-permissions.d.ts +33 -0
- package/dist/discord-permissions.js +42 -0
- package/dist/discord-permissions.js.map +1 -0
- package/dist/env-file.d.ts +11 -0
- package/dist/env-file.js +27 -0
- package/dist/env-file.js.map +1 -0
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-level-config.d.ts +8 -1
- package/dist/fleet-level-config.js +31 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +76 -7
- package/dist/fleet-manager.js +610 -142
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-stop-budget.d.ts +22 -0
- package/dist/fleet-stop-budget.js +95 -0
- package/dist/fleet-stop-budget.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +8 -4
- package/dist/kiro-transcript-lane.js +66 -44
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +88 -0
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.d.ts +5 -0
- package/dist/next-steps.js +26 -0
- package/dist/next-steps.js.map +1 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +6 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.d.ts +7 -0
- package/dist/outbox-open-error.js +15 -0
- package/dist/outbox-open-error.js.map +1 -0
- package/dist/pane-context-cache.d.ts +25 -0
- package/dist/pane-context-cache.js +73 -0
- package/dist/pane-context-cache.js.map +1 -0
- package/dist/paths.d.ts +0 -7
- package/dist/paths.js +8 -2
- package/dist/paths.js.map +1 -1
- package/dist/provider-probe.d.ts +15 -0
- package/dist/provider-probe.js +20 -0
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +75 -0
- package/dist/public-link-progress.js +204 -0
- package/dist/public-link-progress.js.map +1 -0
- package/dist/public-web-gateway.js +4 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -2
- package/dist/public-web-link.js +89 -15
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +93 -20
- package/dist/quickstart-api.js +198 -63
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +15 -0
- package/dist/quickstart.js +100 -29
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +98 -0
- package/dist/reply-buttons-controller.js +174 -0
- package/dist/reply-buttons-controller.js.map +1 -0
- package/dist/reply-buttons.d.ts +113 -0
- package/dist/reply-buttons.js +208 -0
- package/dist/reply-buttons.js.map +1 -0
- package/dist/reply-dedup.js +3 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-guard.d.ts +1 -0
- package/dist/restart-guard.js +8 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/service-activation.d.ts +15 -2
- package/dist/service-activation.js +81 -4
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.js +19 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +53 -12
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-authority.d.ts +11 -0
- package/dist/settings-authority.js +128 -0
- package/dist/settings-authority.js.map +1 -0
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +4 -3
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -0
- package/dist/settings-confirmation.js +7 -1
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +19 -7
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-redaction.d.ts +18 -0
- package/dist/settings-redaction.js +58 -0
- package/dist/settings-redaction.js.map +1 -0
- package/dist/setup-form.d.ts +9 -1
- package/dist/setup-form.js +25 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.js +13 -3
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +26 -19
- package/dist/setup-wizard.js.map +1 -1
- package/dist/systemd-runtime.d.ts +15 -0
- package/dist/systemd-runtime.js +41 -0
- package/dist/systemd-runtime.js.map +1 -0
- package/dist/tmux-control.d.ts +22 -0
- package/dist/tmux-control.js +82 -17
- package/dist/tmux-control.js.map +1 -1
- package/dist/token-env-name.d.ts +37 -0
- package/dist/token-env-name.js +93 -0
- package/dist/token-env-name.js.map +1 -0
- package/dist/tool-permissions.js +5 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +7 -16
- package/dist/topic-commands.js +48 -84
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +3 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +68 -0
- package/dist/transcript-sources.js +336 -29
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +39 -0
- package/dist/transcript-turns.js +58 -0
- package/dist/transcript-turns.js.map +1 -0
- package/dist/tunnel/cloudflared-install.d.ts +42 -0
- package/dist/tunnel/cloudflared-install.js +194 -9
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/deb-extract.d.ts +9 -0
- package/dist/tunnel/deb-extract.js +94 -0
- package/dist/tunnel/deb-extract.js.map +1 -0
- package/dist/turn-reply-guard.d.ts +3 -0
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/ui/app.html +3 -0
- package/dist/ui/chat-render.js +32 -6
- package/dist/ui/chat-store.js +21 -1
- package/dist/ui/chat-strings.js +45 -0
- package/dist/ui/chat-thread.js +109 -52
- package/dist/ui/first-run.js +64 -0
- package/dist/ui/html-attachment.js +26 -0
- package/dist/ui/icons/apple-touch-icon.png +0 -0
- package/dist/ui/icons/favicon.ico +0 -0
- package/dist/ui/icons/favicon.svg +8 -0
- package/dist/ui/panel-chat.js +16 -7
- package/dist/ui/panel-fleet.js +5 -5
- package/dist/ui/panel-settings.js +18 -8
- package/dist/ui/preview-panel.js +47 -7
- package/dist/ui/preview.js +36 -13
- package/dist/ui/settings-apply.js +3 -1
- package/dist/ui/settings-dialogs.js +95 -25
- package/dist/ui/settings-discord.js +130 -0
- package/dist/ui/settings-model.js +21 -0
- package/dist/ui/settings-strings.js +46 -6
- package/dist/ui/settings-token.js +54 -0
- package/dist/ui/settings-wizard.js +63 -29
- package/dist/ui/shared/app-shell.js +19 -33
- package/dist/ui/shared/app-store.js +5 -3
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +34 -2
- package/dist/ui/shared/app.js +2 -1
- package/dist/ui/shared/instance-nav.js +310 -0
- package/dist/ui/shared/panel-view.js +12 -190
- package/dist/ui/shared/view-roster-store.js +5 -0
- package/dist/ui/shared/view-strings.js +12 -0
- package/dist/ui/signin.html +3 -0
- package/dist/update-dispatch.d.ts +1 -1
- package/dist/update-dispatch.js +1 -1
- package/dist/update-install.d.ts +15 -3
- package/dist/update-install.js +41 -13
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +22 -0
- package/dist/update-launch.js +106 -0
- package/dist/update-launch.js.map +1 -0
- package/dist/update-marker.js +3 -8
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/format-rich.js +7 -1
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +4 -0
- package/dist/usage/providers.js +2 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +48 -2
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +40 -0
- package/dist/view-api.js +28 -10
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +8 -0
- package/dist/web-api.js +53 -17
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +8 -0
- package/dist/web-auth.js +34 -8
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +21 -0
- package/dist/web-chat-history.js +44 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-host-guard.d.ts +18 -2
- package/dist/web-host-guard.js +30 -5
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-icons.d.ts +21 -0
- package/dist/web-icons.js +38 -0
- package/dist/web-icons.js.map +1 -0
- package/dist/web-preview.d.ts +9 -0
- package/dist/web-preview.js +15 -9
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +7 -1
- package/dist/web-session.js +23 -2
- package/dist/web-session.js.map +1 -1
- package/launcher/postinstall.cjs +8 -4
- package/package.json +1 -1
- package/templates/systemd.service.ejs +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type RolloutIndex } from "./rollout-index.js";
|
|
2
2
|
import { type KiroDbLane } from "./kiro-transcript-lane.js";
|
|
3
3
|
export { extractKiroAssistantStrings } from "./kiro-db-reader.js";
|
|
4
|
+
import type { TranscriptTurnEvent } from "./transcript-turns.js";
|
|
4
5
|
export interface ToolUseEvent {
|
|
5
6
|
name: string;
|
|
6
7
|
input: unknown;
|
|
@@ -11,6 +12,8 @@ export interface TranscriptEvents {
|
|
|
11
12
|
name: string;
|
|
12
13
|
}>;
|
|
13
14
|
assistantTexts: string[];
|
|
15
|
+
/** #1510: turn boundaries, from a source that records them (codex). */
|
|
16
|
+
turns?: TranscriptTurnEvent[];
|
|
14
17
|
}
|
|
15
18
|
export interface TranscriptCheckpoint {
|
|
16
19
|
path: string;
|
|
@@ -51,6 +54,8 @@ export declare class CodexRolloutSource implements TranscriptSource {
|
|
|
51
54
|
*/
|
|
52
55
|
private accepted;
|
|
53
56
|
private readonly index;
|
|
57
|
+
/** #1510: the turn the records being read belong to (its `task_started`), carried across polls of one file. */
|
|
58
|
+
private openTurnId;
|
|
54
59
|
constructor(workingDirectory: string, sessionsDir?: string, _now?: number, index?: RolloutIndex);
|
|
55
60
|
reset(): void;
|
|
56
61
|
checkpoint(): Promise<TranscriptCheckpoint | null>;
|
|
@@ -70,6 +75,49 @@ export declare class CodexRolloutSource implements TranscriptSource {
|
|
|
70
75
|
private fileBelongsToUs;
|
|
71
76
|
poll(): Promise<TranscriptEvents>;
|
|
72
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* #1510: muse's turn boundaries, from its session logs (`<root>/<YYYY>/<MM>/<DD>/<id>/session.jsonl`, recorded on
|
|
80
|
+
* 1.4.4 in tests/fixtures/reply-guard-1510). Only turns are read; muse's tool activity is not.
|
|
81
|
+
*
|
|
82
|
+
* - a run is a turn: `runtime.session` run `started` → `terminal {reason}` (null: done; "cancelled …": the user
|
|
83
|
+
* stopped it; anything else: it failed);
|
|
84
|
+
* - a message is `runtime.user_intent.accepted` (its text, surface "main") and lands in a run with
|
|
85
|
+
* `user_intent.materialized` — a new run, or the active one when it was typed mid-run;
|
|
86
|
+
* - the end-of-turn reminder observers write their own session directories and no main-surface intents.
|
|
87
|
+
*
|
|
88
|
+
* Every log whose head names this working directory is followed: ownership is decided later, by the delivered text
|
|
89
|
+
* (TranscriptTurnLedger), so two muse instances in one directory cannot vouch for each other. The directory tree is
|
|
90
|
+
* re-listed at most every `listTtlMs`; between listings only the logs already known are read, and only when they grew.
|
|
91
|
+
* Logs present when the source is created are read from their end (existing history is not this launch's).
|
|
92
|
+
*/
|
|
93
|
+
export declare class MuseSessionSource implements TranscriptSource {
|
|
94
|
+
private workingDirectory;
|
|
95
|
+
private root;
|
|
96
|
+
private listTtlMs;
|
|
97
|
+
private now;
|
|
98
|
+
/**
|
|
99
|
+
* session.jsonl path → where to read next (cwd-matched logs only): a byte offset, or `{ anchor }` for a log that
|
|
100
|
+
* existed before this source (read from its last line boundary at `anchor` bytes, or at its size when first read
|
|
101
|
+
* if its size was not known then — never from its start).
|
|
102
|
+
*/
|
|
103
|
+
private files;
|
|
104
|
+
/** Every log present at the baseline, with its size then (null: it was there, its size could not be read). */
|
|
105
|
+
private preexisting;
|
|
106
|
+
/** Whether the baseline listing saw the whole tree; if not, a log found later may be old and is read from its end. */
|
|
107
|
+
private baselineComplete;
|
|
108
|
+
private rejected;
|
|
109
|
+
private sizes;
|
|
110
|
+
private listedAt;
|
|
111
|
+
private baselined;
|
|
112
|
+
/** intent_id → its text and time, until it materializes (bounded). */
|
|
113
|
+
private intents;
|
|
114
|
+
constructor(workingDirectory: string, root?: string, listTtlMs?: number, now?: () => number);
|
|
115
|
+
reset(): void;
|
|
116
|
+
initialize(): Promise<void>;
|
|
117
|
+
private list;
|
|
118
|
+
poll(): Promise<TranscriptEvents>;
|
|
119
|
+
private parse;
|
|
120
|
+
}
|
|
73
121
|
/**
|
|
74
122
|
* The conversation database for one kiro store.
|
|
75
123
|
*
|
|
@@ -112,6 +160,10 @@ includeHistory?: boolean): KiroConversationRead;
|
|
|
112
160
|
* Kiro 2.19 moved primary conversations to conversations_v2 in data.sqlite3;
|
|
113
161
|
* legacy releases use <uuid>.jsonl plus sibling <uuid>.json metadata.
|
|
114
162
|
*/
|
|
163
|
+
/** The Kiro legacy fallback's scan bounds (#1490): see KiroSessionSource.resolveActiveSession. */
|
|
164
|
+
export declare const KIRO_FULL_SCAN_MS = 30000;
|
|
165
|
+
export declare const KIRO_QUIET_MS: number;
|
|
166
|
+
export declare const KIRO_SCAN_CONCURRENCY = 16;
|
|
115
167
|
export declare class KiroSessionSource implements TranscriptSource {
|
|
116
168
|
private workingDirectory;
|
|
117
169
|
private sessionsDir;
|
|
@@ -137,6 +189,22 @@ export declare class KiroSessionSource implements TranscriptSource {
|
|
|
137
189
|
* `null` = the file does not concern us (another cwd, or a subagent child).
|
|
138
190
|
*/
|
|
139
191
|
private metaCache;
|
|
192
|
+
/** The sessions directory as last listed: re-listed only when its mtime moves (a session added or removed). */
|
|
193
|
+
private sessionsListing;
|
|
194
|
+
private fullScanAt;
|
|
195
|
+
/** Monotonic clock for the full-scan spacing; wall clock for comparing with file times. Test hooks. */
|
|
196
|
+
private mono;
|
|
197
|
+
private wall;
|
|
198
|
+
/**
|
|
199
|
+
* The newest session of this working directory, from its metadata (#1490). It used to run `readdirSync` plus a
|
|
200
|
+
* `statSync` of every metadata file on the event loop, on every 2 s poll while the store had no row for us: 16–18 ms
|
|
201
|
+
* per instance with 5,000 sessions. Now it is asynchronous and bounded:
|
|
202
|
+
* - the directory is listed again only when its mtime moved, or when it was read right after a change (file
|
|
203
|
+
* times are coarse: a session created in the same tick would share that mtime), or on a full scan;
|
|
204
|
+
* - a metadata file quiet for KIRO_QUIET_MS is not stat'ed again until the next full scan, so a poll costs one
|
|
205
|
+
* stat of the directory plus the recently active files; an old session resumed is seen within KIRO_FULL_SCAN_MS;
|
|
206
|
+
* - every full scan (each KIRO_FULL_SCAN_MS) re-stats everything, KIRO_SCAN_CONCURRENCY at a time.
|
|
207
|
+
*/
|
|
140
208
|
private resolveActiveSession;
|
|
141
209
|
poll(): Promise<TranscriptEvents>;
|
|
142
210
|
}
|
|
@@ -26,7 +26,8 @@ import { measureSyncWork } from "./sync-work-attribution.js";
|
|
|
26
26
|
* - Every poll re-resolves which transcript is active. Pinning to the first
|
|
27
27
|
* one found silently goes blind when the CLI starts a new session.
|
|
28
28
|
*/
|
|
29
|
-
import { closeSync, existsSync, openSync,
|
|
29
|
+
import { closeSync, existsSync, openSync, readSync, realpathSync } from "node:fs";
|
|
30
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
30
31
|
import { homedir } from "node:os";
|
|
31
32
|
import { join, resolve } from "node:path";
|
|
32
33
|
import { sharedRolloutIndex } from "./rollout-index.js";
|
|
@@ -34,6 +35,8 @@ import Database from "better-sqlite3";
|
|
|
34
35
|
import { sharedKiroTranscriptLane } from "./kiro-transcript-lane.js";
|
|
35
36
|
export { extractKiroAssistantStrings } from "./kiro-db-reader.js";
|
|
36
37
|
import { lastLineBoundary, readNewLines } from "./transcript-jsonl.js";
|
|
38
|
+
import { museSessionCwd, readFileHeadSync } from "./backend/muse.js";
|
|
39
|
+
import { performance } from "node:perf_hooks";
|
|
37
40
|
const EMPTY = { toolUses: [], toolResults: [], assistantTexts: [] };
|
|
38
41
|
function emptyEvents() {
|
|
39
42
|
return { toolUses: [], toolResults: [], assistantTexts: [] };
|
|
@@ -60,6 +63,8 @@ export class CodexRolloutSource {
|
|
|
60
63
|
*/
|
|
61
64
|
accepted = new Set();
|
|
62
65
|
index;
|
|
66
|
+
/** #1510: the turn the records being read belong to (its `task_started`), carried across polls of one file. */
|
|
67
|
+
openTurnId = "";
|
|
63
68
|
constructor(workingDirectory, sessionsDir = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "sessions"), _now = Date.now(), index) {
|
|
64
69
|
this.workingDirectory = workingDirectory;
|
|
65
70
|
this.sessionsDir = sessionsDir;
|
|
@@ -69,6 +74,7 @@ export class CodexRolloutSource {
|
|
|
69
74
|
reset() {
|
|
70
75
|
this.currentFile = null;
|
|
71
76
|
this.byteOffset = 0;
|
|
77
|
+
this.openTurnId = "";
|
|
72
78
|
this.rejected.clear();
|
|
73
79
|
this.accepted.clear();
|
|
74
80
|
this.snapshotExistingFiles();
|
|
@@ -164,10 +170,12 @@ export class CodexRolloutSource {
|
|
|
164
170
|
}
|
|
165
171
|
this.currentFile = active.path;
|
|
166
172
|
this.byteOffset = offset;
|
|
173
|
+
this.openTurnId = "";
|
|
167
174
|
}
|
|
168
175
|
const { lines, newOffset } = await readNewLines(this.currentFile, this.byteOffset);
|
|
169
176
|
this.byteOffset = newOffset;
|
|
170
177
|
const events = emptyEvents();
|
|
178
|
+
const turns = [];
|
|
171
179
|
for (const line of lines) {
|
|
172
180
|
let entry;
|
|
173
181
|
try {
|
|
@@ -176,11 +184,32 @@ export class CodexRolloutSource {
|
|
|
176
184
|
catch {
|
|
177
185
|
continue;
|
|
178
186
|
}
|
|
179
|
-
if (entry.type !== "response_item")
|
|
180
|
-
continue;
|
|
181
187
|
const p = entry.payload;
|
|
182
188
|
if (!p)
|
|
183
189
|
continue;
|
|
190
|
+
// #1510: codex 0.162 writes one task_started per turn and ends it with task_complete (last_agent_message null
|
|
191
|
+
// after a provider error) or turn_aborted; a steer lands inside the open turn as another user message.
|
|
192
|
+
if (entry.type === "event_msg") {
|
|
193
|
+
if (p.type === "task_started" && typeof p.turn_id === "string") {
|
|
194
|
+
this.openTurnId = p.turn_id;
|
|
195
|
+
turns.push({ kind: "start", turnId: p.turn_id });
|
|
196
|
+
}
|
|
197
|
+
else if ((p.type === "task_complete" || p.type === "turn_aborted") && typeof p.turn_id === "string") {
|
|
198
|
+
const end = p.type === "turn_aborted" ? "aborted" : p.last_agent_message == null ? "error" : "complete";
|
|
199
|
+
turns.push({ kind: "end", turnId: p.turn_id, end });
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (entry.type !== "response_item")
|
|
204
|
+
continue;
|
|
205
|
+
if (p.type === "message" && p.role === "user") {
|
|
206
|
+
const text = (p.content ?? [])
|
|
207
|
+
.map(block => typeof block.text === "string" ? block.text : "").join("\n");
|
|
208
|
+
const at = Date.parse(String(entry.timestamp ?? ""));
|
|
209
|
+
if (text.trim() && Number.isFinite(at))
|
|
210
|
+
turns.push({ kind: "user", turnId: this.openTurnId, text, at });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
184
213
|
if (p.type === "function_call") {
|
|
185
214
|
let input = p.arguments;
|
|
186
215
|
if (typeof input === "string") {
|
|
@@ -210,8 +239,230 @@ export class CodexRolloutSource {
|
|
|
210
239
|
}
|
|
211
240
|
}
|
|
212
241
|
}
|
|
242
|
+
if (turns.length)
|
|
243
|
+
events.turns = turns;
|
|
244
|
+
return events;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/* -------------------------------------------------------------------- muse */
|
|
248
|
+
/**
|
|
249
|
+
* #1510: muse's turn boundaries, from its session logs (`<root>/<YYYY>/<MM>/<DD>/<id>/session.jsonl`, recorded on
|
|
250
|
+
* 1.4.4 in tests/fixtures/reply-guard-1510). Only turns are read; muse's tool activity is not.
|
|
251
|
+
*
|
|
252
|
+
* - a run is a turn: `runtime.session` run `started` → `terminal {reason}` (null: done; "cancelled …": the user
|
|
253
|
+
* stopped it; anything else: it failed);
|
|
254
|
+
* - a message is `runtime.user_intent.accepted` (its text, surface "main") and lands in a run with
|
|
255
|
+
* `user_intent.materialized` — a new run, or the active one when it was typed mid-run;
|
|
256
|
+
* - the end-of-turn reminder observers write their own session directories and no main-surface intents.
|
|
257
|
+
*
|
|
258
|
+
* Every log whose head names this working directory is followed: ownership is decided later, by the delivered text
|
|
259
|
+
* (TranscriptTurnLedger), so two muse instances in one directory cannot vouch for each other. The directory tree is
|
|
260
|
+
* re-listed at most every `listTtlMs`; between listings only the logs already known are read, and only when they grew.
|
|
261
|
+
* Logs present when the source is created are read from their end (existing history is not this launch's).
|
|
262
|
+
*/
|
|
263
|
+
export class MuseSessionSource {
|
|
264
|
+
workingDirectory;
|
|
265
|
+
root;
|
|
266
|
+
listTtlMs;
|
|
267
|
+
now;
|
|
268
|
+
/**
|
|
269
|
+
* session.jsonl path → where to read next (cwd-matched logs only): a byte offset, or `{ anchor }` for a log that
|
|
270
|
+
* existed before this source (read from its last line boundary at `anchor` bytes, or at its size when first read
|
|
271
|
+
* if its size was not known then — never from its start).
|
|
272
|
+
*/
|
|
273
|
+
files = new Map();
|
|
274
|
+
/** Every log present at the baseline, with its size then (null: it was there, its size could not be read). */
|
|
275
|
+
preexisting = new Map();
|
|
276
|
+
/** Whether the baseline listing saw the whole tree; if not, a log found later may be old and is read from its end. */
|
|
277
|
+
baselineComplete = false;
|
|
278
|
+
rejected = new Set();
|
|
279
|
+
sizes = new Map();
|
|
280
|
+
listedAt = Number.NEGATIVE_INFINITY;
|
|
281
|
+
baselined = false;
|
|
282
|
+
/** intent_id → its text and time, until it materializes (bounded). */
|
|
283
|
+
intents = new Map();
|
|
284
|
+
constructor(workingDirectory, root = join(homedir(), ".local", "share", "muse", "sessions"), listTtlMs = 30_000, now = () => performance.now()) {
|
|
285
|
+
this.workingDirectory = workingDirectory;
|
|
286
|
+
this.root = root;
|
|
287
|
+
this.listTtlMs = listTtlMs;
|
|
288
|
+
this.now = now;
|
|
289
|
+
}
|
|
290
|
+
reset() {
|
|
291
|
+
this.files.clear();
|
|
292
|
+
this.preexisting.clear();
|
|
293
|
+
this.rejected.clear();
|
|
294
|
+
this.sizes.clear();
|
|
295
|
+
this.intents.clear();
|
|
296
|
+
this.listedAt = Number.NEGATIVE_INFINITY;
|
|
297
|
+
this.baselined = false;
|
|
298
|
+
this.baselineComplete = false;
|
|
299
|
+
}
|
|
300
|
+
async initialize() { await this.list(); }
|
|
301
|
+
async list() {
|
|
302
|
+
this.listedAt = this.now();
|
|
303
|
+
const { logs, complete } = await listMuseSessionLogs(this.root);
|
|
304
|
+
if (!this.baselined) {
|
|
305
|
+
// The baseline is every log there now, whatever its head says: history is not this launch's, and a log whose
|
|
306
|
+
// cwd cannot be read yet is still history when it can.
|
|
307
|
+
for (const path of logs)
|
|
308
|
+
this.preexisting.set(path, await stat(path).then(st => st.size, () => null));
|
|
309
|
+
this.baselineComplete = complete;
|
|
310
|
+
this.baselined = true;
|
|
311
|
+
}
|
|
312
|
+
for (const path of logs) {
|
|
313
|
+
if (this.files.has(path) || this.rejected.has(path))
|
|
314
|
+
continue;
|
|
315
|
+
const head = readFileHeadSync(path, 65_536);
|
|
316
|
+
if (head === null)
|
|
317
|
+
continue;
|
|
318
|
+
const cwd = museSessionCwd(head);
|
|
319
|
+
if (cwd === null) {
|
|
320
|
+
// A log being started names its cwd within moments; one that still does not a minute on never will (the
|
|
321
|
+
// end-of-turn reminder observers write two such logs per turn) and is not read again.
|
|
322
|
+
const mtime = await stat(path).then(st => st.mtimeMs, () => null);
|
|
323
|
+
if (mtime !== null && Date.now() - mtime > 60_000)
|
|
324
|
+
this.rejected.add(path);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (cwd !== this.workingDirectory) {
|
|
328
|
+
this.rejected.add(path);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
// Read from the start only a log provably made after the baseline.
|
|
332
|
+
const fresh = this.baselineComplete && !this.preexisting.has(path);
|
|
333
|
+
this.files.set(path, fresh ? 0 : { anchor: this.preexisting.get(path) ?? null });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async poll() {
|
|
337
|
+
if (!this.baselined || this.now() - this.listedAt >= this.listTtlMs)
|
|
338
|
+
await this.list();
|
|
339
|
+
const turns = [];
|
|
340
|
+
for (const [path, next] of this.files) {
|
|
341
|
+
const size = await stat(path).then(st => st.size, () => null);
|
|
342
|
+
if (size === null || size === this.sizes.get(path))
|
|
343
|
+
continue;
|
|
344
|
+
let from;
|
|
345
|
+
if (typeof next === "number")
|
|
346
|
+
from = next;
|
|
347
|
+
else {
|
|
348
|
+
try {
|
|
349
|
+
from = await lastLineBoundary(path, next.anchor ?? size);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
let read;
|
|
356
|
+
try {
|
|
357
|
+
read = await readNewLines(path, from);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
this.files.set(path, read.newOffset);
|
|
363
|
+
this.sizes.set(path, size);
|
|
364
|
+
// One record at a time: a record of an unexpected shape is skipped, never the batch it came in.
|
|
365
|
+
for (const line of read.lines) {
|
|
366
|
+
try {
|
|
367
|
+
this.parse(line, turns);
|
|
368
|
+
}
|
|
369
|
+
catch { /* skipped */ }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const events = emptyEvents();
|
|
373
|
+
if (turns.length)
|
|
374
|
+
events.turns = turns;
|
|
213
375
|
return events;
|
|
214
376
|
}
|
|
377
|
+
parse(line, turns) {
|
|
378
|
+
let parsed;
|
|
379
|
+
try {
|
|
380
|
+
parsed = JSON.parse(line);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const o = asRecord(parsed), p = asRecord(o?.payload);
|
|
386
|
+
if (!o || !p)
|
|
387
|
+
return;
|
|
388
|
+
const type = o.payload_type;
|
|
389
|
+
const at = typeof o.recorded_at === "number" && Number.isFinite(o.recorded_at) ? Math.floor(o.recorded_at / 1000) : NaN; // µs
|
|
390
|
+
if (type === "runtime.user_intent.accepted") {
|
|
391
|
+
if (p.surface !== "main" || typeof p.intent_id !== "string" || !Number.isFinite(at) || !Array.isArray(p.model_messages))
|
|
392
|
+
return;
|
|
393
|
+
const parts = [];
|
|
394
|
+
for (const message of p.model_messages) {
|
|
395
|
+
const content = asRecord(message)?.content;
|
|
396
|
+
if (!Array.isArray(content))
|
|
397
|
+
return;
|
|
398
|
+
for (const block of content) {
|
|
399
|
+
const b = asRecord(block);
|
|
400
|
+
if (b?.kind === "text" && typeof b.text === "string")
|
|
401
|
+
parts.push(b.text);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const text = parts.join("\n");
|
|
405
|
+
if (!text.trim())
|
|
406
|
+
return;
|
|
407
|
+
this.intents.set(p.intent_id, { text, at });
|
|
408
|
+
if (this.intents.size > 64)
|
|
409
|
+
this.intents.delete(this.intents.keys().next().value);
|
|
410
|
+
}
|
|
411
|
+
else if (type === "runtime.user_intent.materialized") {
|
|
412
|
+
const intent = typeof p.intent_id === "string" ? this.intents.get(p.intent_id) : undefined;
|
|
413
|
+
const runId = asRecord(p.outcome)?.run_id;
|
|
414
|
+
if (!intent || typeof runId !== "string")
|
|
415
|
+
return;
|
|
416
|
+
this.intents.delete(p.intent_id);
|
|
417
|
+
turns.push({ kind: "user", turnId: runId, text: intent.text, at: intent.at });
|
|
418
|
+
}
|
|
419
|
+
else if (type === "runtime.session" && p.kind === "run" && typeof p.run_id === "string") {
|
|
420
|
+
const event = asRecord(p.event);
|
|
421
|
+
if (event?.kind === "started")
|
|
422
|
+
turns.push({ kind: "start", turnId: p.run_id });
|
|
423
|
+
else if (event?.kind === "terminal" && "reason" in event) {
|
|
424
|
+
// Only what was recorded proves anything: an explicit null is a run that finished; a missing or unfamiliar
|
|
425
|
+
// reason ends nothing (the turn stays open, and the guard's running cap stands it down).
|
|
426
|
+
const reason = event.reason;
|
|
427
|
+
if (reason === null)
|
|
428
|
+
turns.push({ kind: "end", turnId: p.run_id, end: "complete" });
|
|
429
|
+
else if (typeof reason === "string")
|
|
430
|
+
turns.push({ kind: "end", turnId: p.run_id, end: reason.startsWith("cancelled") ? "aborted" : "error" });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function asRecord(value) {
|
|
436
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* `<root>/<YYYY>/<MM>/<DD>/<id>/session.jsonl` for every session directory. `complete` is false when a directory
|
|
440
|
+
* that exists could not be read: what it holds is unknown. A root that does not exist yet is complete (empty).
|
|
441
|
+
*/
|
|
442
|
+
async function listMuseSessionLogs(root) {
|
|
443
|
+
let complete = true;
|
|
444
|
+
const children = async (dir, isRoot = false) => {
|
|
445
|
+
try {
|
|
446
|
+
return await readdir(dir);
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
if (!(isRoot && err.code === "ENOENT"))
|
|
450
|
+
complete = false;
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const logs = [];
|
|
455
|
+
for (const year of await children(root, true)) {
|
|
456
|
+
if (!/^\d{4}$/.test(year))
|
|
457
|
+
continue;
|
|
458
|
+
for (const month of await children(join(root, year))) {
|
|
459
|
+
for (const day of await children(join(root, year, month))) {
|
|
460
|
+
for (const session of await children(join(root, year, month, day)))
|
|
461
|
+
logs.push(join(root, year, month, day, session, "session.jsonl"));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return { logs, complete };
|
|
215
466
|
}
|
|
216
467
|
/* -------------------------------------------------------------------- kiro */
|
|
217
468
|
/**
|
|
@@ -294,6 +545,18 @@ includeHistory = true) {
|
|
|
294
545
|
* Kiro 2.19 moved primary conversations to conversations_v2 in data.sqlite3;
|
|
295
546
|
* legacy releases use <uuid>.jsonl plus sibling <uuid>.json metadata.
|
|
296
547
|
*/
|
|
548
|
+
/** The Kiro legacy fallback's scan bounds (#1490): see KiroSessionSource.resolveActiveSession. */
|
|
549
|
+
export const KIRO_FULL_SCAN_MS = 30_000;
|
|
550
|
+
export const KIRO_QUIET_MS = 10 * 60_000;
|
|
551
|
+
export const KIRO_SCAN_CONCURRENCY = 16;
|
|
552
|
+
const KIRO_HOT_DIR_MS = 2_000;
|
|
553
|
+
/** Run `task(0..count-1)`, at most `limit` at a time. */
|
|
554
|
+
async function forEachLimited(count, limit, task) {
|
|
555
|
+
let next = 0;
|
|
556
|
+
const worker = async () => { while (next < count)
|
|
557
|
+
await task(next++); };
|
|
558
|
+
await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
|
|
559
|
+
}
|
|
297
560
|
export class KiroSessionSource {
|
|
298
561
|
workingDirectory;
|
|
299
562
|
sessionsDir;
|
|
@@ -365,26 +628,59 @@ export class KiroSessionSource {
|
|
|
365
628
|
* `null` = the file does not concern us (another cwd, or a subagent child).
|
|
366
629
|
*/
|
|
367
630
|
metaCache = new Map();
|
|
368
|
-
|
|
369
|
-
|
|
631
|
+
/** The sessions directory as last listed: re-listed only when its mtime moves (a session added or removed). */
|
|
632
|
+
sessionsListing = null;
|
|
633
|
+
fullScanAt = Number.NEGATIVE_INFINITY;
|
|
634
|
+
/** Monotonic clock for the full-scan spacing; wall clock for comparing with file times. Test hooks. */
|
|
635
|
+
mono = () => performance.now();
|
|
636
|
+
wall = () => Date.now();
|
|
637
|
+
/**
|
|
638
|
+
* The newest session of this working directory, from its metadata (#1490). It used to run `readdirSync` plus a
|
|
639
|
+
* `statSync` of every metadata file on the event loop, on every 2 s poll while the store had no row for us: 16–18 ms
|
|
640
|
+
* per instance with 5,000 sessions. Now it is asynchronous and bounded:
|
|
641
|
+
* - the directory is listed again only when its mtime moved, or when it was read right after a change (file
|
|
642
|
+
* times are coarse: a session created in the same tick would share that mtime), or on a full scan;
|
|
643
|
+
* - a metadata file quiet for KIRO_QUIET_MS is not stat'ed again until the next full scan, so a poll costs one
|
|
644
|
+
* stat of the directory plus the recently active files; an old session resumed is seen within KIRO_FULL_SCAN_MS;
|
|
645
|
+
* - every full scan (each KIRO_FULL_SCAN_MS) re-stats everything, KIRO_SCAN_CONCURRENCY at a time.
|
|
646
|
+
*/
|
|
647
|
+
async resolveActiveSession() {
|
|
648
|
+
const now = this.wall();
|
|
649
|
+
const full = this.mono() - this.fullScanAt >= KIRO_FULL_SCAN_MS;
|
|
650
|
+
let dirMtimeMs;
|
|
370
651
|
try {
|
|
371
|
-
|
|
652
|
+
dirMtimeMs = (await stat(this.sessionsDir)).mtimeMs;
|
|
372
653
|
}
|
|
373
654
|
catch {
|
|
374
655
|
return null;
|
|
375
656
|
}
|
|
376
|
-
let
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (!e.endsWith(".json") || e.endsWith(".jsonl"))
|
|
380
|
-
continue;
|
|
381
|
-
const metaPath = join(this.sessionsDir, e);
|
|
382
|
-
seen.add(metaPath);
|
|
657
|
+
let listing = this.sessionsListing;
|
|
658
|
+
if (full || !listing || listing.mtimeMs !== dirMtimeMs || listing.hotWhenRead) {
|
|
659
|
+
let entries;
|
|
383
660
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
661
|
+
entries = await readdir(this.sessionsDir);
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
listing = { mtimeMs: dirMtimeMs, names: entries.filter(e => e.endsWith(".json") && !e.endsWith(".jsonl")).sort(), hotWhenRead: now - dirMtimeMs < KIRO_HOT_DIR_MS };
|
|
667
|
+
this.sessionsListing = listing;
|
|
668
|
+
}
|
|
669
|
+
if (full)
|
|
670
|
+
this.fullScanAt = this.mono();
|
|
671
|
+
const verdicts = new Array(listing.names.length).fill(null);
|
|
672
|
+
const names = listing.names;
|
|
673
|
+
await forEachLimited(names.length, KIRO_SCAN_CONCURRENCY, async (i) => {
|
|
674
|
+
const metaPath = join(this.sessionsDir, names[i]);
|
|
675
|
+
let cached = this.metaCache.get(metaPath);
|
|
676
|
+
if (!full && cached && now - cached.mtimeMs > KIRO_QUIET_MS) {
|
|
677
|
+
verdicts[i] = cached.meta;
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
const st = await stat(metaPath);
|
|
386
682
|
if (!cached || cached.mtimeMs !== st.mtimeMs || cached.size !== st.size) {
|
|
387
|
-
const meta = JSON.parse(
|
|
683
|
+
const meta = JSON.parse(await readFile(metaPath, "utf-8"));
|
|
388
684
|
// Subagent sessions are children of a turn already being reported.
|
|
389
685
|
const ours = meta.cwd === this.workingDirectory && meta.session_created_reason !== "subagent";
|
|
390
686
|
cached = {
|
|
@@ -396,21 +692,31 @@ export class KiroSessionSource {
|
|
|
396
692
|
};
|
|
397
693
|
this.metaCache.set(metaPath, cached);
|
|
398
694
|
}
|
|
399
|
-
|
|
400
|
-
continue;
|
|
401
|
-
if (!best || cached.meta.updated > best.updated) {
|
|
402
|
-
const jsonlPath = join(this.sessionsDir, e.replace(/\.json$/, ".jsonl"));
|
|
403
|
-
best = { jsonlPath, updated: cached.meta.updated, createdAtMs: cached.meta.created };
|
|
404
|
-
}
|
|
695
|
+
verdicts[i] = cached.meta;
|
|
405
696
|
}
|
|
406
|
-
catch { /* partially written metadata — next poll */ }
|
|
407
|
-
}
|
|
697
|
+
catch { /* partially written metadata, or deleted meanwhile — next poll */ }
|
|
698
|
+
});
|
|
699
|
+
const seen = new Set(names.map(e => join(this.sessionsDir, e)));
|
|
408
700
|
for (const known of this.metaCache.keys())
|
|
409
701
|
if (!seen.has(known))
|
|
410
702
|
this.metaCache.delete(known);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
703
|
+
let best = null;
|
|
704
|
+
names.forEach((e, i) => {
|
|
705
|
+
const meta = verdicts[i];
|
|
706
|
+
if (meta && (!best || meta.updated > best.updated)) {
|
|
707
|
+
best = { jsonlPath: join(this.sessionsDir, e.replace(/\.json$/, ".jsonl")), updated: meta.updated, createdAtMs: meta.created };
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
const chosen = best;
|
|
711
|
+
if (!chosen)
|
|
712
|
+
return null;
|
|
713
|
+
try {
|
|
714
|
+
await stat(chosen.jsonlPath);
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
return { jsonlPath: chosen.jsonlPath, createdAtMs: chosen.createdAtMs };
|
|
414
720
|
}
|
|
415
721
|
async poll() {
|
|
416
722
|
// Current Kiro stores the primary session in SQLite; JSONL is now mostly
|
|
@@ -421,8 +727,8 @@ export class KiroSessionSource {
|
|
|
421
727
|
return EMPTY;
|
|
422
728
|
if (dbEvents !== null)
|
|
423
729
|
return dbEvents;
|
|
424
|
-
const active = this.resolveActiveSession();
|
|
425
|
-
if (!active)
|
|
730
|
+
const active = await this.resolveActiveSession();
|
|
731
|
+
if (generation !== this.generation || !active)
|
|
426
732
|
return EMPTY;
|
|
427
733
|
if (active.jsonlPath !== this.currentFile) {
|
|
428
734
|
if (active.createdAtMs >= this.createdAt) {
|
|
@@ -590,6 +896,7 @@ export function createTranscriptSource(backend, workingDirectory,
|
|
|
590
896
|
storeHome) {
|
|
591
897
|
switch (backend) {
|
|
592
898
|
case "codex": return new CodexRolloutSource(workingDirectory);
|
|
899
|
+
case "muse": return new MuseSessionSource(workingDirectory);
|
|
593
900
|
// undefined keeps each parameter's own default; only the store moves.
|
|
594
901
|
case "kiro-cli": return new KiroSessionSource(workingDirectory, undefined, undefined, kiroStoreDbPath(storeHome));
|
|
595
902
|
case "opencode": return new OpenCodeDbSource(workingDirectory);
|