@adhdev/daemon-core 0.9.82-rc.195 → 0.9.82-rc.196
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/cli-adapter-types.d.ts +1 -0
- package/dist/index.js +202 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -73
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/contracts.d.ts +1 -1
- package/dist/mesh/mesh-active-work.d.ts +1 -1
- package/dist/mesh/{beads-db.d.ts → mesh-runtime-store.d.ts} +2 -2
- package/dist/mesh/mesh-work-queue.d.ts +3 -3
- package/dist/providers/provider-instance.d.ts +1 -1
- package/dist/providers/spec/driver.d.ts +4 -1
- package/dist/providers/spec/schema.gen.d.ts +46 -0
- package/dist/providers/spec/types.d.ts +39 -0
- package/dist/shared-types-extra.d.ts +1 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli-adapter-types.ts +1 -0
- package/src/cli-adapters/cli-state-engine.ts +44 -2
- package/src/mesh/contracts.ts +1 -1
- package/src/mesh/mesh-active-work.ts +8 -8
- package/src/mesh/mesh-events.ts +12 -12
- package/src/mesh/{beads-db.ts → mesh-runtime-store.ts} +30 -7
- package/src/mesh/mesh-work-queue.ts +33 -33
- package/src/providers/cli-provider-instance.ts +31 -8
- package/src/providers/provider-instance.ts +1 -1
- package/src/providers/spec/driver.ts +34 -3
- package/src/providers/spec/evaluator.ts +32 -3
- package/src/providers/spec/schema.gen.ts +22 -2
- package/src/providers/spec/schema.json +1 -0
- package/src/providers/spec/types.ts +39 -0
- package/src/providers/types/interactive-prompt.ts +21 -7
- package/src/shared-types-extra.ts +1 -1
- package/src/status/normalize.ts +2 -0
package/dist/mesh/contracts.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - Session identifier keys diverged across stores: targetSessionId /
|
|
17
17
|
* assignedSessionId / instanceId / runtimeSessionId / providerSessionId.
|
|
18
18
|
* resolveEventSessionId() tried four fallbacks per call.
|
|
19
|
-
* - No protocol version on the JSONL ledger or
|
|
19
|
+
* - No protocol version on the JSONL ledger or MeshRuntimeStore. Schema
|
|
20
20
|
* evolutions had no guard rail.
|
|
21
21
|
* - mesh_reconcile_ledger existed as a routine recovery tool, not as
|
|
22
22
|
* an incident-response escape hatch. That itself signals the routing
|
|
@@ -65,7 +65,7 @@ export interface BuildMeshActiveWorkOptions {
|
|
|
65
65
|
queue?: MeshWorkQueueEntry[];
|
|
66
66
|
ledgerEntries?: MeshLedgerEntry[];
|
|
67
67
|
/**
|
|
68
|
-
* Active direct dispatches from
|
|
68
|
+
* Active direct dispatches from MeshRuntimeStore. When provided, these are used instead of
|
|
69
69
|
* scanning ledger entries for direct dispatches — eliminates the O(n_ledger) scan.
|
|
70
70
|
* Falls back to ledger scanning when not provided.
|
|
71
71
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
2
|
-
export declare class
|
|
2
|
+
export declare class MeshRuntimeStore {
|
|
3
3
|
private static instance;
|
|
4
4
|
private readonly db;
|
|
5
5
|
private readonly dbPath;
|
|
@@ -9,7 +9,7 @@ export declare class BeadsDB {
|
|
|
9
9
|
private static readonly WAL_CHECK_INTERVAL;
|
|
10
10
|
private static readonly WAL_MAX_BYTES;
|
|
11
11
|
private constructor();
|
|
12
|
-
static getInstance():
|
|
12
|
+
static getInstance(): MeshRuntimeStore;
|
|
13
13
|
static resetForTests(): void;
|
|
14
14
|
close(): void;
|
|
15
15
|
transaction<T>(fn: () => T): T;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
3
3
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
4
4
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
5
5
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
@@ -137,8 +137,8 @@ export declare function getMeshQueueStats(meshId: string): MeshWorkQueueStats;
|
|
|
137
137
|
export declare function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void;
|
|
138
138
|
export declare function __clearMeshQueueForTests(meshId: string): void;
|
|
139
139
|
export declare function __clearDirectDispatchesForTests(meshId: string): void;
|
|
140
|
-
export declare function
|
|
141
|
-
export type DirectDispatchRecord = ReturnType<
|
|
140
|
+
export declare function __resetMeshRuntimeStoreForTests(): void;
|
|
141
|
+
export type DirectDispatchRecord = ReturnType<MeshRuntimeStore['getActiveDirectDispatches']>[number];
|
|
142
142
|
export declare function insertDirectDispatch(meshId: string, data: {
|
|
143
143
|
taskId: string;
|
|
144
144
|
nodeId?: string;
|
|
@@ -12,7 +12,7 @@ import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMe
|
|
|
12
12
|
import type { MessageInputSupport } from './provider-input-support.js';
|
|
13
13
|
import type { ChatMessage } from '../types.js';
|
|
14
14
|
import type { InteractivePrompt } from './types/interactive-prompt.js';
|
|
15
|
-
export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
15
|
+
export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'waiting_choice' | 'error' | 'stopped' | 'starting';
|
|
16
16
|
export interface ProviderRuntimeWriteOwner {
|
|
17
17
|
clientId: string;
|
|
18
18
|
ownerType: 'agent' | 'user';
|
|
@@ -98,7 +98,10 @@ export interface SpecDriverOpts {
|
|
|
98
98
|
*/
|
|
99
99
|
export declare function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number;
|
|
100
100
|
export declare function matchesCompletionIdleRule(spec: CliSpec, ev: SpecEvaluation, screen: string): string | null;
|
|
101
|
-
export declare function matchesCompletionIdleTargetState(spec: CliSpec, ev: SpecEvaluation, screen: string
|
|
101
|
+
export declare function matchesCompletionIdleTargetState(spec: CliSpec, ev: SpecEvaluation, screen: string, cursor?: {
|
|
102
|
+
row: number;
|
|
103
|
+
col: number;
|
|
104
|
+
}): boolean;
|
|
102
105
|
export declare class SpecDriver {
|
|
103
106
|
private readonly opts;
|
|
104
107
|
private spec;
|
|
@@ -112,6 +112,9 @@ export declare const SCHEMA: {
|
|
|
112
112
|
readonly type: "string";
|
|
113
113
|
readonly minLength: 1;
|
|
114
114
|
};
|
|
115
|
+
readonly requiresFinalAssistantBeforeIdle: {
|
|
116
|
+
readonly type: "boolean";
|
|
117
|
+
};
|
|
115
118
|
readonly debounce: {
|
|
116
119
|
readonly type: "object";
|
|
117
120
|
readonly additionalProperties: false;
|
|
@@ -144,6 +147,10 @@ export declare const SCHEMA: {
|
|
|
144
147
|
readonly type: "integer";
|
|
145
148
|
readonly minimum: 0;
|
|
146
149
|
};
|
|
150
|
+
readonly force_after_ms: {
|
|
151
|
+
readonly type: "integer";
|
|
152
|
+
readonly minimum: 0;
|
|
153
|
+
};
|
|
147
154
|
};
|
|
148
155
|
};
|
|
149
156
|
};
|
|
@@ -185,6 +192,45 @@ export declare const SCHEMA: {
|
|
|
185
192
|
};
|
|
186
193
|
};
|
|
187
194
|
};
|
|
195
|
+
readonly anchor_regex: {
|
|
196
|
+
readonly type: "string";
|
|
197
|
+
readonly minLength: 1;
|
|
198
|
+
};
|
|
199
|
+
readonly anchor_flags: {
|
|
200
|
+
readonly type: "string";
|
|
201
|
+
};
|
|
202
|
+
readonly anchor_last: {
|
|
203
|
+
readonly type: "boolean";
|
|
204
|
+
};
|
|
205
|
+
readonly anchor_context: {
|
|
206
|
+
readonly type: "object";
|
|
207
|
+
readonly additionalProperties: false;
|
|
208
|
+
readonly properties: {
|
|
209
|
+
readonly prev: {
|
|
210
|
+
readonly type: "string";
|
|
211
|
+
};
|
|
212
|
+
readonly prev_flags: {
|
|
213
|
+
readonly type: "string";
|
|
214
|
+
};
|
|
215
|
+
readonly next: {
|
|
216
|
+
readonly type: "string";
|
|
217
|
+
};
|
|
218
|
+
readonly next_flags: {
|
|
219
|
+
readonly type: "string";
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
readonly lines: {
|
|
224
|
+
readonly type: "integer";
|
|
225
|
+
readonly minimum: 1;
|
|
226
|
+
};
|
|
227
|
+
readonly until_regex: {
|
|
228
|
+
readonly type: "string";
|
|
229
|
+
readonly minLength: 1;
|
|
230
|
+
};
|
|
231
|
+
readonly until_regex_flags: {
|
|
232
|
+
readonly type: "string";
|
|
233
|
+
};
|
|
188
234
|
};
|
|
189
235
|
};
|
|
190
236
|
readonly sectionRegex: {
|
|
@@ -6,6 +6,33 @@ export interface Section {
|
|
|
6
6
|
until?: {
|
|
7
7
|
section: string;
|
|
8
8
|
};
|
|
9
|
+
/** Scan the screen for the first (or last, if anchor_last=true) line
|
|
10
|
+
* matching this regex and use that line as the section start.
|
|
11
|
+
* Combine with `lines`, `until_regex`, or leave alone (defaults to
|
|
12
|
+
* end-of-screen) to control how far it extends. */
|
|
13
|
+
anchor_regex?: string;
|
|
14
|
+
anchor_flags?: string;
|
|
15
|
+
/** If true, use the LAST matching line instead of the first. Useful
|
|
16
|
+
* when the anchor pattern appears multiple times (e.g. separator
|
|
17
|
+
* lines) and you want the one closest to the bottom. */
|
|
18
|
+
anchor_last?: boolean;
|
|
19
|
+
/** Optional neighbouring-line guards that must also match for the
|
|
20
|
+
* anchor to be accepted. Reduces false positives when the anchor
|
|
21
|
+
* pattern could appear elsewhere on screen (e.g. `>` in body text).
|
|
22
|
+
* `prev` checks the line immediately above; `next` the line below. */
|
|
23
|
+
anchor_context?: {
|
|
24
|
+
prev?: string;
|
|
25
|
+
prev_flags?: string;
|
|
26
|
+
next?: string;
|
|
27
|
+
next_flags?: string;
|
|
28
|
+
};
|
|
29
|
+
/** When used with anchor_regex: take at most this many lines from the
|
|
30
|
+
* anchor point. */
|
|
31
|
+
lines?: number;
|
|
32
|
+
/** When used with anchor_regex: extend until the first line matching
|
|
33
|
+
* this regex (exclusive). Takes precedence over `lines`. */
|
|
34
|
+
until_regex?: string;
|
|
35
|
+
until_regex_flags?: string;
|
|
9
36
|
}
|
|
10
37
|
export interface SectionRegex {
|
|
11
38
|
section?: string;
|
|
@@ -205,6 +232,13 @@ export interface CliSpec {
|
|
|
205
232
|
notifications?: NotificationRule[];
|
|
206
233
|
delegate?: DelegateTrigger[];
|
|
207
234
|
native_history?: NativeHistoryConfig;
|
|
235
|
+
/**
|
|
236
|
+
* When true, the daemon's completed-finalization gate will not emit
|
|
237
|
+
* `generating_completed` until native history confirms the last message
|
|
238
|
+
* role is `assistant`. Prevents PTY quiet-period from triggering idle
|
|
239
|
+
* before the CLI has finished writing its response to disk.
|
|
240
|
+
*/
|
|
241
|
+
requiresFinalAssistantBeforeIdle?: boolean;
|
|
208
242
|
/**
|
|
209
243
|
* Per-spec debounce knobs. Defaults are conservative (busy_hold_ms
|
|
210
244
|
* 6000) and live in SpecDriver. Spec authors override here when a
|
|
@@ -232,6 +266,11 @@ export interface CliSpec {
|
|
|
232
266
|
regex: string;
|
|
233
267
|
flags?: string;
|
|
234
268
|
hold_ms: number;
|
|
269
|
+
/** If the target-state check still fails this many ms after hold_ms
|
|
270
|
+
* expired, force an idle transition anyway. Guards against TUIs that
|
|
271
|
+
* show transient UI (pickers, option lists) after the completion
|
|
272
|
+
* marker appears, keeping the target-state regex from matching. */
|
|
273
|
+
force_after_ms?: number;
|
|
235
274
|
};
|
|
236
275
|
};
|
|
237
276
|
}
|
|
@@ -18,7 +18,7 @@ export interface RuntimeAttachedClient {
|
|
|
18
18
|
readOnly: boolean;
|
|
19
19
|
}
|
|
20
20
|
/** Session status union (used by SessionEntry.status, legacy recent-launch metadata, etc.) */
|
|
21
|
-
export type SessionStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
21
|
+
export type SessionStatus = 'idle' | 'generating' | 'waiting_approval' | 'waiting_choice' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
22
22
|
/** Inbox bucket categories for recent sessions */
|
|
23
23
|
export type RecentSessionBucket = 'needs_attention' | 'working' | 'task_complete' | 'idle';
|
|
24
24
|
/** Terminal backend status */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActiveChatData } from '../providers/provider-instance.js';
|
|
2
2
|
import type { SessionActiveChatData } from '../shared-types.js';
|
|
3
|
-
export type ManagedStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
3
|
+
export type ManagedStatus = 'idle' | 'generating' | 'waiting_approval' | 'waiting_choice' | 'error' | 'stopped' | 'starting' | 'panel_hidden' | 'not_monitored' | 'disconnected';
|
|
4
4
|
export interface NormalizeActiveChatOptions {
|
|
5
5
|
includeMessages?: boolean;
|
|
6
6
|
includeInputContent?: boolean;
|
package/dist/status/normalize.js
CHANGED
|
@@ -119,6 +119,7 @@ function normalizeManagedStatus(status, opts) {
|
|
|
119
119
|
if (hasApprovalButtons(opts?.activeModal)) return "waiting_approval";
|
|
120
120
|
const normalized = String(status || "idle").trim().toLowerCase();
|
|
121
121
|
if (normalized === "waiting_approval") return "waiting_approval";
|
|
122
|
+
if (normalized === "waiting_choice") return "waiting_choice";
|
|
122
123
|
if (WORKING_STATUSES.has(normalized)) return "generating";
|
|
123
124
|
if (normalized === "error") return "error";
|
|
124
125
|
if (normalized === "stopped") return "stopped";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\nimport type { SessionActiveChatData } from '../shared-types.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nexport interface NormalizeActiveChatOptions {\n includeMessages?: boolean;\n includeInputContent?: boolean;\n includeActiveModal?: boolean;\n messageLimit?: number;\n totalBytesLimit?: number;\n stringLimit?: number;\n fallbackStringLimit?: number;\n}\n\n// Full snapshots are still capped, but can carry recent chat context for API/inspection use.\nconst FULL_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: true,\n includeInputContent: true,\n includeActiveModal: true,\n messageLimit: 60,\n totalBytesLimit: 96 * 1024,\n stringLimit: 4 * 1024,\n fallbackStringLimit: 1024,\n};\n\n// Live/metadata snapshots only need routing + UI summary. Current chat text is loaded\n// on demand via `read_chat`, and older history via `chat_history`.\nexport const LIVE_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: false,\n includeInputContent: false,\n includeActiveModal: false,\n messageLimit: 0,\n totalBytesLimit: 0,\n stringLimit: 512,\n fallbackStringLimit: 256,\n};\n\nconst STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;\nconst STATUS_MODAL_BUTTON_LIMIT = 120;\n\nfunction truncateString(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));\n return `${value.slice(0, maxChars - 12)}...[truncated]`;\n}\n\nfunction truncateStringTail(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));\n return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;\n}\n\nfunction trimStructuredStrings(value: unknown, maxChars: number): unknown {\n if (typeof value === 'string') return truncateString(value, maxChars);\n if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));\n if (!value || typeof value !== 'object') return value;\n return Object.fromEntries(\n Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),\n );\n}\n\nfunction estimateBytes(value: unknown): number {\n try {\n return JSON.stringify(value).length;\n } catch {\n return String(value ?? '').length;\n }\n}\n\nfunction trimMessageForStatus(message: unknown, stringLimit: number): unknown {\n if (!message || typeof message !== 'object') return message;\n return trimStructuredStrings(message, stringLimit);\n}\n\n/**\n * Collapse timestamp / createdAt into receivedAt so downstream consumers\n * only ever need to read a single canonical time field.\n */\nfunction normalizeMessageTime(message: unknown): unknown {\n if (!message || typeof message !== 'object') return message;\n const msg = message as Record<string, unknown>;\n if (msg.receivedAt == null) {\n const fallback = msg.timestamp ?? msg.createdAt;\n if (fallback != null) {\n const ts = typeof fallback === 'string' ? Date.parse(fallback as string) : Number(fallback);\n if (Number.isFinite(ts) && ts > 0) msg.receivedAt = ts;\n }\n }\n return msg;\n}\n\nfunction trimMessagesForStatus(\n messages: unknown[] | null | undefined,\n options: Required<NormalizeActiveChatOptions>,\n): unknown[] {\n if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];\n if (!Array.isArray(messages) || messages.length === 0) return [];\n\n const recent = messages.slice(-options.messageLimit);\n const kept: unknown[] = [];\n let totalBytes = 0;\n\n for (let i = recent.length - 1; i >= 0; i -= 1) {\n let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));\n let size = estimateBytes(normalized);\n\n if (size > options.totalBytesLimit) {\n normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));\n size = estimateBytes(normalized);\n }\n\n if (kept.length > 0 && (totalBytes + size) > options.totalBytesLimit) {\n continue;\n }\n\n kept.push(normalized);\n totalBytes += size;\n }\n\n return kept.reverse();\n}\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n options: NormalizeActiveChatOptions = FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n): T extends null | undefined ? T : SessionActiveChatData {\n if (!activeChat) return activeChat as T extends null | undefined ? T : SessionActiveChatData;\n const resolvedOptions: Required<NormalizeActiveChatOptions> = {\n ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n ...options,\n };\n const {\n messages: _messages,\n ...rest\n } = activeChat;\n const normalized: SessionActiveChatData = {\n ...rest,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {\n message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),\n buttons: (activeChat.activeModal.buttons || []).map((button) =>\n truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)\n ),\n } : null,\n inputContent: resolvedOptions.includeInputContent && activeChat.inputContent\n ? truncateString(activeChat.inputContent, 2 * 1024)\n : undefined,\n };\n if (resolvedOptions.includeMessages) {\n normalized.messages = trimMessagesForStatus(activeChat.messages, resolvedOptions) as SessionActiveChatData['messages'];\n }\n return normalized as T extends null | undefined ? T : SessionActiveChatData;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\nimport type { SessionActiveChatData } from '../shared-types.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'waiting_choice'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nexport interface NormalizeActiveChatOptions {\n includeMessages?: boolean;\n includeInputContent?: boolean;\n includeActiveModal?: boolean;\n messageLimit?: number;\n totalBytesLimit?: number;\n stringLimit?: number;\n fallbackStringLimit?: number;\n}\n\n// Full snapshots are still capped, but can carry recent chat context for API/inspection use.\nconst FULL_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: true,\n includeInputContent: true,\n includeActiveModal: true,\n messageLimit: 60,\n totalBytesLimit: 96 * 1024,\n stringLimit: 4 * 1024,\n fallbackStringLimit: 1024,\n};\n\n// Live/metadata snapshots only need routing + UI summary. Current chat text is loaded\n// on demand via `read_chat`, and older history via `chat_history`.\nexport const LIVE_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: false,\n includeInputContent: false,\n includeActiveModal: false,\n messageLimit: 0,\n totalBytesLimit: 0,\n stringLimit: 512,\n fallbackStringLimit: 256,\n};\n\nconst STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;\nconst STATUS_MODAL_BUTTON_LIMIT = 120;\n\nfunction truncateString(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));\n return `${value.slice(0, maxChars - 12)}...[truncated]`;\n}\n\nfunction truncateStringTail(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));\n return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;\n}\n\nfunction trimStructuredStrings(value: unknown, maxChars: number): unknown {\n if (typeof value === 'string') return truncateString(value, maxChars);\n if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));\n if (!value || typeof value !== 'object') return value;\n return Object.fromEntries(\n Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),\n );\n}\n\nfunction estimateBytes(value: unknown): number {\n try {\n return JSON.stringify(value).length;\n } catch {\n return String(value ?? '').length;\n }\n}\n\nfunction trimMessageForStatus(message: unknown, stringLimit: number): unknown {\n if (!message || typeof message !== 'object') return message;\n return trimStructuredStrings(message, stringLimit);\n}\n\n/**\n * Collapse timestamp / createdAt into receivedAt so downstream consumers\n * only ever need to read a single canonical time field.\n */\nfunction normalizeMessageTime(message: unknown): unknown {\n if (!message || typeof message !== 'object') return message;\n const msg = message as Record<string, unknown>;\n if (msg.receivedAt == null) {\n const fallback = msg.timestamp ?? msg.createdAt;\n if (fallback != null) {\n const ts = typeof fallback === 'string' ? Date.parse(fallback as string) : Number(fallback);\n if (Number.isFinite(ts) && ts > 0) msg.receivedAt = ts;\n }\n }\n return msg;\n}\n\nfunction trimMessagesForStatus(\n messages: unknown[] | null | undefined,\n options: Required<NormalizeActiveChatOptions>,\n): unknown[] {\n if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];\n if (!Array.isArray(messages) || messages.length === 0) return [];\n\n const recent = messages.slice(-options.messageLimit);\n const kept: unknown[] = [];\n let totalBytes = 0;\n\n for (let i = recent.length - 1; i >= 0; i -= 1) {\n let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));\n let size = estimateBytes(normalized);\n\n if (size > options.totalBytesLimit) {\n normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));\n size = estimateBytes(normalized);\n }\n\n if (kept.length > 0 && (totalBytes + size) > options.totalBytesLimit) {\n continue;\n }\n\n kept.push(normalized);\n totalBytes += size;\n }\n\n return kept.reverse();\n}\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (normalized === 'waiting_choice') return 'waiting_choice';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n options: NormalizeActiveChatOptions = FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n): T extends null | undefined ? T : SessionActiveChatData {\n if (!activeChat) return activeChat as T extends null | undefined ? T : SessionActiveChatData;\n const resolvedOptions: Required<NormalizeActiveChatOptions> = {\n ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n ...options,\n };\n const {\n messages: _messages,\n ...rest\n } = activeChat;\n const normalized: SessionActiveChatData = {\n ...rest,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {\n message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),\n buttons: (activeChat.activeModal.buttons || []).map((button) =>\n truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)\n ),\n } : null,\n inputContent: resolvedOptions.includeInputContent && activeChat.inputContent\n ? truncateString(activeChat.inputContent, 2 * 1024)\n : undefined,\n };\n if (resolvedOptions.includeMessages) {\n normalized.messages = trimMessagesForStatus(activeChat.messages, resolvedOptions) as SessionActiveChatData['messages'];\n }\n return normalized as T extends null | undefined ? T : SessionActiveChatData;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -91,6 +91,7 @@ function normalizeManagedStatus(status, opts) {
|
|
|
91
91
|
if (hasApprovalButtons(opts?.activeModal)) return "waiting_approval";
|
|
92
92
|
const normalized = String(status || "idle").trim().toLowerCase();
|
|
93
93
|
if (normalized === "waiting_approval") return "waiting_approval";
|
|
94
|
+
if (normalized === "waiting_choice") return "waiting_choice";
|
|
94
95
|
if (WORKING_STATUSES.has(normalized)) return "generating";
|
|
95
96
|
if (normalized === "error") return "error";
|
|
96
97
|
if (normalized === "stopped") return "stopped";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\nimport type { SessionActiveChatData } from '../shared-types.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nexport interface NormalizeActiveChatOptions {\n includeMessages?: boolean;\n includeInputContent?: boolean;\n includeActiveModal?: boolean;\n messageLimit?: number;\n totalBytesLimit?: number;\n stringLimit?: number;\n fallbackStringLimit?: number;\n}\n\n// Full snapshots are still capped, but can carry recent chat context for API/inspection use.\nconst FULL_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: true,\n includeInputContent: true,\n includeActiveModal: true,\n messageLimit: 60,\n totalBytesLimit: 96 * 1024,\n stringLimit: 4 * 1024,\n fallbackStringLimit: 1024,\n};\n\n// Live/metadata snapshots only need routing + UI summary. Current chat text is loaded\n// on demand via `read_chat`, and older history via `chat_history`.\nexport const LIVE_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: false,\n includeInputContent: false,\n includeActiveModal: false,\n messageLimit: 0,\n totalBytesLimit: 0,\n stringLimit: 512,\n fallbackStringLimit: 256,\n};\n\nconst STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;\nconst STATUS_MODAL_BUTTON_LIMIT = 120;\n\nfunction truncateString(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));\n return `${value.slice(0, maxChars - 12)}...[truncated]`;\n}\n\nfunction truncateStringTail(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));\n return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;\n}\n\nfunction trimStructuredStrings(value: unknown, maxChars: number): unknown {\n if (typeof value === 'string') return truncateString(value, maxChars);\n if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));\n if (!value || typeof value !== 'object') return value;\n return Object.fromEntries(\n Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),\n );\n}\n\nfunction estimateBytes(value: unknown): number {\n try {\n return JSON.stringify(value).length;\n } catch {\n return String(value ?? '').length;\n }\n}\n\nfunction trimMessageForStatus(message: unknown, stringLimit: number): unknown {\n if (!message || typeof message !== 'object') return message;\n return trimStructuredStrings(message, stringLimit);\n}\n\n/**\n * Collapse timestamp / createdAt into receivedAt so downstream consumers\n * only ever need to read a single canonical time field.\n */\nfunction normalizeMessageTime(message: unknown): unknown {\n if (!message || typeof message !== 'object') return message;\n const msg = message as Record<string, unknown>;\n if (msg.receivedAt == null) {\n const fallback = msg.timestamp ?? msg.createdAt;\n if (fallback != null) {\n const ts = typeof fallback === 'string' ? Date.parse(fallback as string) : Number(fallback);\n if (Number.isFinite(ts) && ts > 0) msg.receivedAt = ts;\n }\n }\n return msg;\n}\n\nfunction trimMessagesForStatus(\n messages: unknown[] | null | undefined,\n options: Required<NormalizeActiveChatOptions>,\n): unknown[] {\n if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];\n if (!Array.isArray(messages) || messages.length === 0) return [];\n\n const recent = messages.slice(-options.messageLimit);\n const kept: unknown[] = [];\n let totalBytes = 0;\n\n for (let i = recent.length - 1; i >= 0; i -= 1) {\n let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));\n let size = estimateBytes(normalized);\n\n if (size > options.totalBytesLimit) {\n normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));\n size = estimateBytes(normalized);\n }\n\n if (kept.length > 0 && (totalBytes + size) > options.totalBytesLimit) {\n continue;\n }\n\n kept.push(normalized);\n totalBytes += size;\n }\n\n return kept.reverse();\n}\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n options: NormalizeActiveChatOptions = FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n): T extends null | undefined ? T : SessionActiveChatData {\n if (!activeChat) return activeChat as T extends null | undefined ? T : SessionActiveChatData;\n const resolvedOptions: Required<NormalizeActiveChatOptions> = {\n ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n ...options,\n };\n const {\n messages: _messages,\n ...rest\n } = activeChat;\n const normalized: SessionActiveChatData = {\n ...rest,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {\n message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),\n buttons: (activeChat.activeModal.buttons || []).map((button) =>\n truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)\n ),\n } : null,\n inputContent: resolvedOptions.includeInputContent && activeChat.inputContent\n ? truncateString(activeChat.inputContent, 2 * 1024)\n : undefined,\n };\n if (resolvedOptions.includeMessages) {\n normalized.messages = trimMessagesForStatus(activeChat.messages, resolvedOptions) as SessionActiveChatData['messages'];\n }\n return normalized as T extends null | undefined ? T : SessionActiveChatData;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\nimport type { SessionActiveChatData } from '../shared-types.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'waiting_choice'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nexport interface NormalizeActiveChatOptions {\n includeMessages?: boolean;\n includeInputContent?: boolean;\n includeActiveModal?: boolean;\n messageLimit?: number;\n totalBytesLimit?: number;\n stringLimit?: number;\n fallbackStringLimit?: number;\n}\n\n// Full snapshots are still capped, but can carry recent chat context for API/inspection use.\nconst FULL_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: true,\n includeInputContent: true,\n includeActiveModal: true,\n messageLimit: 60,\n totalBytesLimit: 96 * 1024,\n stringLimit: 4 * 1024,\n fallbackStringLimit: 1024,\n};\n\n// Live/metadata snapshots only need routing + UI summary. Current chat text is loaded\n// on demand via `read_chat`, and older history via `chat_history`.\nexport const LIVE_STATUS_ACTIVE_CHAT_OPTIONS: Required<NormalizeActiveChatOptions> = {\n includeMessages: false,\n includeInputContent: false,\n includeActiveModal: false,\n messageLimit: 0,\n totalBytesLimit: 0,\n stringLimit: 512,\n fallbackStringLimit: 256,\n};\n\nconst STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;\nconst STATUS_MODAL_BUTTON_LIMIT = 120;\n\nfunction truncateString(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));\n return `${value.slice(0, maxChars - 12)}...[truncated]`;\n}\n\nfunction truncateStringTail(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));\n return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;\n}\n\nfunction trimStructuredStrings(value: unknown, maxChars: number): unknown {\n if (typeof value === 'string') return truncateString(value, maxChars);\n if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));\n if (!value || typeof value !== 'object') return value;\n return Object.fromEntries(\n Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),\n );\n}\n\nfunction estimateBytes(value: unknown): number {\n try {\n return JSON.stringify(value).length;\n } catch {\n return String(value ?? '').length;\n }\n}\n\nfunction trimMessageForStatus(message: unknown, stringLimit: number): unknown {\n if (!message || typeof message !== 'object') return message;\n return trimStructuredStrings(message, stringLimit);\n}\n\n/**\n * Collapse timestamp / createdAt into receivedAt so downstream consumers\n * only ever need to read a single canonical time field.\n */\nfunction normalizeMessageTime(message: unknown): unknown {\n if (!message || typeof message !== 'object') return message;\n const msg = message as Record<string, unknown>;\n if (msg.receivedAt == null) {\n const fallback = msg.timestamp ?? msg.createdAt;\n if (fallback != null) {\n const ts = typeof fallback === 'string' ? Date.parse(fallback as string) : Number(fallback);\n if (Number.isFinite(ts) && ts > 0) msg.receivedAt = ts;\n }\n }\n return msg;\n}\n\nfunction trimMessagesForStatus(\n messages: unknown[] | null | undefined,\n options: Required<NormalizeActiveChatOptions>,\n): unknown[] {\n if (!options.includeMessages || options.messageLimit <= 0 || options.totalBytesLimit <= 0) return [];\n if (!Array.isArray(messages) || messages.length === 0) return [];\n\n const recent = messages.slice(-options.messageLimit);\n const kept: unknown[] = [];\n let totalBytes = 0;\n\n for (let i = recent.length - 1; i >= 0; i -= 1) {\n let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.stringLimit));\n let size = estimateBytes(normalized);\n\n if (size > options.totalBytesLimit) {\n normalized = normalizeMessageTime(trimMessageForStatus(recent[i], options.fallbackStringLimit));\n size = estimateBytes(normalized);\n }\n\n if (kept.length > 0 && (totalBytes + size) > options.totalBytesLimit) {\n continue;\n }\n\n kept.push(normalized);\n totalBytes += size;\n }\n\n return kept.reverse();\n}\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (normalized === 'waiting_choice') return 'waiting_choice';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n options: NormalizeActiveChatOptions = FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n): T extends null | undefined ? T : SessionActiveChatData {\n if (!activeChat) return activeChat as T extends null | undefined ? T : SessionActiveChatData;\n const resolvedOptions: Required<NormalizeActiveChatOptions> = {\n ...FULL_STATUS_ACTIVE_CHAT_OPTIONS,\n ...options,\n };\n const {\n messages: _messages,\n ...rest\n } = activeChat;\n const normalized: SessionActiveChatData = {\n ...rest,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n activeModal: resolvedOptions.includeActiveModal && activeChat.activeModal ? {\n message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),\n buttons: (activeChat.activeModal.buttons || []).map((button) =>\n truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)\n ),\n } : null,\n inputContent: resolvedOptions.includeInputContent && activeChat.inputContent\n ? truncateString(activeChat.inputContent, 2 * 1024)\n : undefined,\n };\n if (resolvedOptions.includeMessages) {\n normalized.messages = trimMessagesForStatus(activeChat.messages, resolvedOptions) as SessionActiveChatData['messages'];\n }\n return normalized as T extends null | undefined ? T : SessionActiveChatData;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/cli-adapter-types.ts
CHANGED
|
@@ -695,6 +695,12 @@ export class CliStateEngine {
|
|
|
695
695
|
|
|
696
696
|
private applyGenerating(ctx: SettledEvalContext): void {
|
|
697
697
|
const { modal, parsedMessages, lastParsedAssistant, parsedStatus, prevStatus } = ctx;
|
|
698
|
+
const noActiveTurn = !this.currentTurnScope;
|
|
699
|
+
// If the turn is already complete (isWaitingForResponse=false, no scope,
|
|
700
|
+
// no modal), PTY generating blips are just the CLI repainting its prompt.
|
|
701
|
+
// Check BEFORE cancelling the pending idle-finish timer — we don't want
|
|
702
|
+
// to cancel a scheduled idle transition just because the CLI blinked.
|
|
703
|
+
if (!this.isWaitingForResponse && noActiveTurn && !modal) return;
|
|
698
704
|
this.clearIdleFinishCandidate('generating');
|
|
699
705
|
// Cancel any pending grace-window idle transition. We have fresh
|
|
700
706
|
// evidence the provider is still generating; the previous
|
|
@@ -702,7 +708,6 @@ export class CliStateEngine {
|
|
|
702
708
|
this.cancelPendingIdleFinish('generating_signal_returned');
|
|
703
709
|
const snap = this.transport.getSnapshot();
|
|
704
710
|
const effectiveScreenText = snap.screenText || snap.accumulatedBuffer;
|
|
705
|
-
const noActiveTurn = !this.currentTurnScope;
|
|
706
711
|
const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText);
|
|
707
712
|
const parsedShowsLiveProgress = parsedStatus === 'generating' && !!lastParsedAssistant;
|
|
708
713
|
if (prevStatus === 'idle' && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveProgress) return;
|
|
@@ -1009,7 +1014,44 @@ export class CliStateEngine {
|
|
|
1009
1014
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status.trim() : '';
|
|
1010
1015
|
if (parsedStatus !== 'idle') return true;
|
|
1011
1016
|
if (parsed?.activeModal || parsed?.modal) return true;
|
|
1012
|
-
|
|
1017
|
+
// For providers that own their history externally (e.g. SpecCliAdapter
|
|
1018
|
+
// reading native JSONL), parsed.messages only reflects PTY-visible
|
|
1019
|
+
// content which may include previous-turn assistant messages. Gate on
|
|
1020
|
+
// whether the *current turn* has a final assistant: find the last user
|
|
1021
|
+
// in parsed.messages, and check there is an assistant after it.
|
|
1022
|
+
// If no user found at all (e.g. PTY hid it), defer — the native-history
|
|
1023
|
+
// completion gate in cli-provider-instance will confirm via JSONL read.
|
|
1024
|
+
// For providers that own their history externally (e.g. SpecCliAdapter
|
|
1025
|
+
// reading native JSONL), parsed.messages may include previous-turn
|
|
1026
|
+
// assistant messages. Only an assistant that appears *after* the last
|
|
1027
|
+
// user message counts as evidence the current turn finished.
|
|
1028
|
+
const messages: any[] = Array.isArray(parsed?.messages) ? parsed.messages : [];
|
|
1029
|
+
let lastUserIdx = -1;
|
|
1030
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1031
|
+
if (messages[i]?.role === 'user') { lastUserIdx = i; break; }
|
|
1032
|
+
}
|
|
1033
|
+
if (lastUserIdx < 0) {
|
|
1034
|
+
// No user message visible in PTY parser. Two sub-cases:
|
|
1035
|
+
// (a) messages is non-empty — some content was parsed but no user
|
|
1036
|
+
// line was found. Use the any-assistant fallback.
|
|
1037
|
+
// (b) messages is empty — the provider owns history externally
|
|
1038
|
+
// (e.g. SpecCliAdapter always returns []) and PTY gives us
|
|
1039
|
+
// nothing to judge. Do NOT defer here: deferring with empty
|
|
1040
|
+
// messages causes an infinite reschedule loop because
|
|
1041
|
+
// parsedStatusHasFinalStandardAssistantMessage also returns
|
|
1042
|
+
// false on empty input. Let cli-provider-instance's native-
|
|
1043
|
+
// history gate handle this case.
|
|
1044
|
+
if (messages.length === 0) return false;
|
|
1045
|
+
return !this.parsedStatusHasFinalStandardAssistantMessage(parsed);
|
|
1046
|
+
}
|
|
1047
|
+
// Check for a final standard assistant after the last user
|
|
1048
|
+
const hasCurrentTurnAssistant = messages.slice(lastUserIdx + 1).some((m: any) => {
|
|
1049
|
+
if (!m || m.role !== 'assistant') return false;
|
|
1050
|
+
if (typeof m.content !== 'string' || !m.content.trim()) return false;
|
|
1051
|
+
const kind = typeof m.kind === 'string' && m.kind.trim() ? m.kind.trim() : 'standard';
|
|
1052
|
+
return kind === 'standard' && m.meta?.streaming !== true;
|
|
1053
|
+
});
|
|
1054
|
+
return !hasCurrentTurnAssistant;
|
|
1013
1055
|
}
|
|
1014
1056
|
|
|
1015
1057
|
private rescheduleTranscriptFinishCheck(reason: string): void {
|
package/src/mesh/contracts.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - Session identifier keys diverged across stores: targetSessionId /
|
|
17
17
|
* assignedSessionId / instanceId / runtimeSessionId / providerSessionId.
|
|
18
18
|
* resolveEventSessionId() tried four fallbacks per call.
|
|
19
|
-
* - No protocol version on the JSONL ledger or
|
|
19
|
+
* - No protocol version on the JSONL ledger or MeshRuntimeStore. Schema
|
|
20
20
|
* evolutions had no guard rail.
|
|
21
21
|
* - mesh_reconcile_ledger existed as a routine recovery tool, not as
|
|
22
22
|
* an incident-response escape hatch. That itself signals the routing
|
|
@@ -70,7 +70,7 @@ export interface BuildMeshActiveWorkOptions {
|
|
|
70
70
|
queue?: MeshWorkQueueEntry[];
|
|
71
71
|
ledgerEntries?: MeshLedgerEntry[];
|
|
72
72
|
/**
|
|
73
|
-
* Active direct dispatches from
|
|
73
|
+
* Active direct dispatches from MeshRuntimeStore. When provided, these are used instead of
|
|
74
74
|
* scanning ledger entries for direct dispatches — eliminates the O(n_ledger) scan.
|
|
75
75
|
* Falls back to ledger scanning when not provided.
|
|
76
76
|
*/
|
|
@@ -224,9 +224,9 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
// When
|
|
228
|
-
// ALSO scan ledger for remote dispatches (P2P) whose taskIds are not in
|
|
229
|
-
// are never written to the local
|
|
227
|
+
// When MeshRuntimeStore direct dispatches are provided, use them for LOCAL dispatches (O(1) indexed).
|
|
228
|
+
// ALSO scan ledger for remote dispatches (P2P) whose taskIds are not in MeshRuntimeStore — these
|
|
229
|
+
// are never written to the local MeshRuntimeStore since they're dispatched from a remote daemon.
|
|
230
230
|
if (opts.directDispatches !== undefined) {
|
|
231
231
|
const dbTaskIds = new Set(opts.directDispatches.map(d => d.taskId));
|
|
232
232
|
for (const dispatch of opts.directDispatches) {
|
|
@@ -274,13 +274,13 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
274
274
|
}
|
|
275
275
|
records.push(record);
|
|
276
276
|
}
|
|
277
|
-
// Also scan ledger for remote dispatches (via p2p_direct) whose taskIds are NOT in
|
|
278
|
-
// Remote daemons write their own local
|
|
277
|
+
// Also scan ledger for remote dispatches (via p2p_direct) whose taskIds are NOT in MeshRuntimeStore.
|
|
278
|
+
// Remote daemons write their own local MeshRuntimeStore; this coordinator's MeshRuntimeStore only has local dispatches.
|
|
279
279
|
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
280
280
|
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
281
281
|
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
282
282
|
const taskId = directDispatchTaskId(dispatch);
|
|
283
|
-
if (dbTaskIds.has(taskId)) continue; // already covered by
|
|
283
|
+
if (dbTaskIds.has(taskId)) continue; // already covered by MeshRuntimeStore path above
|
|
284
284
|
const terminal = terminals
|
|
285
285
|
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
286
286
|
.find(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
@@ -329,7 +329,7 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
329
329
|
records.push(record);
|
|
330
330
|
}
|
|
331
331
|
} else {
|
|
332
|
-
// Full ledger scan: no
|
|
332
|
+
// Full ledger scan: no MeshRuntimeStore direct dispatches available (standalone mode or empty).
|
|
333
333
|
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
334
334
|
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
335
335
|
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { LOG } from '../logging/logger.js';
|
|
|
8
8
|
import { appendLedgerEntry, buildTaskCompletionEvidence, getLedgerDir, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
|
|
9
9
|
import type { MeshLedgerKind, SessionRecoveryContext } from './mesh-ledger.js';
|
|
10
10
|
import { buildMeshNodeCapabilityTags, claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, getActiveDirectDispatches } from './mesh-work-queue.js';
|
|
11
|
-
import {
|
|
11
|
+
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
12
12
|
import { fastForwardMeshNode } from './mesh-fast-forward.js';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -49,7 +49,7 @@ export function __resetIdleAutoFastForwardForTests(): void {
|
|
|
49
49
|
|
|
50
50
|
function sweepExpiredRemoteIdleSessions(): void {
|
|
51
51
|
try {
|
|
52
|
-
|
|
52
|
+
MeshRuntimeStore.getInstance().pruneExpiredRemoteIdleSessions();
|
|
53
53
|
} catch { /* best-effort */ }
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -438,7 +438,7 @@ const RECENT_COMPLETION_FINGERPRINT_TTL_MS = 10 * 60 * 1000;
|
|
|
438
438
|
|
|
439
439
|
function hasFingerprintSeen(fingerprint: string): boolean {
|
|
440
440
|
try {
|
|
441
|
-
return
|
|
441
|
+
return MeshRuntimeStore.getInstance().hasCompletionFingerprint(fingerprint);
|
|
442
442
|
} catch {
|
|
443
443
|
return false;
|
|
444
444
|
}
|
|
@@ -446,7 +446,7 @@ function hasFingerprintSeen(fingerprint: string): boolean {
|
|
|
446
446
|
|
|
447
447
|
function recordFingerprintSeen(fingerprint: string): void {
|
|
448
448
|
try {
|
|
449
|
-
const db =
|
|
449
|
+
const db = MeshRuntimeStore.getInstance();
|
|
450
450
|
db.recordCompletionFingerprint(fingerprint, RECENT_COMPLETION_FINGERPRINT_TTL_MS);
|
|
451
451
|
db.sweepExpiredFingerprints();
|
|
452
452
|
} catch { /* best-effort; duplicate events are preferable to a crash */ }
|
|
@@ -550,7 +550,7 @@ function findRecentTerminalLedgerEvidence(args: {
|
|
|
550
550
|
if (!args.sessionId && !args.nodeId) return null;
|
|
551
551
|
// Tail-limit: 200 entries gives a wide enough window to catch terminal events for active
|
|
552
552
|
// sessions while avoiding a full O(n) scan. If a terminal is older than 200 entries,
|
|
553
|
-
// the
|
|
553
|
+
// the MeshRuntimeStore fingerprint dedup will still block duplicate processing downstream.
|
|
554
554
|
const entries = readLedgerEntries(args.meshId, { tail: 200 });
|
|
555
555
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
556
556
|
const entry = entries[i];
|
|
@@ -1280,7 +1280,7 @@ export async function triggerMeshQueue(components: DaemonComponents, meshId: str
|
|
|
1280
1280
|
// Also check known idle remote sessions
|
|
1281
1281
|
let remoteSessions: Array<{ nodeId: string; sessionId: string; providerType: string }> = [];
|
|
1282
1282
|
try {
|
|
1283
|
-
remoteSessions =
|
|
1283
|
+
remoteSessions = MeshRuntimeStore.getInstance().getRemoteIdleSessions();
|
|
1284
1284
|
} catch { /* best-effort */ }
|
|
1285
1285
|
|
|
1286
1286
|
for (const idle of remoteSessions) {
|
|
@@ -1291,7 +1291,7 @@ export async function triggerMeshQueue(components: DaemonComponents, meshId: str
|
|
|
1291
1291
|
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
1292
1292
|
if (assigned) {
|
|
1293
1293
|
try {
|
|
1294
|
-
|
|
1294
|
+
MeshRuntimeStore.getInstance().deleteRemoteIdleSession(idle.nodeId, idle.sessionId);
|
|
1295
1295
|
} catch { /* best-effort */ }
|
|
1296
1296
|
}
|
|
1297
1297
|
}
|
|
@@ -1534,7 +1534,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1534
1534
|
if (intentionalCleanupStop) {
|
|
1535
1535
|
if (eventSessionId && eventNodeId) {
|
|
1536
1536
|
try {
|
|
1537
|
-
|
|
1537
|
+
MeshRuntimeStore.getInstance().deleteRemoteIdleSession(eventNodeId, eventSessionId);
|
|
1538
1538
|
} catch { /* best-effort */ }
|
|
1539
1539
|
}
|
|
1540
1540
|
LOG.info('MeshEvents', `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || '(unknown session)'}`);
|
|
@@ -1718,14 +1718,14 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1718
1718
|
if (sessionId && nodeId && providerType) {
|
|
1719
1719
|
sweepExpiredRemoteIdleSessions();
|
|
1720
1720
|
try {
|
|
1721
|
-
|
|
1721
|
+
MeshRuntimeStore.getInstance().setRemoteIdleSession(nodeId, sessionId, providerType, Date.now() + REMOTE_IDLE_SESSION_TTL_MS);
|
|
1722
1722
|
} catch { /* best-effort */ }
|
|
1723
1723
|
setImmediate(() => {
|
|
1724
1724
|
maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType })
|
|
1725
1725
|
.finally(() => {
|
|
1726
1726
|
try {
|
|
1727
1727
|
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
1728
|
-
if (assigned)
|
|
1728
|
+
if (assigned) MeshRuntimeStore.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1729
1729
|
} catch (e: any) {
|
|
1730
1730
|
LOG.warn('MeshQueue', `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
|
|
1731
1731
|
}
|
|
@@ -1737,7 +1737,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1737
1737
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1738
1738
|
if (sessionId && nodeId) {
|
|
1739
1739
|
try {
|
|
1740
|
-
|
|
1740
|
+
MeshRuntimeStore.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1741
1741
|
} catch { /* best-effort */ }
|
|
1742
1742
|
}
|
|
1743
1743
|
if (sessionId) {
|
|
@@ -1748,7 +1748,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1748
1748
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1749
1749
|
if (sessionId && nodeId) {
|
|
1750
1750
|
try {
|
|
1751
|
-
|
|
1751
|
+
MeshRuntimeStore.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1752
1752
|
} catch { /* best-effort */ }
|
|
1753
1753
|
}
|
|
1754
1754
|
if (sessionId) {
|