@dougbots/avenor-core 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +59 -15
- package/dist/index.js +356 -108
- package/dist/postinstall.d.ts +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,7 @@ interface WorkflowControllerCreateParams {
|
|
|
145
145
|
max_inflight: number;
|
|
146
146
|
}
|
|
147
147
|
interface MessageSendParams {
|
|
148
|
+
expected_sentinel_file?: string;
|
|
148
149
|
to_run_id: string;
|
|
149
150
|
message_id: string;
|
|
150
151
|
generation: string;
|
|
@@ -317,7 +318,6 @@ declare class Client {
|
|
|
317
318
|
events(options?: SubscribeOptions): AsyncIterableIterator<Event>;
|
|
318
319
|
history(options: HistoryOptions): Promise<HistoryResult>;
|
|
319
320
|
status(runtimeId?: string): Promise<Record<string, unknown>>;
|
|
320
|
-
result(runtimeId?: string): Promise<Record<string, unknown>>;
|
|
321
321
|
list(): Promise<Array<Record<string, unknown>>>;
|
|
322
322
|
spawn(params: SpawnParams): Promise<SpawnResult>;
|
|
323
323
|
shutdown(mode: string): Promise<void>;
|
|
@@ -356,7 +356,9 @@ declare class Client {
|
|
|
356
356
|
messagePreflight(): Promise<MessagePreflightResult>;
|
|
357
357
|
resume(params: ResumeParams): Promise<ResumeResult>;
|
|
358
358
|
sessionRename(sessionId: string, name: string): Promise<SessionRenameResult>;
|
|
359
|
-
sessionLast(runtimeId: string): Promise<SessionLastResult>;
|
|
359
|
+
sessionLast(runtimeId: string, expectedSentinelFile?: string): Promise<SessionLastResult>;
|
|
360
|
+
/** The complete terminal reply for a runtime, with the sentinel that turn currently owns. */
|
|
361
|
+
result(runtimeId: string): Promise<Record<string, unknown>>;
|
|
360
362
|
sessionLastSet(runtimeId: string, finalOutput: string, stopReason: string, truncated: boolean): Promise<void>;
|
|
361
363
|
workflowCancel(workflowId: string, actor: string, reason: string): Promise<Record<string, unknown>>;
|
|
362
364
|
isClosed(): boolean;
|
|
@@ -523,7 +525,12 @@ interface RunSnapshot {
|
|
|
523
525
|
}
|
|
524
526
|
declare function createRunSnapshot(limits?: Partial<RunReducerLimits>): RunSnapshot;
|
|
525
527
|
declare function reduceRunSnapshot(current: RunSnapshot, input: unknown, limits?: Partial<RunReducerLimits>): RunSnapshot;
|
|
526
|
-
|
|
528
|
+
/**
|
|
529
|
+
* A snapshot as the inspect surface and an observer publish it: the reducer's
|
|
530
|
+
* dedupe bookkeeping stays internal to `RunReducer`.
|
|
531
|
+
*/
|
|
532
|
+
type RunSnapshotView = Omit<RunSnapshot, '_seen_seq_by_scope' | '_recent_legacy_keys' | '_last_semantic_fingerprint' | '_last_source_event'>;
|
|
533
|
+
type RunSnapshotSeed = Partial<RunSnapshotView>;
|
|
527
534
|
declare class RunReducer {
|
|
528
535
|
private state;
|
|
529
536
|
constructor(limits?: Partial<RunReducerLimits>, initial?: RunSnapshotSeed);
|
|
@@ -625,20 +632,46 @@ declare class Supervisor {
|
|
|
625
632
|
registerResumed(run: RunInfo): void;
|
|
626
633
|
/** The one place a run's ID and label are registered, for spawn and for resume. */
|
|
627
634
|
private register;
|
|
628
|
-
/**
|
|
635
|
+
/** The stored runs that carry a broker runtime ID. */
|
|
636
|
+
getRunsByRuntimeId(runtimeId: string): RunInfo[];
|
|
637
|
+
/**
|
|
638
|
+
* Match a stored run by its broker runtime ID, returning the entry only
|
|
639
|
+
* when exactly one run carries the id. Several registered turns can share
|
|
640
|
+
* a runtime (a warm resume, or a restart that re-minted one while the
|
|
641
|
+
* pre-restart entry stays registered); the current turn is identified by
|
|
642
|
+
* the sentinel the runtime holds, so a shared id is disambiguated with
|
|
643
|
+
* getRunByRuntimeTurn rather than by whichever entry was inserted last.
|
|
644
|
+
*/
|
|
629
645
|
getRunByRuntimeId(runtimeId: string): RunInfo | undefined;
|
|
646
|
+
/**
|
|
647
|
+
* Return the entry for (runtimeId, sentinelPath) only when exactly one
|
|
648
|
+
* stored run carries both. A sentinel no entry carries, or one carried by
|
|
649
|
+
* several entries, is claimed by no turn.
|
|
650
|
+
*/
|
|
651
|
+
getRunByRuntimeTurn(runtimeId: string, sentinelPath: string): RunInfo | undefined;
|
|
630
652
|
/**
|
|
631
653
|
* The live singleton when it owns supervisorId, null otherwise. Single
|
|
632
654
|
* source of truth for the ownership check, so callers cannot observe
|
|
633
655
|
* isCurrentInstance true while currentInstance is null.
|
|
634
656
|
*/
|
|
657
|
+
/**
|
|
658
|
+
* Build an instance that owns socketPath without spawning a supervisor
|
|
659
|
+
* process, and adopt it as the live singleton. `start` is the production
|
|
660
|
+
* writer of both; this exists so a test can drive the in-process plane
|
|
661
|
+
* without forging private state, and hands back the previous singleton so
|
|
662
|
+
* the caller can restore it.
|
|
663
|
+
*/
|
|
664
|
+
static adoptTestInstance(binaryPath: string, socketPath: string): {
|
|
665
|
+
supervisor: Supervisor;
|
|
666
|
+
restore: () => void;
|
|
667
|
+
};
|
|
635
668
|
static getInstance(supervisorId: string): Supervisor | null;
|
|
636
669
|
static isCurrentInstance(supervisorId: string): boolean;
|
|
637
670
|
static currentInstance(): Supervisor | null;
|
|
638
671
|
/** Iterate stored runs without exposing the internal map. */
|
|
639
672
|
forEachRun(callback: (info: RunInfo) => void): void;
|
|
640
673
|
getClient(): Client;
|
|
641
|
-
spawn(params: SpawnParams, runId?:
|
|
674
|
+
spawn(params: SpawnParams, runId?: string, metadata?: SpawnMetadata): Promise<RunInfo>;
|
|
642
675
|
close(options?: {
|
|
643
676
|
skipShutdown?: boolean;
|
|
644
677
|
}): Promise<void>;
|
|
@@ -749,6 +782,7 @@ interface StatusResult {
|
|
|
749
782
|
latest_seq?: number;
|
|
750
783
|
final_output?: string;
|
|
751
784
|
final_output_truncated?: boolean;
|
|
785
|
+
runtime_output_available?: boolean;
|
|
752
786
|
}
|
|
753
787
|
declare const statusTool: (args: StatusToolArgs) => Promise<StatusResult | StatusListResult>;
|
|
754
788
|
//#endregion
|
|
@@ -768,6 +802,11 @@ interface EventsToolResult {
|
|
|
768
802
|
[key: string]: unknown;
|
|
769
803
|
}>;
|
|
770
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Stream the parsed records of one NDJSON event log. Malformed lines are
|
|
807
|
+
* dropped; an unreadable log rejects, so a caller that cannot read the log
|
|
808
|
+
* never sees a silent empty tail.
|
|
809
|
+
*/
|
|
771
810
|
declare const eventsTool: (args: EventsToolArgs) => Promise<EventsToolResult>;
|
|
772
811
|
//#endregion
|
|
773
812
|
//#region src/tools/inspect.d.ts
|
|
@@ -781,7 +820,7 @@ interface InspectResult {
|
|
|
781
820
|
run_id: string;
|
|
782
821
|
label: string;
|
|
783
822
|
status: StatusResult;
|
|
784
|
-
snapshot:
|
|
823
|
+
snapshot: RunSnapshotView;
|
|
785
824
|
transcript: RunSnapshot['transcript'];
|
|
786
825
|
tools: RunSnapshot['tools'];
|
|
787
826
|
live_tools: RunSnapshot['live_tools'];
|
|
@@ -967,9 +1006,9 @@ interface MessageProvider {
|
|
|
967
1006
|
surface(): MessageSurface;
|
|
968
1007
|
role(): string;
|
|
969
1008
|
resolve(ref: string): Promise<Resolution>;
|
|
970
|
-
state(runId: string): Promise<RunFacts>;
|
|
971
|
-
tell(targetRunId: string, message: string): Promise<ProviderResult>;
|
|
972
|
-
ask(targetRunId: string, message: string, timeoutMs?: number, signal?: AbortSignal): Promise<ProviderResult>;
|
|
1009
|
+
state(runId: string, publicRunId?: string): Promise<RunFacts>;
|
|
1010
|
+
tell(targetRunId: string, message: string, publicRunId?: string): Promise<ProviderResult>;
|
|
1011
|
+
ask(targetRunId: string, message: string, timeoutMs?: number, signal?: AbortSignal, publicRunId?: string): Promise<ProviderResult>;
|
|
973
1012
|
reply(inboundAskId: string, message: string, askerRunId: string): Promise<ProviderResult>;
|
|
974
1013
|
withdraw(messageId: string): Promise<ProviderResult>;
|
|
975
1014
|
controlReply?(target: string, payload: unknown): Promise<ProviderResult>;
|
|
@@ -984,7 +1023,7 @@ interface MessageProvider {
|
|
|
984
1023
|
sessionId?: string;
|
|
985
1024
|
name: string;
|
|
986
1025
|
}): Promise<ProviderResult>;
|
|
987
|
-
last(runId: string): Promise<ProviderResult>;
|
|
1026
|
+
last(runId: string, publicRunId?: string): Promise<ProviderResult>;
|
|
988
1027
|
peers(): Promise<PeersProviderResult>;
|
|
989
1028
|
/** Host surfaces that own a turn loop publish their own finished turn. */
|
|
990
1029
|
recordLastTurn?(text: string, stopReason: string, truncated: boolean): Promise<void>;
|
|
@@ -1098,6 +1137,7 @@ interface MessageClient {
|
|
|
1098
1137
|
generation: string;
|
|
1099
1138
|
body: string;
|
|
1100
1139
|
no_reply?: boolean;
|
|
1140
|
+
expected_sentinel_file?: string;
|
|
1101
1141
|
}): Promise<MessageSendResult>;
|
|
1102
1142
|
messageWait(params: {
|
|
1103
1143
|
message_id: string;
|
|
@@ -1127,7 +1167,7 @@ interface MessageClient {
|
|
|
1127
1167
|
on_event?: string;
|
|
1128
1168
|
}): Promise<ResumeResult>;
|
|
1129
1169
|
sessionRename(sessionId: string, name: string): Promise<SessionRenameResult>;
|
|
1130
|
-
sessionLast(runtimeId: string): Promise<SessionLastResult>;
|
|
1170
|
+
sessionLast(runtimeId: string, expectedSentinelFile?: string): Promise<SessionLastResult>;
|
|
1131
1171
|
sessionLastSet(runtimeId: string, finalOutput: string, stopReason: string, truncated: boolean): Promise<void>;
|
|
1132
1172
|
status(runtimeId?: string): Promise<Record<string, unknown>>;
|
|
1133
1173
|
}
|
|
@@ -1200,10 +1240,14 @@ declare function createChildMessageProvider(options: ChildProviderOptions): Mess
|
|
|
1200
1240
|
//#region src/tools/peers.d.ts
|
|
1201
1241
|
interface PeersToolArgs extends PeersInput {}
|
|
1202
1242
|
type PeersResult = PeersRouterResult;
|
|
1203
|
-
/** The one avenor_peers handler; it resolves and reads through the bound provider.
|
|
1204
|
-
|
|
1243
|
+
/** The one avenor_peers handler; it resolves and reads through the bound provider.
|
|
1244
|
+
*
|
|
1245
|
+
* Admission belongs to the shared router for every surface, so the argument is
|
|
1246
|
+
* the untyped JSON a tool call carries and `executePeers` validates it first —
|
|
1247
|
+
* the same shape `messageTool` takes. */
|
|
1248
|
+
declare function peersTool(args: unknown, provider: MessageProvider): Promise<PeersResult>;
|
|
1205
1249
|
/** Bind the peers handler to one provider. */
|
|
1206
|
-
declare function createPeersTool(provider: MessageProvider): (args:
|
|
1250
|
+
declare function createPeersTool(provider: MessageProvider): (args: unknown) => Promise<PeersResult>;
|
|
1207
1251
|
//#endregion
|
|
1208
1252
|
//#region src/tools/kill.d.ts
|
|
1209
1253
|
interface KillToolArgs {
|
|
@@ -1330,4 +1374,4 @@ type WorkflowCancelResult = Record<string, unknown>;
|
|
|
1330
1374
|
declare function createWorkflowCancelTool(getSupervisorClient: GetSupervisorClient): (args: WorkflowCancelArgs) => Promise<WorkflowCancelResult>;
|
|
1331
1375
|
declare const workflowCancelTool: (args: WorkflowCancelArgs) => Promise<WorkflowCancelResult>;
|
|
1332
1376
|
//#endregion
|
|
1333
|
-
export { type AskClaims, type BrokerReceiveArgs, type BrokerReceiveResult, type ChildBroker, type Client, ClosedSocketError, DEFAULT_RUN_REDUCER_LIMITS, type Event, type ExecutionIdentity, ForeignPeerError, type HistoryOptions, type HistoryResult, type InboundAsk, type InspectResult, type KillResult, type KillToolArgs, MAX_FINAL_OUTPUT_RUNES, MAX_WAIT_MS, MESSAGE_OUTCOMES, type MessageCallerKind, type MessageClient, type MessageInput, type MessageOutcome, type MessageProvider, type MessageSurface, type NormalizedRunEvent, type ObserveRunOptions, type OwnerAttachOptions, OwnerAuthError, OwnerRecordMissingError, type PeerRecord, type PeersInput, type PeersProviderResult, type PeersResult, type PeersRouterResult, type PeersToolArgs, type PendingAsk, type ProviderResult, type Resolution, type ResultResult, type ResultToolArgs, RpcError, type RunChunkRole, type RunFacts, type RunIdentity, type RunInfo, type RunObserver, type RunObserverClient, type RunPermissionOption, type RunPermissionSummary, RunReducer, type RunReducerLimits, type RunSnapshot, type RunSnapshotSeed, type RunToolState, type RunToolSummary, type RunTranscriptEntry, SUPERVISOR_RUN_ID, type SpawnMetadata, type SpawnParams, type SpawnResult, type SpawnSelectionInput, type SpawnToolArgs, type SpawnToolResult, StaleOwnerRecordError, type StatusListResult, type StatusResult, type StatusToolArgs, type StatusView, type SubscribeOptions, Supervisor, type SupervisorClientOptions, type SupervisorOptions, THINKING_LEVELS, type ThinkingLevel, type ThinkingOutcome, ToolError, type WorkflowCancelArgs, type WorkflowCancelResult, type WorkflowCompleteParams, type WorkflowCompleteResult, type WorkflowCompleteToolArgs, type WorkflowControllerCreateParams, type WorkflowControllerStatusResult, type WorkflowControllerStatusToolArgs, type WorkflowEventsResult, type WorkflowEventsToolArgs, type WorkflowGateOperation, type WorkflowGateParams, type WorkflowGateResult, type WorkflowGateToolArgs, type WorkflowHeartbeatParams, type WorkflowInspectResult, type WorkflowInspectToolArgs, type WorkflowReadyResult, type WorkflowReadyToolArgs, type WorkflowStatusResult, type WorkflowStatusToolArgs, type WorkflowWaitResult, type WorkflowWaitToolArgs, answerPermissionTool, assertMessageInput, assertPeersInput, attachOwner, boundFinalOutput, brokerReceiveTool, createChildMessageProvider, createHostMessageProvider, createMessageTool, createPeersTool, createRunSnapshot, createWorkflowCancelTool, createWorkflowCompleteTool, createWorkflowControllerStatusTool, createWorkflowEventsTool, createWorkflowGateTool, createWorkflowInspectTool, createWorkflowReadyTool, createWorkflowStatusTool, createWorkflowWaitTool, dial, evaluateThinkingPolicy, eventsTool, executeMessage, executePeers, extractEventText, extractReplyMessage, extractTurnText, findAvenorBinary, getInstallDir, getSupervisorClient, getVersion, inspectTool, installAskClaims, installerBinaryPath, isClosedSocketError, isGenerationMismatchError, isThinkingLevel, killTool, messageTool, newMsgID, normalizeAskTimeoutMs, normalizeRunEvent, normalizeTurnStopReason, observeRun, peersDetails, peersTool, quote, quoteList, reduceRunSnapshot, renderAskFailure, renderLast, renderOutcome, renderPeerList, repair, resultOutput, resultTool, sanitize, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, validateThinking, validateThinkingForBackend, validateThinkingForBackendResume, workflowCancelTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool };
|
|
1377
|
+
export { type AskClaims, type BrokerReceiveArgs, type BrokerReceiveResult, type ChildBroker, type Client, ClosedSocketError, DEFAULT_RUN_REDUCER_LIMITS, type Event, type ExecutionIdentity, ForeignPeerError, type HistoryOptions, type HistoryResult, type InboundAsk, type InspectResult, type KillResult, type KillToolArgs, MAX_FINAL_OUTPUT_RUNES, MAX_WAIT_MS, MESSAGE_OUTCOMES, type MessageCallerKind, type MessageClient, type MessageInput, type MessageOutcome, type MessageProvider, type MessageSurface, type NormalizedRunEvent, type ObserveRunOptions, type OwnerAttachOptions, OwnerAuthError, OwnerRecordMissingError, type PeerRecord, type PeersInput, type PeersProviderResult, type PeersResult, type PeersRouterResult, type PeersToolArgs, type PendingAsk, type ProviderResult, type Resolution, type ResultResult, type ResultToolArgs, RpcError, type RunChunkRole, type RunFacts, type RunIdentity, type RunInfo, type RunObserver, type RunObserverClient, type RunPermissionOption, type RunPermissionSummary, RunReducer, type RunReducerLimits, type RunSnapshot, type RunSnapshotSeed, type RunSnapshotView, type RunToolState, type RunToolSummary, type RunTranscriptEntry, SUPERVISOR_RUN_ID, type SpawnMetadata, type SpawnParams, type SpawnResult, type SpawnSelectionInput, type SpawnToolArgs, type SpawnToolResult, StaleOwnerRecordError, type StatusListResult, type StatusResult, type StatusToolArgs, type StatusView, type SubscribeOptions, Supervisor, type SupervisorClientOptions, type SupervisorOptions, THINKING_LEVELS, type ThinkingLevel, type ThinkingOutcome, ToolError, type WorkflowCancelArgs, type WorkflowCancelResult, type WorkflowCompleteParams, type WorkflowCompleteResult, type WorkflowCompleteToolArgs, type WorkflowControllerCreateParams, type WorkflowControllerStatusResult, type WorkflowControllerStatusToolArgs, type WorkflowEventsResult, type WorkflowEventsToolArgs, type WorkflowGateOperation, type WorkflowGateParams, type WorkflowGateResult, type WorkflowGateToolArgs, type WorkflowHeartbeatParams, type WorkflowInspectResult, type WorkflowInspectToolArgs, type WorkflowReadyResult, type WorkflowReadyToolArgs, type WorkflowStatusResult, type WorkflowStatusToolArgs, type WorkflowWaitResult, type WorkflowWaitToolArgs, answerPermissionTool, assertMessageInput, assertPeersInput, attachOwner, boundFinalOutput, brokerReceiveTool, createChildMessageProvider, createHostMessageProvider, createMessageTool, createPeersTool, createRunSnapshot, createWorkflowCancelTool, createWorkflowCompleteTool, createWorkflowControllerStatusTool, createWorkflowEventsTool, createWorkflowGateTool, createWorkflowInspectTool, createWorkflowReadyTool, createWorkflowStatusTool, createWorkflowWaitTool, dial, evaluateThinkingPolicy, eventsTool, executeMessage, executePeers, extractEventText, extractReplyMessage, extractTurnText, findAvenorBinary, getInstallDir, getSupervisorClient, getVersion, inspectTool, installAskClaims, installerBinaryPath, isClosedSocketError, isGenerationMismatchError, isThinkingLevel, killTool, messageTool, newMsgID, normalizeAskTimeoutMs, normalizeRunEvent, normalizeTurnStopReason, observeRun, peersDetails, peersTool, quote, quoteList, reduceRunSnapshot, renderAskFailure, renderLast, renderOutcome, renderPeerList, repair, resultOutput, resultTool, sanitize, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, validateThinking, validateThinkingForBackend, validateThinkingForBackendResume, workflowCancelTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool };
|
package/dist/index.js
CHANGED
|
@@ -368,10 +368,6 @@ var Client = class {
|
|
|
368
368
|
const params = runtimeId ? { runtime_id: runtimeId } : void 0;
|
|
369
369
|
return this.call("status", params);
|
|
370
370
|
}
|
|
371
|
-
async result(runtimeId) {
|
|
372
|
-
const params = runtimeId ? { runtime_id: runtimeId } : void 0;
|
|
373
|
-
return this.call("result", params);
|
|
374
|
-
}
|
|
375
371
|
async list() {
|
|
376
372
|
return this.call("list");
|
|
377
373
|
}
|
|
@@ -504,7 +500,7 @@ var Client = class {
|
|
|
504
500
|
});
|
|
505
501
|
}
|
|
506
502
|
async messageSend(params) {
|
|
507
|
-
return this.call("message_send", params);
|
|
503
|
+
return this.call(params.expected_sentinel_file ? "message_send_turn" : "message_send", params);
|
|
508
504
|
}
|
|
509
505
|
async messageWait(params) {
|
|
510
506
|
const requested = params.timeout_ms !== void 0 && params.timeout_ms > 0 ? params.timeout_ms : MESSAGE_MAX_WAIT_MS;
|
|
@@ -535,8 +531,15 @@ var Client = class {
|
|
|
535
531
|
name
|
|
536
532
|
});
|
|
537
533
|
}
|
|
538
|
-
async sessionLast(runtimeId) {
|
|
539
|
-
return this.call("session_last", {
|
|
534
|
+
async sessionLast(runtimeId, expectedSentinelFile) {
|
|
535
|
+
return this.call(expectedSentinelFile ? "session_last_turn" : "session_last", {
|
|
536
|
+
runtime_id: runtimeId,
|
|
537
|
+
...expectedSentinelFile ? { expected_sentinel_file: expectedSentinelFile } : {}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
/** The complete terminal reply for a runtime, with the sentinel that turn currently owns. */
|
|
541
|
+
async result(runtimeId) {
|
|
542
|
+
return this.call("result", { runtime_id: runtimeId });
|
|
540
543
|
}
|
|
541
544
|
async sessionLastSet(runtimeId, finalOutput, stopReason, truncated) {
|
|
542
545
|
await this.call("session_last_set", {
|
|
@@ -2149,16 +2152,56 @@ var Supervisor = class Supervisor {
|
|
|
2149
2152
|
this.runs.set(run.runId, run);
|
|
2150
2153
|
this.aliases.set(run.label, run);
|
|
2151
2154
|
}
|
|
2152
|
-
/**
|
|
2155
|
+
/** The stored runs that carry a broker runtime ID. */
|
|
2156
|
+
getRunsByRuntimeId(runtimeId) {
|
|
2157
|
+
if (!runtimeId) return [];
|
|
2158
|
+
return [...this.runs.values()].filter((info) => info.runtimeId === runtimeId);
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Match a stored run by its broker runtime ID, returning the entry only
|
|
2162
|
+
* when exactly one run carries the id. Several registered turns can share
|
|
2163
|
+
* a runtime (a warm resume, or a restart that re-minted one while the
|
|
2164
|
+
* pre-restart entry stays registered); the current turn is identified by
|
|
2165
|
+
* the sentinel the runtime holds, so a shared id is disambiguated with
|
|
2166
|
+
* getRunByRuntimeTurn rather than by whichever entry was inserted last.
|
|
2167
|
+
*/
|
|
2153
2168
|
getRunByRuntimeId(runtimeId) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2169
|
+
const matches = this.getRunsByRuntimeId(runtimeId);
|
|
2170
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Return the entry for (runtimeId, sentinelPath) only when exactly one
|
|
2174
|
+
* stored run carries both. A sentinel no entry carries, or one carried by
|
|
2175
|
+
* several entries, is claimed by no turn.
|
|
2176
|
+
*/
|
|
2177
|
+
getRunByRuntimeTurn(runtimeId, sentinelPath) {
|
|
2178
|
+
if (!runtimeId || !sentinelPath) return void 0;
|
|
2179
|
+
const matches = this.getRunsByRuntimeId(runtimeId).filter((info) => info.sentinelPath === sentinelPath);
|
|
2180
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2156
2181
|
}
|
|
2157
2182
|
/**
|
|
2158
2183
|
* The live singleton when it owns supervisorId, null otherwise. Single
|
|
2159
2184
|
* source of truth for the ownership check, so callers cannot observe
|
|
2160
2185
|
* isCurrentInstance true while currentInstance is null.
|
|
2161
2186
|
*/
|
|
2187
|
+
/**
|
|
2188
|
+
* Build an instance that owns socketPath without spawning a supervisor
|
|
2189
|
+
* process, and adopt it as the live singleton. `start` is the production
|
|
2190
|
+
* writer of both; this exists so a test can drive the in-process plane
|
|
2191
|
+
* without forging private state, and hands back the previous singleton so
|
|
2192
|
+
* the caller can restore it.
|
|
2193
|
+
*/
|
|
2194
|
+
static adoptTestInstance(binaryPath, socketPath) {
|
|
2195
|
+
const previous = Supervisor.instance;
|
|
2196
|
+
const supervisor = new Supervisor(binaryPath, socketPath);
|
|
2197
|
+
Supervisor.instance = supervisor;
|
|
2198
|
+
return {
|
|
2199
|
+
supervisor,
|
|
2200
|
+
restore: () => {
|
|
2201
|
+
Supervisor.instance = previous;
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2162
2205
|
static getInstance(supervisorId) {
|
|
2163
2206
|
if (Supervisor.instance === null) return null;
|
|
2164
2207
|
if (Supervisor.instance.supervisorId === supervisorId) return Supervisor.instance;
|
|
@@ -2637,7 +2680,7 @@ var thinking_policy_umpire_default = {
|
|
|
2637
2680
|
{
|
|
2638
2681
|
"op": "condIn",
|
|
2639
2682
|
"condition": "backend",
|
|
2640
|
-
"values": ["claude"
|
|
2683
|
+
"values": ["claude"]
|
|
2641
2684
|
},
|
|
2642
2685
|
{
|
|
2643
2686
|
"op": "condEq",
|
|
@@ -2657,7 +2700,7 @@ var thinking_policy_umpire_default = {
|
|
|
2657
2700
|
}
|
|
2658
2701
|
]
|
|
2659
2702
|
},
|
|
2660
|
-
"reason": "claude
|
|
2703
|
+
"reason": "claude supports low through max only on a new session"
|
|
2661
2704
|
}]
|
|
2662
2705
|
}
|
|
2663
2706
|
}]
|
|
@@ -2781,6 +2824,55 @@ function validateThinkingForBackendResume(backend, value) {
|
|
|
2781
2824
|
throw new Error(`backend ${JSON.stringify(backend)} does not support parameter "thinking"`);
|
|
2782
2825
|
}
|
|
2783
2826
|
//#endregion
|
|
2827
|
+
//#region src/tools/sentinel-status.ts
|
|
2828
|
+
/**
|
|
2829
|
+
* Sentinel statuses that settle the provider session itself, so the recorded
|
|
2830
|
+
* session id is a loadable transcript. TIMEOUT and KILLED are supervisor-imposed
|
|
2831
|
+
* terminations (time budget, cancellation) where an automatic continuation would
|
|
2832
|
+
* surprise. Matches the Go `readSentinelSession`.
|
|
2833
|
+
*/
|
|
2834
|
+
const RESUMABLE_SENTINEL_STATUSES = new Set([
|
|
2835
|
+
"DONE",
|
|
2836
|
+
"FAILED",
|
|
2837
|
+
"BLOCKED"
|
|
2838
|
+
]);
|
|
2839
|
+
/**
|
|
2840
|
+
* The terminal sentinel statuses the provider's state fallback answers from,
|
|
2841
|
+
* matching the Go `terminalStatusFromRunInfo` set: a run that settled before
|
|
2842
|
+
* a supervisor restart keeps its outcome discoverable by run id.
|
|
2843
|
+
*/
|
|
2844
|
+
const TERMINAL_SENTINEL_STATUSES = new Set([
|
|
2845
|
+
"DONE",
|
|
2846
|
+
"FAILED",
|
|
2847
|
+
"BLOCKED",
|
|
2848
|
+
"TIMEOUT",
|
|
2849
|
+
"KILLED"
|
|
2850
|
+
]);
|
|
2851
|
+
/**
|
|
2852
|
+
* The result word a sentinel status settles on. Both the status surface and the
|
|
2853
|
+
* message provider's state fallback answer from this one mapping, so the same
|
|
2854
|
+
* sentinel file cannot classify differently between them. BLOCKED reads as
|
|
2855
|
+
* failed: it names why the run stopped, not a result of its own.
|
|
2856
|
+
*/
|
|
2857
|
+
function sentinelStatusToResult(sentinelStatus) {
|
|
2858
|
+
switch (sentinelStatus) {
|
|
2859
|
+
case "DONE": return "done";
|
|
2860
|
+
case "FAILED": return "failed";
|
|
2861
|
+
case "TIMEOUT": return "timeout";
|
|
2862
|
+
case "KILLED": return "killed";
|
|
2863
|
+
case "BLOCKED": return "failed";
|
|
2864
|
+
default: return "running";
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Whether a terminal sentinel leaves a session worth resuming. Mirrors the only
|
|
2869
|
+
* hit Go's `runFactsFromStatus` reaches for a registry entry: a non-empty
|
|
2870
|
+
* session id. The sentinel word never decides this.
|
|
2871
|
+
*/
|
|
2872
|
+
function resumableFromSession(sessionId) {
|
|
2873
|
+
return typeof sessionId === "string" && sessionId !== "";
|
|
2874
|
+
}
|
|
2875
|
+
//#endregion
|
|
2784
2876
|
//#region src/tools/run-resolution.ts
|
|
2785
2877
|
/** Resolve a local public run ID before considering a label alias. */
|
|
2786
2878
|
function findLocalRunByReference(sup, reference) {
|
|
@@ -2821,16 +2913,6 @@ async function sentinelExists(filePath) {
|
|
|
2821
2913
|
function rawStatusPhase(source) {
|
|
2822
2914
|
return stringField(source ?? {}, "phase") || stringField(source ?? {}, "status") || "running";
|
|
2823
2915
|
}
|
|
2824
|
-
function sentinelStatusToResult(sentinelStatus) {
|
|
2825
|
-
switch (sentinelStatus) {
|
|
2826
|
-
case "DONE": return "done";
|
|
2827
|
-
case "FAILED": return "failed";
|
|
2828
|
-
case "TIMEOUT": return "timeout";
|
|
2829
|
-
case "KILLED": return "killed";
|
|
2830
|
-
case "BLOCKED": return "failed";
|
|
2831
|
-
default: return "running";
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
2916
|
function translateStatus(rawPhase, sentinelData) {
|
|
2835
2917
|
const sentinelStatus = sentinelData?._status;
|
|
2836
2918
|
const phase = rawPhase.toLowerCase();
|
|
@@ -2933,16 +3015,19 @@ function shapeStatusResult(result, view = "full") {
|
|
|
2933
3015
|
};
|
|
2934
3016
|
}
|
|
2935
3017
|
async function buildRunStatus(runInfo, liveStatus) {
|
|
2936
|
-
|
|
3018
|
+
const liveSentinel = stringField(liveStatus ?? {}, "sentinel_file");
|
|
3019
|
+
const belongsToRun = !liveSentinel || liveSentinel === runInfo.sentinelPath;
|
|
3020
|
+
if (liveStatus && belongsToRun) retainLiveIdentity(runInfo, liveStatus);
|
|
2937
3021
|
let sentinel = null;
|
|
2938
3022
|
if (await sentinelExists(runInfo.sentinelPath)) sentinel = await parseSentinel(runInfo.sentinelPath);
|
|
2939
|
-
const
|
|
2940
|
-
|
|
3023
|
+
const source = belongsToRun ? liveStatus : null;
|
|
3024
|
+
const translated = belongsToRun && liveStatus ? translateStatus(rawStatusPhase(source), sentinel) : sentinel ? sentinelStatusToResult(sentinel._status) : translateStatus(rawStatusPhase(source), null);
|
|
3025
|
+
const result = buildBaseStatus(source ?? {}, {
|
|
2941
3026
|
run_id: runInfo.runId,
|
|
2942
3027
|
label: runInfo.label,
|
|
2943
3028
|
prefer_fallback_run_id: true,
|
|
2944
3029
|
runtime_id: runInfo.runtimeId,
|
|
2945
|
-
session_id: sentinel?.SESSION ??
|
|
3030
|
+
session_id: sentinel?.SESSION ?? source?.session_id ?? runInfo.sessionId,
|
|
2946
3031
|
stop_reason: sentinel?.STOP_REASON,
|
|
2947
3032
|
identity: {
|
|
2948
3033
|
roster_file: runInfo.rosterFile,
|
|
@@ -2956,6 +3041,10 @@ async function buildRunStatus(runInfo, liveStatus) {
|
|
|
2956
3041
|
effective_backend: runInfo.effectiveBackend
|
|
2957
3042
|
}
|
|
2958
3043
|
}, translated);
|
|
3044
|
+
result.label = runInfo.label;
|
|
3045
|
+
if (!belongsToRun || !result.event_path) result.event_path = runInfo.eventLogPath;
|
|
3046
|
+
if (!belongsToRun || !liveStatus) result.runtime_output_available = false;
|
|
3047
|
+
return result;
|
|
2959
3048
|
}
|
|
2960
3049
|
async function queryLiveStatus(client, runtimeId) {
|
|
2961
3050
|
if (!runtimeId) return null;
|
|
@@ -3112,27 +3201,41 @@ function matchesFilters(record, args) {
|
|
|
3112
3201
|
if (args.after_seq !== void 0 && seq !== void 0 && seq <= args.after_seq) return false;
|
|
3113
3202
|
return true;
|
|
3114
3203
|
}
|
|
3115
|
-
|
|
3204
|
+
/**
|
|
3205
|
+
* Stream the parsed records of one NDJSON event log. Malformed lines are
|
|
3206
|
+
* dropped; an unreadable log rejects, so a caller that cannot read the log
|
|
3207
|
+
* never sees a silent empty tail.
|
|
3208
|
+
*/
|
|
3209
|
+
async function* readNdjsonRecords(filePath) {
|
|
3116
3210
|
const stream = fs.createReadStream(filePath, { encoding: "utf-8" });
|
|
3117
3211
|
const rl = readline.createInterface({
|
|
3118
3212
|
input: stream,
|
|
3119
3213
|
crlfDelay: Infinity
|
|
3120
3214
|
});
|
|
3121
|
-
const events = [];
|
|
3122
3215
|
try {
|
|
3123
3216
|
for await (const line of rl) {
|
|
3124
3217
|
if (!line.trim()) continue;
|
|
3218
|
+
let parsed;
|
|
3125
3219
|
try {
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3220
|
+
parsed = JSON.parse(line);
|
|
3221
|
+
} catch {
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
const record = asRecord(parsed);
|
|
3225
|
+
if (record) yield record;
|
|
3131
3226
|
}
|
|
3132
3227
|
} finally {
|
|
3133
3228
|
rl.close();
|
|
3134
3229
|
stream.close();
|
|
3135
3230
|
}
|
|
3231
|
+
}
|
|
3232
|
+
async function readNdjsonEvents(filePath, args) {
|
|
3233
|
+
const events = [];
|
|
3234
|
+
for await (const record of readNdjsonRecords(filePath)) {
|
|
3235
|
+
if (!matchesFilters(record, args)) continue;
|
|
3236
|
+
events.push(clipEvent(record));
|
|
3237
|
+
if (events.length > args.limit) events.splice(0, events.length - args.limit);
|
|
3238
|
+
}
|
|
3136
3239
|
return events;
|
|
3137
3240
|
}
|
|
3138
3241
|
async function resolveRun(args, getSupervisorClient) {
|
|
@@ -3221,7 +3324,7 @@ async function executeEventsTool(args, getSupervisorClient) {
|
|
|
3221
3324
|
} catch (error) {
|
|
3222
3325
|
failures.push(contextualError(`read event log ${resolved.eventLogPath}`, error));
|
|
3223
3326
|
}
|
|
3224
|
-
if (resolved.runtimeId) try {
|
|
3327
|
+
if (resolved.runtimeId && !resolved.runInfo) try {
|
|
3225
3328
|
const history = await resolved.client.history({
|
|
3226
3329
|
runtime_id: resolved.runtimeId,
|
|
3227
3330
|
limit,
|
|
@@ -3333,23 +3436,31 @@ function sleep(ms, signal) {
|
|
|
3333
3436
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3334
3437
|
});
|
|
3335
3438
|
}
|
|
3336
|
-
async function
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3439
|
+
async function outputFromEvents(filePath) {
|
|
3440
|
+
let output;
|
|
3441
|
+
for await (const record of readNdjsonRecords(filePath)) try {
|
|
3442
|
+
const event = normalizeRunEvent(record);
|
|
3443
|
+
if (event?.event === "session.end" && event.final_output !== void 0) output = event.final_output;
|
|
3444
|
+
} catch {}
|
|
3445
|
+
return output;
|
|
3446
|
+
}
|
|
3447
|
+
async function realResult(supervisorId, runtimeId) {
|
|
3448
|
+
const { client, isSingleton } = await getSupervisorClient(supervisorId);
|
|
3449
|
+
try {
|
|
3450
|
+
return await client.result(runtimeId);
|
|
3451
|
+
} finally {
|
|
3452
|
+
if (!isSingleton) client.close();
|
|
3345
3453
|
}
|
|
3346
|
-
|
|
3347
|
-
|
|
3454
|
+
}
|
|
3455
|
+
async function realSentinelPath(supervisorId, runId) {
|
|
3456
|
+
const { sup, supervisorId: resolvedId } = await getSupervisorClient(supervisorId);
|
|
3457
|
+
return (sup ? sup.getRunByReference(runId) : void 0)?.sentinelPath ?? findExternalRun(resolvedId, runId)?.sentinelPath;
|
|
3348
3458
|
}
|
|
3349
3459
|
const defaultDeps = {
|
|
3350
3460
|
status: statusTool,
|
|
3351
3461
|
inspect: inspectTool,
|
|
3352
|
-
|
|
3462
|
+
result: realResult,
|
|
3463
|
+
sentinelPath: realSentinelPath,
|
|
3353
3464
|
sleep,
|
|
3354
3465
|
now: Date.now,
|
|
3355
3466
|
pollIntervalMs: DEFAULT_POLL_INTERVAL_MS
|
|
@@ -3388,14 +3499,25 @@ async function executeResultTool(args, deps) {
|
|
|
3388
3499
|
if (TERMINAL_STATUSES$1.has(status.status)) {
|
|
3389
3500
|
let exactOutputRetrieved = false;
|
|
3390
3501
|
try {
|
|
3391
|
-
|
|
3502
|
+
let output = status.event_path ? await outputFromEvents(status.event_path).catch(() => void 0) : void 0;
|
|
3392
3503
|
if (output !== void 0) {
|
|
3393
3504
|
status.final_output = output;
|
|
3394
3505
|
status.final_output_truncated = false;
|
|
3395
3506
|
exactOutputRetrieved = true;
|
|
3396
3507
|
}
|
|
3397
3508
|
} catch {}
|
|
3398
|
-
if (status.
|
|
3509
|
+
if (!exactOutputRetrieved && status.runtime_output_available !== false && status.runtime_id) try {
|
|
3510
|
+
const result = await deps.result(args.supervisorId, status.runtime_id);
|
|
3511
|
+
const output = typeof result.final_output === "string" ? result.final_output : void 0;
|
|
3512
|
+
const sentinel = await deps.sentinelPath(args.supervisorId, status.run_id);
|
|
3513
|
+
const matchesTurn = sentinel === void 0 ? true : sentinel !== "" && typeof result.sentinel_file === "string" && result.sentinel_file === sentinel;
|
|
3514
|
+
if (output !== void 0 && matchesTurn) {
|
|
3515
|
+
status.final_output = output;
|
|
3516
|
+
status.final_output_truncated = false;
|
|
3517
|
+
exactOutputRetrieved = true;
|
|
3518
|
+
}
|
|
3519
|
+
} catch {}
|
|
3520
|
+
if (!exactOutputRetrieved && status.final_output === void 0 && status.runtime_output_available !== false) try {
|
|
3399
3521
|
const inspected = await deps.inspect({
|
|
3400
3522
|
runId: args.runId,
|
|
3401
3523
|
supervisorId: args.supervisorId
|
|
@@ -5223,7 +5345,7 @@ var message_route_conformance_default = {
|
|
|
5223
5345
|
"expect": {
|
|
5224
5346
|
"code": "unsupported_action",
|
|
5225
5347
|
"facts": {},
|
|
5226
|
-
"message": "action \"control_reply\" is
|
|
5348
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5227
5349
|
}
|
|
5228
5350
|
},
|
|
5229
5351
|
{
|
|
@@ -5242,7 +5364,7 @@ var message_route_conformance_default = {
|
|
|
5242
5364
|
"expect": {
|
|
5243
5365
|
"code": "unsupported_action",
|
|
5244
5366
|
"facts": {},
|
|
5245
|
-
"message": "action \"control_reply\" is
|
|
5367
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5246
5368
|
}
|
|
5247
5369
|
},
|
|
5248
5370
|
{
|
|
@@ -5261,7 +5383,7 @@ var message_route_conformance_default = {
|
|
|
5261
5383
|
"expect": {
|
|
5262
5384
|
"code": "unsupported_action",
|
|
5263
5385
|
"facts": {},
|
|
5264
|
-
"message": "action \"control_reply\" is
|
|
5386
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5265
5387
|
}
|
|
5266
5388
|
},
|
|
5267
5389
|
{
|
|
@@ -5280,7 +5402,7 @@ var message_route_conformance_default = {
|
|
|
5280
5402
|
"expect": {
|
|
5281
5403
|
"code": "unsupported_action",
|
|
5282
5404
|
"facts": {},
|
|
5283
|
-
"message": "action \"control_reply\" is
|
|
5405
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5284
5406
|
}
|
|
5285
5407
|
},
|
|
5286
5408
|
{
|
|
@@ -5299,7 +5421,7 @@ var message_route_conformance_default = {
|
|
|
5299
5421
|
"expect": {
|
|
5300
5422
|
"code": "unsupported_action",
|
|
5301
5423
|
"facts": {},
|
|
5302
|
-
"message": "action \"control_reply\" is
|
|
5424
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5303
5425
|
}
|
|
5304
5426
|
},
|
|
5305
5427
|
{
|
|
@@ -5318,7 +5440,7 @@ var message_route_conformance_default = {
|
|
|
5318
5440
|
"expect": {
|
|
5319
5441
|
"code": "unsupported_action",
|
|
5320
5442
|
"facts": {},
|
|
5321
|
-
"message": "action \"control_reply\" is
|
|
5443
|
+
"message": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
5322
5444
|
}
|
|
5323
5445
|
},
|
|
5324
5446
|
{
|
|
@@ -8274,6 +8396,41 @@ function renderAskFailure(messageId, target, outcome = "possibly-pending") {
|
|
|
8274
8396
|
function normalizeAskTimeoutMs(timeoutMs) {
|
|
8275
8397
|
return timeoutMs !== void 0 && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : void 0;
|
|
8276
8398
|
}
|
|
8399
|
+
/** Render a value the way Go's `%q` verb does (`strconv.Quote`). */
|
|
8400
|
+
function goQuote(value) {
|
|
8401
|
+
let out = "\"";
|
|
8402
|
+
for (const character of value) out += goEscapedRune(character);
|
|
8403
|
+
return `${out}"`;
|
|
8404
|
+
}
|
|
8405
|
+
const GO_PRINTABLE = /[\p{L}\p{M}\p{N}\p{P}\p{S}]/u;
|
|
8406
|
+
/**
|
|
8407
|
+
* One rune in `strconv.Quote` form: short escapes for a quote, a backslash, a
|
|
8408
|
+
* bell and the whitespace controls; `\xNN` for the remaining ASCII escapes;
|
|
8409
|
+
* `\uNNNN` and `\UNNNNNNNN` for a non-printable rune; the rune itself when it
|
|
8410
|
+
* is printable. Printable is Go's `strconv.IsPrint` — ASCII space through `~`,
|
|
8411
|
+
* and elsewhere the categories L, M, N, P and S. V8's Unicode tables can be
|
|
8412
|
+
* newer than Go's, so a rune assigned since Go's tables were generated renders
|
|
8413
|
+
* escaped here where Go writes it verbatim; that direction only fails to match
|
|
8414
|
+
* a sentence, never invents one.
|
|
8415
|
+
*/
|
|
8416
|
+
function goEscapedRune(character) {
|
|
8417
|
+
const code = character.codePointAt(0) ?? 0;
|
|
8418
|
+
switch (character) {
|
|
8419
|
+
case "\"": return "\\\"";
|
|
8420
|
+
case "\\": return "\\\\";
|
|
8421
|
+
case "\x07": return "\\a";
|
|
8422
|
+
case "\b": return "\\b";
|
|
8423
|
+
case "\f": return "\\f";
|
|
8424
|
+
case "\n": return "\\n";
|
|
8425
|
+
case "\r": return "\\r";
|
|
8426
|
+
case " ": return "\\t";
|
|
8427
|
+
case "\v": return "\\v";
|
|
8428
|
+
}
|
|
8429
|
+
if (code < 128 ? code >= 32 && code !== 127 : GO_PRINTABLE.test(character)) return character;
|
|
8430
|
+
if (code < 128) return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
8431
|
+
if (code < 65536) return `\\u${code.toString(16).padStart(4, "0")}`;
|
|
8432
|
+
return `\\U${code.toString(16).padStart(8, "0")}`;
|
|
8433
|
+
}
|
|
8277
8434
|
//#endregion
|
|
8278
8435
|
//#region ../../schemas/message.input.json
|
|
8279
8436
|
var message_input_default = {
|
|
@@ -8409,11 +8566,11 @@ var message_input_default = {
|
|
|
8409
8566
|
"properties": {
|
|
8410
8567
|
"action": {
|
|
8411
8568
|
"const": "control_reply",
|
|
8412
|
-
"description": "
|
|
8569
|
+
"description": "Retained in the contract for the retired claude-channel backend's reply path; no surface can deliver one."
|
|
8413
8570
|
},
|
|
8414
8571
|
"target": {
|
|
8415
8572
|
"type": "string",
|
|
8416
|
-
"description": "Opaque
|
|
8573
|
+
"description": "Opaque control message id, passed through unchanged."
|
|
8417
8574
|
},
|
|
8418
8575
|
"payload": {
|
|
8419
8576
|
"type": "object",
|
|
@@ -8452,7 +8609,7 @@ var message_route_umpire_default = {
|
|
|
8452
8609
|
},
|
|
8453
8610
|
"controlReplySupported": {
|
|
8454
8611
|
"type": "boolean",
|
|
8455
|
-
"description": "
|
|
8612
|
+
"description": "Retained in the contract; no surface can deliver a control reply now that the claude-channel sidecar is retired."
|
|
8456
8613
|
}
|
|
8457
8614
|
},
|
|
8458
8615
|
fields: {
|
|
@@ -8509,7 +8666,7 @@ var message_route_umpire_default = {
|
|
|
8509
8666
|
"condition": "controlReplySupported"
|
|
8510
8667
|
}]
|
|
8511
8668
|
},
|
|
8512
|
-
"reason": "action \"control_reply\" is
|
|
8669
|
+
"reason": "action \"control_reply\" is retained in the contract but no surface can deliver one"
|
|
8513
8670
|
},
|
|
8514
8671
|
{
|
|
8515
8672
|
"type": "fairWhen",
|
|
@@ -8953,7 +9110,7 @@ async function tell(provider, input, resolution) {
|
|
|
8953
9110
|
const label = resolutionLabel(resolution, input.target ?? "");
|
|
8954
9111
|
let result;
|
|
8955
9112
|
try {
|
|
8956
|
-
result = await provider.tell(resolution.runId, input.message ?? "");
|
|
9113
|
+
result = await provider.tell(resolution.runId, input.message ?? "", resolution.publicRunId);
|
|
8957
9114
|
} catch (err) {
|
|
8958
9115
|
throw asToolError(err);
|
|
8959
9116
|
}
|
|
@@ -8971,7 +9128,7 @@ async function ask(provider, input, resolution, signal) {
|
|
|
8971
9128
|
const label = resolutionLabel(resolution, input.target ?? "");
|
|
8972
9129
|
let result;
|
|
8973
9130
|
try {
|
|
8974
|
-
result = await provider.ask(resolution.runId, input.message ?? "", input.timeout_ms, signal);
|
|
9131
|
+
result = await provider.ask(resolution.runId, input.message ?? "", input.timeout_ms, signal, resolution.publicRunId);
|
|
8975
9132
|
} catch (err) {
|
|
8976
9133
|
throw asToolError(err);
|
|
8977
9134
|
}
|
|
@@ -9088,9 +9245,10 @@ async function executeMessage(provider, input, options) {
|
|
|
9088
9245
|
conditions.targetMatch = resolution.match;
|
|
9089
9246
|
const denial = permissionDenial(action, conditions);
|
|
9090
9247
|
if (denial) throw denial;
|
|
9248
|
+
if (action === "resume" && typed.idempotency_key) return resume(provider, typed, resolution);
|
|
9091
9249
|
let facts;
|
|
9092
9250
|
try {
|
|
9093
|
-
facts = await provider.state(resolution.runId);
|
|
9251
|
+
facts = await provider.state(resolution.runId, resolution.publicRunId);
|
|
9094
9252
|
} catch (err) {
|
|
9095
9253
|
throw refused(err);
|
|
9096
9254
|
}
|
|
@@ -9168,7 +9326,7 @@ async function executePeers(provider, input) {
|
|
|
9168
9326
|
const resolution = await resolveTarget(provider, typed.target ?? "");
|
|
9169
9327
|
let last;
|
|
9170
9328
|
try {
|
|
9171
|
-
last = await provider.last(resolution.runId);
|
|
9329
|
+
last = await provider.last(resolution.runId, resolution.publicRunId);
|
|
9172
9330
|
} catch (err) {
|
|
9173
9331
|
throw asToolError(err);
|
|
9174
9332
|
}
|
|
@@ -9302,19 +9460,6 @@ function createMessageTool(provider) {
|
|
|
9302
9460
|
return (args, options) => messageTool(args, provider, options);
|
|
9303
9461
|
}
|
|
9304
9462
|
//#endregion
|
|
9305
|
-
//#region src/tools/sentinel-status.ts
|
|
9306
|
-
/**
|
|
9307
|
-
* Sentinel statuses that settle the provider session itself, so the recorded
|
|
9308
|
-
* session id is a loadable transcript. TIMEOUT and KILLED are supervisor-imposed
|
|
9309
|
-
* terminations (time budget, cancellation) where an automatic continuation would
|
|
9310
|
-
* surprise. Matches the Go `readSentinelSession`.
|
|
9311
|
-
*/
|
|
9312
|
-
const RESUMABLE_SENTINEL_STATUSES = new Set([
|
|
9313
|
-
"DONE",
|
|
9314
|
-
"FAILED",
|
|
9315
|
-
"BLOCKED"
|
|
9316
|
-
]);
|
|
9317
|
-
//#endregion
|
|
9318
9463
|
//#region src/tools/message-provider.ts
|
|
9319
9464
|
const SUPERVISOR_RUN_ID = "supervisor";
|
|
9320
9465
|
const TERMINAL_STATUSES = new Set([
|
|
@@ -9354,6 +9499,16 @@ function isGenerationMismatchError(error) {
|
|
|
9354
9499
|
}
|
|
9355
9500
|
return error instanceof Error && /generation mismatch/i.test(error.message);
|
|
9356
9501
|
}
|
|
9502
|
+
/**
|
|
9503
|
+
* Matches Go's `statusReportsNoRuntime`: true only for the supervisor's absence
|
|
9504
|
+
* answer for this exact runtime, so a refusal or a transport failure stays the
|
|
9505
|
+
* caller's error. The sentence is rendered with `goQuote` because the
|
|
9506
|
+
* supervisor formats the id with Go's `%q`.
|
|
9507
|
+
*/
|
|
9508
|
+
function statusReportsNoRuntime(error, runId) {
|
|
9509
|
+
if (!(error instanceof RpcError) || error.code !== -32e3) return false;
|
|
9510
|
+
return rpcText(error) === `runtime ${goQuote(runId)} not found`;
|
|
9511
|
+
}
|
|
9357
9512
|
/** Reject an abort exactly the way fetch does, so the ask cleanup path runs. */
|
|
9358
9513
|
function raceAbort(promise, signal) {
|
|
9359
9514
|
if (!signal) return promise;
|
|
@@ -9425,15 +9580,42 @@ function translateRunFacts(rawStatus, rawPhase) {
|
|
|
9425
9580
|
live: !TERMINAL_STATUSES.has(status)
|
|
9426
9581
|
};
|
|
9427
9582
|
}
|
|
9428
|
-
|
|
9429
|
-
|
|
9583
|
+
/**
|
|
9584
|
+
* Classify one broker peer row into the facts the router gates on. The Go
|
|
9585
|
+
* providers share this translation through `messaging.PeerRunFacts`: a peer row
|
|
9586
|
+
* carries no session and no sentinel, so the run is never resumable from here —
|
|
9587
|
+
* the supervisor that spawned it owns a resume.
|
|
9588
|
+
*/
|
|
9589
|
+
function peerFacts(peer) {
|
|
9590
|
+
const translation = translateRunFacts(peer?.status ?? "", "");
|
|
9591
|
+
return {
|
|
9592
|
+
live: translation.live,
|
|
9593
|
+
resumable: false,
|
|
9594
|
+
status: translation.status
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
/** Read one sentinel file's status word, or undefined when the file is unreadable. */
|
|
9598
|
+
async function readSentinelStatus(filePath) {
|
|
9430
9599
|
try {
|
|
9431
|
-
|
|
9432
|
-
return RESUMABLE_SENTINEL_STATUSES.has(status);
|
|
9600
|
+
return ((await fs.promises.readFile(filePath, "utf-8")).trim().split("\n")[0] ?? "").trim().toUpperCase();
|
|
9433
9601
|
} catch {
|
|
9434
|
-
return
|
|
9602
|
+
return;
|
|
9435
9603
|
}
|
|
9436
9604
|
}
|
|
9605
|
+
/**
|
|
9606
|
+
* Read one sentinel file's terminal result word, or undefined when the file is
|
|
9607
|
+
* unreadable or holds a status that is not terminal. The mapping is the status
|
|
9608
|
+
* surface's, so both classify a sentinel the same way.
|
|
9609
|
+
*/
|
|
9610
|
+
async function terminalSentinelStatus(filePath) {
|
|
9611
|
+
const status = await readSentinelStatus(filePath);
|
|
9612
|
+
if (!status || !TERMINAL_SENTINEL_STATUSES.has(status)) return void 0;
|
|
9613
|
+
return sentinelStatusToResult(status);
|
|
9614
|
+
}
|
|
9615
|
+
async function sentinelResumable(filePath) {
|
|
9616
|
+
const status = await readSentinelStatus(filePath);
|
|
9617
|
+
return status !== void 0 && RESUMABLE_SENTINEL_STATUSES.has(status);
|
|
9618
|
+
}
|
|
9437
9619
|
function peerRecords(peers) {
|
|
9438
9620
|
return (peers.peers ?? []).map((peer) => {
|
|
9439
9621
|
const session = peer.session;
|
|
@@ -9503,10 +9685,33 @@ function createHostMessageProvider(options) {
|
|
|
9503
9685
|
});
|
|
9504
9686
|
} catch {}
|
|
9505
9687
|
}
|
|
9506
|
-
function publicRunIdFor(runId) {
|
|
9507
|
-
const
|
|
9508
|
-
if (
|
|
9509
|
-
|
|
9688
|
+
async function publicRunIdFor(runId) {
|
|
9689
|
+
const instance = Supervisor.getInstance(supervisorId);
|
|
9690
|
+
if (instance && instance.getRunsByRuntimeId(runId).length > 0) {
|
|
9691
|
+
const unique = instance.getRunByRuntimeId(runId);
|
|
9692
|
+
if (unique) return unique.runId;
|
|
9693
|
+
const status = await client.status(runId);
|
|
9694
|
+
const match = typeof status.sentinel_file === "string" ? instance.getRunByRuntimeTurn(runId, status.sentinel_file) : void 0;
|
|
9695
|
+
if (!match) throw new Error(`turn identity unavailable for ${runId}`);
|
|
9696
|
+
return match.runId;
|
|
9697
|
+
}
|
|
9698
|
+
const matches = listExternalRuns(supervisorId).filter((info) => info.runtimeId === runId);
|
|
9699
|
+
if (matches.length <= 1) return matches[0]?.runId;
|
|
9700
|
+
const status = await client.status(runId);
|
|
9701
|
+
const match = matches.find((info) => info.sentinelPath === status.sentinel_file);
|
|
9702
|
+
if (!match) throw new Error(`turn identity unavailable for ${runId}`);
|
|
9703
|
+
return match.runId;
|
|
9704
|
+
}
|
|
9705
|
+
function publicRunInfo(publicRunId) {
|
|
9706
|
+
if (!publicRunId) return void 0;
|
|
9707
|
+
const instance = Supervisor.getInstance(supervisorId);
|
|
9708
|
+
return instance ? instance.getRunById(publicRunId) : findExternalRunById(supervisorId, publicRunId);
|
|
9709
|
+
}
|
|
9710
|
+
function expectedSentinel(publicRunId) {
|
|
9711
|
+
if (!publicRunId) return void 0;
|
|
9712
|
+
const info = publicRunInfo(publicRunId);
|
|
9713
|
+
if (!info?.sentinelPath) throw new Error(`turn identity unavailable for ${publicRunId}`);
|
|
9714
|
+
return info.sentinelPath;
|
|
9510
9715
|
}
|
|
9511
9716
|
function unique(info) {
|
|
9512
9717
|
return {
|
|
@@ -9516,6 +9721,18 @@ function createHostMessageProvider(options) {
|
|
|
9516
9721
|
match: "unique"
|
|
9517
9722
|
};
|
|
9518
9723
|
}
|
|
9724
|
+
async function peerRunState(runId) {
|
|
9725
|
+
let listed;
|
|
9726
|
+
try {
|
|
9727
|
+
listed = await listedPeers();
|
|
9728
|
+
} catch (error) {
|
|
9729
|
+
if (isGenerationMismatchError(error)) throw error;
|
|
9730
|
+
return;
|
|
9731
|
+
}
|
|
9732
|
+
if (listed.detail) return void 0;
|
|
9733
|
+
const peer = listed.peers.find((entry) => entry.run_id === runId);
|
|
9734
|
+
return peer ? peerFacts(peer) : void 0;
|
|
9735
|
+
}
|
|
9519
9736
|
const provider = {
|
|
9520
9737
|
caller: () => ({
|
|
9521
9738
|
kind: "owner",
|
|
@@ -9545,14 +9762,14 @@ function createHostMessageProvider(options) {
|
|
|
9545
9762
|
const exact = peers.find((peer) => peer.run_id === ref);
|
|
9546
9763
|
if (exact) return {
|
|
9547
9764
|
runId: exact.run_id,
|
|
9548
|
-
publicRunId: publicRunIdFor(exact.run_id),
|
|
9765
|
+
publicRunId: await publicRunIdFor(exact.run_id),
|
|
9549
9766
|
label: firstNonEmpty(exact.label, exact.run_id),
|
|
9550
9767
|
match: "unique"
|
|
9551
9768
|
};
|
|
9552
9769
|
const candidates = peers.filter((peer) => peer.label === ref).map((peer) => peer.run_id);
|
|
9553
9770
|
if (candidates.length === 1) return {
|
|
9554
9771
|
runId: candidates[0],
|
|
9555
|
-
publicRunId: publicRunIdFor(candidates[0]),
|
|
9772
|
+
publicRunId: await publicRunIdFor(candidates[0]),
|
|
9556
9773
|
label: ref,
|
|
9557
9774
|
match: "unique"
|
|
9558
9775
|
};
|
|
@@ -9566,20 +9783,38 @@ function createHostMessageProvider(options) {
|
|
|
9566
9783
|
match: "none"
|
|
9567
9784
|
};
|
|
9568
9785
|
},
|
|
9569
|
-
async state(runId) {
|
|
9786
|
+
async state(runId, publicRunId) {
|
|
9570
9787
|
if (runId === "supervisor") {
|
|
9571
9788
|
const { peers, detail } = await listedPeers();
|
|
9572
9789
|
if (detail) throw new Error(`the peer listing is incomplete: ${detail}`);
|
|
9573
|
-
|
|
9574
|
-
return {
|
|
9575
|
-
live: translation.live,
|
|
9576
|
-
resumable: false,
|
|
9577
|
-
status: translation.status
|
|
9578
|
-
};
|
|
9790
|
+
return peerFacts(peers.find((entry) => entry.run_id === SUPERVISOR_RUN_ID));
|
|
9579
9791
|
}
|
|
9580
|
-
|
|
9792
|
+
let status;
|
|
9793
|
+
try {
|
|
9794
|
+
status = await client.status(runId);
|
|
9795
|
+
} catch (error) {
|
|
9796
|
+
if (!statusReportsNoRuntime(error, runId)) throw error;
|
|
9797
|
+
const registered = Supervisor.getInstance(supervisorId)?.getRunById(runId) ?? findExternalRunById(supervisorId, runId);
|
|
9798
|
+
if (registered) {
|
|
9799
|
+
const terminal = await terminalSentinelStatus(registered.sentinelPath);
|
|
9800
|
+
if (terminal) return {
|
|
9801
|
+
live: false,
|
|
9802
|
+
resumable: resumableFromSession(registered.sessionId),
|
|
9803
|
+
status: terminal
|
|
9804
|
+
};
|
|
9805
|
+
}
|
|
9806
|
+
const listed = await peerRunState(runId);
|
|
9807
|
+
if (listed) return listed;
|
|
9808
|
+
throw error;
|
|
9809
|
+
}
|
|
9810
|
+
const info = publicRunInfo(publicRunId);
|
|
9811
|
+
if (info && typeof status.sentinel_file === "string" && status.sentinel_file !== info.sentinelPath) return {
|
|
9812
|
+
live: false,
|
|
9813
|
+
resumable: false,
|
|
9814
|
+
status: await terminalSentinelStatus(info.sentinelPath) ?? "unknown"
|
|
9815
|
+
};
|
|
9581
9816
|
const translation = translateRunFacts(typeof status.status === "string" ? status.status : "", typeof status.phase === "string" ? status.phase : "");
|
|
9582
|
-
let resumable =
|
|
9817
|
+
let resumable = resumableFromSession(status.session_id);
|
|
9583
9818
|
if (!resumable && typeof status.sentinel_file === "string") resumable = await sentinelResumable(status.sentinel_file);
|
|
9584
9819
|
return {
|
|
9585
9820
|
live: translation.live,
|
|
@@ -9587,7 +9822,8 @@ function createHostMessageProvider(options) {
|
|
|
9587
9822
|
status: translation.status
|
|
9588
9823
|
};
|
|
9589
9824
|
},
|
|
9590
|
-
async tell(targetRunId, message) {
|
|
9825
|
+
async tell(targetRunId, message, publicRunId) {
|
|
9826
|
+
const expected = expectedSentinel(publicRunId);
|
|
9591
9827
|
const messageId = newMsgID();
|
|
9592
9828
|
const body = JSON.stringify({
|
|
9593
9829
|
from: SUPERVISOR_RUN_ID,
|
|
@@ -9603,7 +9839,8 @@ function createHostMessageProvider(options) {
|
|
|
9603
9839
|
message_id: messageId,
|
|
9604
9840
|
generation,
|
|
9605
9841
|
body,
|
|
9606
|
-
no_reply: true
|
|
9842
|
+
no_reply: true,
|
|
9843
|
+
...expected ? { expected_sentinel_file: expected } : {}
|
|
9607
9844
|
});
|
|
9608
9845
|
} catch (error) {
|
|
9609
9846
|
const outcome = controlOutcome(error);
|
|
@@ -9618,7 +9855,8 @@ function createHostMessageProvider(options) {
|
|
|
9618
9855
|
messageId: firstNonEmpty(result.message_id, messageId)
|
|
9619
9856
|
};
|
|
9620
9857
|
},
|
|
9621
|
-
async ask(targetRunId, message, timeoutMs, signal) {
|
|
9858
|
+
async ask(targetRunId, message, timeoutMs, signal, publicRunId) {
|
|
9859
|
+
const expected = expectedSentinel(publicRunId);
|
|
9622
9860
|
const messageId = newMsgID();
|
|
9623
9861
|
const body = JSON.stringify({
|
|
9624
9862
|
id: messageId,
|
|
@@ -9635,7 +9873,8 @@ function createHostMessageProvider(options) {
|
|
|
9635
9873
|
to_run_id: targetRunId,
|
|
9636
9874
|
message_id: messageId,
|
|
9637
9875
|
generation,
|
|
9638
|
-
body
|
|
9876
|
+
body,
|
|
9877
|
+
...expected ? { expected_sentinel_file: expected } : {}
|
|
9639
9878
|
});
|
|
9640
9879
|
} catch (error) {
|
|
9641
9880
|
const outcome = controlOutcome(error);
|
|
@@ -9764,10 +10003,20 @@ function createHostMessageProvider(options) {
|
|
|
9764
10003
|
previousLabel: renamed.previous_label
|
|
9765
10004
|
};
|
|
9766
10005
|
},
|
|
9767
|
-
async last(runId) {
|
|
10006
|
+
async last(runId, publicRunId) {
|
|
10007
|
+
const expected = expectedSentinel(publicRunId);
|
|
10008
|
+
if (expected) {
|
|
10009
|
+
let status;
|
|
10010
|
+
try {
|
|
10011
|
+
status = await client.status(runId);
|
|
10012
|
+
} catch (error) {
|
|
10013
|
+
throw authorityError(error);
|
|
10014
|
+
}
|
|
10015
|
+
if (typeof status.sentinel_file === "string" && status.sentinel_file !== expected) throw authorityError(new RpcError(-32e3, `turn no longer current for runtime ${runId}`, { code: "invalid-request" }));
|
|
10016
|
+
}
|
|
9768
10017
|
let last;
|
|
9769
10018
|
try {
|
|
9770
|
-
last = await client.sessionLast(runId);
|
|
10019
|
+
last = await client.sessionLast(runId, expected);
|
|
9771
10020
|
} catch (error) {
|
|
9772
10021
|
throw authorityError(error);
|
|
9773
10022
|
}
|
|
@@ -9917,12 +10166,7 @@ function createChildMessageProvider(options) {
|
|
|
9917
10166
|
if (detail) throw new Error(`the peer listing is incomplete: ${detail}`);
|
|
9918
10167
|
const peer = peers.find((entry) => entry.run_id === targetRunId);
|
|
9919
10168
|
if (!peer) throw new Error(`run ${targetRunId} is not visible`);
|
|
9920
|
-
|
|
9921
|
-
return {
|
|
9922
|
-
live: translation.live,
|
|
9923
|
-
resumable: false,
|
|
9924
|
-
status: translation.status
|
|
9925
|
-
};
|
|
10169
|
+
return peerFacts(peer);
|
|
9926
10170
|
},
|
|
9927
10171
|
tell: (targetRunId, message) => broker.tell(targetRunId, message, newMsgID()),
|
|
9928
10172
|
ask: (targetRunId, message, timeoutMs, signal) => broker.ask(targetRunId, message, newMsgID(), timeoutMs, signal),
|
|
@@ -9944,7 +10188,11 @@ function createChildMessageProvider(options) {
|
|
|
9944
10188
|
}
|
|
9945
10189
|
//#endregion
|
|
9946
10190
|
//#region src/tools/peers.ts
|
|
9947
|
-
/** The one avenor_peers handler; it resolves and reads through the bound provider.
|
|
10191
|
+
/** The one avenor_peers handler; it resolves and reads through the bound provider.
|
|
10192
|
+
*
|
|
10193
|
+
* Admission belongs to the shared router for every surface, so the argument is
|
|
10194
|
+
* the untyped JSON a tool call carries and `executePeers` validates it first —
|
|
10195
|
+
* the same shape `messageTool` takes. */
|
|
9948
10196
|
async function peersTool(args, provider) {
|
|
9949
10197
|
return executePeers(provider, args);
|
|
9950
10198
|
}
|
package/dist/postinstall.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ import { n as getVersion, r as installerBinaryPath, t as getInstallDir } from ".
|
|
|
4
4
|
declare function platformMapping(platform: string, arch: string): string | null;
|
|
5
5
|
declare function verifyChecksum(buffer: Buffer, expectedHash: string): boolean;
|
|
6
6
|
declare function parseChecksums(content: string, assetName: string): string | null;
|
|
7
|
-
declare function postinstall(fetchFn?:
|
|
7
|
+
declare function postinstall(fetchFn?: (url: string) => Promise<Response>, platform?: string, arch?: string): Promise<void>;
|
|
8
8
|
//#endregion
|
|
9
9
|
export { getInstallDir, getVersion, installerBinaryPath, parseChecksums, platformMapping, postinstall, verifyChecksum };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsdown",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
26
27
|
"test": "bun test",
|
|
27
28
|
"test:e2e": "AVENOR_REAL_INTEGRATION=1 bun test",
|
|
28
29
|
"dev": "tsdown --watch",
|