@sema-agent/client-core 0.104.1 → 0.105.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/CHANGELOG.md +90 -0
- package/README.md +11 -4
- package/dist/adapt/arms.js +14 -6
- package/dist/adapter/activeRunSelfHeal.d.ts +1 -0
- package/dist/adapter/activeRunSelfHeal.js +10 -5
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -4
- package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
- package/dist/adapter/downstream/wiringManifestView.js +25 -0
- package/dist/adapter/runStream.js +5 -1
- package/dist/cloudConfigWireCaps.js +3 -0
- package/dist/compensations.js +6 -6
- package/dist/coreValuePorts.d.ts +3 -1
- package/dist/coreValuePorts.js +4 -0
- package/dist/displayUntrusted.d.ts +4 -2
- package/dist/displayUntrusted.js +45 -22
- package/dist/engineGenerationPort.d.ts +11 -2
- package/dist/engineGenerationPort.js +63 -9
- package/dist/engineHealthFacts.d.ts +5 -0
- package/dist/engineHealthFacts.js +11 -0
- package/dist/eventLeg.d.ts +1 -1
- package/dist/eventLeg.js +23 -3
- package/dist/generated/engineFactTables.d.ts +1 -0
- package/dist/generated/engineFactTables.js +1 -0
- package/dist/generated/sdkFactTables.d.ts +2 -0
- package/dist/generated/sdkFactTables.js +9 -0
- package/dist/headlessReconnectWire.js +31 -4
- package/dist/hitl/askGateWire.js +26 -2
- package/dist/hitl/frameRouter.d.ts +4 -0
- package/dist/hitl/frameRouter.js +14 -0
- package/dist/hitl/gateIdentity.d.ts +1 -0
- package/dist/hitl/gateIdentity.js +3 -0
- package/dist/hitl/planReviewCard.d.ts +21 -0
- package/dist/hitl/planReviewCard.js +229 -0
- package/dist/hitl/planReviewWire.d.ts +38 -13
- package/dist/hitl/planReviewWire.js +355 -133
- package/dist/hostSettingsSource.d.ts +1 -1
- package/dist/hostSettingsSource.js +8 -4
- package/dist/hostTurnSettings.d.ts +1 -1
- package/dist/hostTurnSettings.js +29 -10
- package/dist/i18n/messages.en.d.ts +50 -0
- package/dist/i18n/messages.en.js +50 -0
- package/dist/i18n/messages.zh.js +51 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +9 -1
- package/dist/mcpTransportKindsCapability.d.ts +18 -0
- package/dist/mcpTransportKindsCapability.js +75 -0
- package/dist/mcpWireCaps.d.ts +4 -1
- package/dist/mcpWireCaps.js +24 -13
- package/dist/notifications.d.ts +9 -0
- package/dist/notifications.js +11 -2
- package/dist/ownKey.d.ts +4 -0
- package/dist/ownKey.js +8 -0
- package/dist/permissionModeWriteCapability.d.ts +13 -0
- package/dist/permissionModeWriteCapability.js +35 -0
- package/dist/permissionsAllowGrantCapability.d.ts +3 -0
- package/dist/permissionsAllowGrantCapability.js +26 -3
- package/dist/permissionsDeclaration.d.ts +23 -1
- package/dist/permissionsDeclaration.js +60 -30
- package/dist/planReviewCardFacts.d.ts +116 -0
- package/dist/planReviewCardFacts.js +469 -0
- package/dist/reattachWindow.d.ts +8 -1
- package/dist/reattachWindow.js +44 -12
- package/dist/runFollow.d.ts +30 -0
- package/dist/runFollow.js +117 -0
- package/dist/runLedgerSnapshot.d.ts +20 -0
- package/dist/runLedgerSnapshot.js +272 -0
- package/dist/runPermissionModeSwitch.d.ts +57 -0
- package/dist/runPermissionModeSwitch.js +135 -0
- package/dist/runReattachPolicy.d.ts +13 -5
- package/dist/runReattachPolicy.js +58 -23
- package/dist/seam.d.ts +3 -0
- package/dist/seam.js +1 -1
- package/dist/seatContract.js +1 -0
- package/dist/sessionMemoryErase.d.ts +1 -1
- package/dist/sessionMemoryErase.js +5 -0
- package/dist/sessionMemoryStatus.d.ts +27 -1
- package/dist/sessionMemoryStatus.js +117 -0
- package/dist/sseIdleTriage.d.ts +3 -1
- package/dist/sseIdleTriage.js +19 -4
- package/dist/subagent/engineSubagentTail.js +2 -0
- package/dist/subagentContentStore.d.ts +3 -0
- package/dist/subagentContentStore.js +10 -1
- package/dist/turnFailurePresentation.d.ts +5 -2
- package/dist/turnFailurePresentation.js +12 -4
- package/docs/INTEGRATION-CLIENTS.md +493 -10
- package/package.json +2 -2
package/dist/reattachWindow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { usableClockMs } from './clockReading.js';
|
|
2
2
|
import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
|
|
3
|
+
import { isReattachBreak, mintRunNotFollowed } from './runFollow.js';
|
|
3
4
|
export const REATTACH_WINDOW_MS = 300_000;
|
|
4
5
|
export const REATTACH_BACKOFF_START_MS = 1_000;
|
|
5
6
|
export const REATTACH_BACKOFF_CAP_MS = 30_000;
|
|
@@ -25,15 +26,37 @@ export function reattachBackoffMs(attempt, tuning) {
|
|
|
25
26
|
const raw = startMs * 2 ** (attempt - 1);
|
|
26
27
|
return Number.isFinite(raw) ? Math.min(raw, capMs) : capMs;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
const isRunHandle = (v) => typeof v === 'string' && v !== '';
|
|
30
|
+
export function reattachWindowOpen(nowMs, tuning, runId) {
|
|
29
31
|
const t = tuned(tuning);
|
|
30
32
|
const start = usableClockMs(nowMs) ?? Number.NaN;
|
|
31
|
-
return Object.freeze({
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
enteredAtMs: start,
|
|
35
|
+
deadlineMs: start + t.windowMs,
|
|
36
|
+
attempt: 0,
|
|
37
|
+
consecutiveNotFound: 0,
|
|
38
|
+
notFoundConfirmed: false,
|
|
39
|
+
tuning: Object.freeze(t),
|
|
40
|
+
...(isRunHandle(runId) ? { runId } : {}),
|
|
41
|
+
});
|
|
32
42
|
}
|
|
33
43
|
const step = (state, decision) => Object.freeze({ state, decision });
|
|
34
|
-
const
|
|
44
|
+
const runOf = (state) => {
|
|
45
|
+
const v = readThrownSlot(state, 'runId', 'data');
|
|
46
|
+
return isRunHandle(v) ? v : undefined;
|
|
47
|
+
};
|
|
48
|
+
const exhausted = (state) => {
|
|
49
|
+
const brk = readThrownSlot(state, 'lastBreak', 'data');
|
|
50
|
+
return step(state, Object.freeze({ action: 'window-exhausted', notFollowed: mintRunNotFollowed('reattach_exhausted', runOf(state), isReattachBreak(brk) ? brk : undefined) }));
|
|
51
|
+
};
|
|
52
|
+
const badInput = (state, runId) => step(state, Object.freeze({ action: 'window-exhausted', notFollowed: mintRunNotFollowed('bad_input', runId) }));
|
|
35
53
|
const cont = (state, notFoundConfirmed = false) => step(state, Object.freeze({ action: 'continue', notFoundConfirmed }));
|
|
36
|
-
const next = (state, patch) =>
|
|
54
|
+
const next = (state, patch, lastBreak) => {
|
|
55
|
+
if (lastBreak === undefined)
|
|
56
|
+
return Object.freeze({ ...state, ...patch });
|
|
57
|
+
const { lastBreak: _previous, ...rest } = state;
|
|
58
|
+
return Object.freeze(lastBreak.to === undefined ? { ...rest, ...patch } : { ...rest, ...patch, lastBreak: lastBreak.to });
|
|
59
|
+
};
|
|
37
60
|
const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
38
61
|
function validState(s) {
|
|
39
62
|
if (typeof s !== 'object' || s === null)
|
|
@@ -42,18 +65,20 @@ function validState(s) {
|
|
|
42
65
|
return typeof o.enteredAtMs === 'number' && typeof o.deadlineMs === 'number' && isCount(o.attempt) && isCount(o.consecutiveNotFound) && typeof o.notFoundConfirmed === 'boolean' && typeof o.tuning === 'object' && o.tuning !== null;
|
|
43
66
|
}
|
|
44
67
|
export function reattachWindowStep(state, event, nowMs) {
|
|
68
|
+
let windowRun;
|
|
45
69
|
try {
|
|
70
|
+
windowRun = runOf(state);
|
|
46
71
|
if (!validState(state))
|
|
47
|
-
return
|
|
72
|
+
return badInput(reattachWindowOpen(Number.NaN, undefined, windowRun), windowRun);
|
|
48
73
|
if (typeof event !== 'object' || event === null)
|
|
49
74
|
return cont(state);
|
|
50
75
|
const type = event.type;
|
|
51
76
|
const result = event.result;
|
|
52
77
|
const serverRetryAfterMs = event.serverRetryAfterMs;
|
|
53
78
|
if (type === 'new-segment')
|
|
54
|
-
return cont(reattachWindowOpen(nowMs, state.tuning));
|
|
55
|
-
if (usableClockMs(nowMs) === null || !Number.isFinite(state.deadlineMs))
|
|
56
|
-
return
|
|
79
|
+
return cont(reattachWindowOpen(nowMs, state.tuning, windowRun));
|
|
80
|
+
if (usableClockMs(nowMs) === null || usableClockMs(state.enteredAtMs) === null || !Number.isFinite(state.deadlineMs))
|
|
81
|
+
return badInput(state, windowRun);
|
|
57
82
|
switch (type) {
|
|
58
83
|
case 'attempt':
|
|
59
84
|
return nowMs < state.deadlineMs ? cont(next(state, { attempt: state.attempt + 1 })) : exhausted(state);
|
|
@@ -70,18 +95,25 @@ export function reattachWindowStep(state, event, nowMs) {
|
|
|
70
95
|
case 'tail-frame':
|
|
71
96
|
return cont(next(state, { consecutiveNotFound: 0, notFoundConfirmed: false }));
|
|
72
97
|
case 'tail-refused-404':
|
|
73
|
-
return state.notFoundConfirmed
|
|
98
|
+
return state.notFoundConfirmed
|
|
99
|
+
? step(state, Object.freeze({ action: 'run-unknown', confirmations: state.consecutiveNotFound, notFollowed: mintRunNotFollowed('run_unknown', windowRun) }))
|
|
100
|
+
: cont(state);
|
|
74
101
|
case 'backoff': {
|
|
75
|
-
const
|
|
102
|
+
const brkRead = readThrownSlot(event, 'break');
|
|
103
|
+
const brk = isReattachBreak(brkRead) ? brkRead : undefined;
|
|
104
|
+
const handoffRead = readThrownSlot(event, 'runId');
|
|
105
|
+
const handoff = isRunHandle(handoffRead) && handoffRead !== windowRun ? handoffRead : undefined;
|
|
106
|
+
const moved = next(state, handoff !== undefined ? { runId: handoff, consecutiveNotFound: 0, notFoundConfirmed: false } : {}, { to: brk });
|
|
107
|
+
const own = reattachBackoffMs(moved.attempt, moved.tuning);
|
|
76
108
|
const delay = typeof serverRetryAfterMs === 'number' && Number.isFinite(serverRetryAfterMs) && serverRetryAfterMs > 0 ? Math.max(own, serverRetryAfterMs) : own;
|
|
77
|
-
return nowMs + delay >=
|
|
109
|
+
return nowMs + delay >= moved.deadlineMs ? exhausted(moved) : step(moved, Object.freeze({ action: 'wait', afterMs: delay }));
|
|
78
110
|
}
|
|
79
111
|
default:
|
|
80
112
|
return cont(state);
|
|
81
113
|
}
|
|
82
114
|
}
|
|
83
115
|
catch {
|
|
84
|
-
return
|
|
116
|
+
return badInput(reattachWindowOpen(Number.NaN, undefined, windowRun), windowRun);
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
119
|
export function poisonFrameInitial() {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
2
|
+
import { type MessageRef } from './i18n/translate.js';
|
|
3
|
+
import type { ReattachEvent } from './runReattachPolicy.js';
|
|
4
|
+
export type RunNotFollowedCause = 'reattach_exhausted' | 'no_run_id' | 'bad_input' | 'run_unknown' | 'target_unconfirmed' | 'cursor_unknown';
|
|
5
|
+
export type ReattachBreak = ReattachEvent['cause'];
|
|
6
|
+
export type RunNotFollowed<C extends RunNotFollowedCause = RunNotFollowedCause> = C extends 'reattach_exhausted' ? RunNotFollowedExhausted : RunNotFollowedFact<C>;
|
|
7
|
+
interface RunNotFollowedFact<C extends RunNotFollowedCause> {
|
|
8
|
+
readonly kind: 'not_followable';
|
|
9
|
+
readonly cause: C;
|
|
10
|
+
readonly taskId?: string;
|
|
11
|
+
}
|
|
12
|
+
interface RunNotFollowedExhausted extends RunNotFollowedFact<'reattach_exhausted'> {
|
|
13
|
+
readonly lastBreak?: ReattachBreak;
|
|
14
|
+
}
|
|
15
|
+
export declare const isReattachBreak: (v: {} | null | undefined) => v is ReattachBreak;
|
|
16
|
+
export declare function mintRunNotFollowed<C extends RunNotFollowedCause>(cause: C, taskId?: string, lastBreak?: ReattachBreak): RunNotFollowed<C>;
|
|
17
|
+
export declare function registerRunNotFollowed(thrown: object, fact: RunNotFollowed): void;
|
|
18
|
+
export declare function runNotFollowedOf(err: {} | null | undefined): RunNotFollowed | undefined;
|
|
19
|
+
interface NoticeParts {
|
|
20
|
+
readonly reason: MessageRef;
|
|
21
|
+
readonly ending: MessageRef | '';
|
|
22
|
+
readonly exit: MessageRef | '';
|
|
23
|
+
}
|
|
24
|
+
export declare function runNotFollowedHeadRef(taskId: string | undefined, parts: NoticeParts): MessageRef;
|
|
25
|
+
export declare function runNotFollowedReasonRef(cause: RunNotFollowedCause, lastBreak?: ReattachBreak): MessageRef;
|
|
26
|
+
export declare function runNotFollowedEndingUnknownRef(): MessageRef;
|
|
27
|
+
export declare function runNotFollowedExitRef(cause: RunNotFollowedCause): MessageRef | '';
|
|
28
|
+
export declare function runNotFollowedNoticeRef(fact: RunNotFollowed): MessageRef;
|
|
29
|
+
export declare function runNotFollowedNotice(fact: RunNotFollowed, lang?: UiLanguage): string;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { msg, renderMessage } from './i18n/translate.js';
|
|
2
|
+
import { displayUntrusted } from './displayUntrusted.js';
|
|
3
|
+
import { guardedRead, readOwnDataValue } from './ownKey.js';
|
|
4
|
+
const RUN_NOT_FOLLOWED_CAUSES = Object.freeze(['reattach_exhausted', 'no_run_id', 'bad_input', 'run_unknown', 'target_unconfirmed', 'cursor_unknown']);
|
|
5
|
+
const _causesPin = true;
|
|
6
|
+
void _causesPin;
|
|
7
|
+
const REATTACH_BREAKS = Object.freeze(['connection_lost', 'stream_cap', 'stream_draining']);
|
|
8
|
+
const _breaksPin = true;
|
|
9
|
+
void _breaksPin;
|
|
10
|
+
const isCause = (v) => RUN_NOT_FOLLOWED_CAUSES.includes(v);
|
|
11
|
+
export const isReattachBreak = (v) => REATTACH_BREAKS.includes(v);
|
|
12
|
+
export function mintRunNotFollowed(cause, taskId, lastBreak) {
|
|
13
|
+
const c = isCause(cause) ? cause : 'bad_input';
|
|
14
|
+
const run = typeof taskId === 'string' && taskId !== '' ? { taskId } : {};
|
|
15
|
+
const fact = c === 'reattach_exhausted' ? { kind: 'not_followable', cause: c, ...run, ...(isReattachBreak(lastBreak) ? { lastBreak } : {}) } : { kind: 'not_followable', cause: c, ...run };
|
|
16
|
+
return Object.freeze(fact);
|
|
17
|
+
}
|
|
18
|
+
const notFollowedByThrown = new WeakMap();
|
|
19
|
+
export function registerRunNotFollowed(thrown, fact) {
|
|
20
|
+
try {
|
|
21
|
+
if ((typeof thrown === 'object' && thrown !== null) || typeof thrown === 'function')
|
|
22
|
+
notFollowedByThrown.set(thrown, fact);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function runNotFollowedOf(err) {
|
|
28
|
+
try {
|
|
29
|
+
if (typeof err === 'object' && err !== null)
|
|
30
|
+
return notFollowedByThrown.get(err);
|
|
31
|
+
if (typeof err === 'function')
|
|
32
|
+
return notFollowedByThrown.get(err);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const TASK_ID_MAX = 64;
|
|
40
|
+
function readFact(fact) {
|
|
41
|
+
if (typeof fact !== 'object' || fact === null)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (guardedRead(() => readOwnDataValue(fact, 'kind')) !== 'not_followable')
|
|
44
|
+
return undefined;
|
|
45
|
+
const cause = guardedRead(() => readOwnDataValue(fact, 'cause'));
|
|
46
|
+
if (!isCause(cause))
|
|
47
|
+
return undefined;
|
|
48
|
+
const rawTask = guardedRead(() => readOwnDataValue(fact, 'taskId'));
|
|
49
|
+
const shown = typeof rawTask === 'string' && rawTask !== '' ? guardedRead(() => displayUntrusted(rawTask, { max: TASK_ID_MAX, blanks: true })) : undefined;
|
|
50
|
+
const brk = cause === 'reattach_exhausted' ? guardedRead(() => readOwnDataValue(fact, 'lastBreak')) : undefined;
|
|
51
|
+
return { cause, ...(shown !== undefined && shown !== '' ? { taskId: shown } : {}), ...(isReattachBreak(brk) ? { lastBreak: brk } : {}) };
|
|
52
|
+
}
|
|
53
|
+
export function runNotFollowedHeadRef(taskId, parts) {
|
|
54
|
+
const tail = { reason: parts.reason, ending: parts.ending, exit: parts.exit };
|
|
55
|
+
return taskId !== undefined && taskId !== '' ? msg('follow.notFollowed.headRun', { taskId, ...tail }) : msg('follow.notFollowed.headTurn', tail);
|
|
56
|
+
}
|
|
57
|
+
const LAST_BREAK_REASON_KEYS = Object.freeze({
|
|
58
|
+
stream_draining: 'follow.notFollowed.reason.exhaustedDraining',
|
|
59
|
+
connection_lost: 'follow.notFollowed.reason.exhaustedConnectionLost',
|
|
60
|
+
stream_cap: 'follow.notFollowed.reason.exhaustedStreamCap',
|
|
61
|
+
});
|
|
62
|
+
export function runNotFollowedReasonRef(cause, lastBreak) {
|
|
63
|
+
switch (cause) {
|
|
64
|
+
case 'reattach_exhausted':
|
|
65
|
+
return msg(lastBreak !== undefined && isReattachBreak(lastBreak) ? LAST_BREAK_REASON_KEYS[lastBreak] : 'follow.notFollowed.reason.exhausted');
|
|
66
|
+
case 'bad_input':
|
|
67
|
+
return msg('follow.notFollowed.reason.badInput');
|
|
68
|
+
case 'no_run_id':
|
|
69
|
+
return msg('follow.notFollowed.reason.noRunId');
|
|
70
|
+
case 'run_unknown':
|
|
71
|
+
return msg('follow.notFollowed.reason.runUnknown');
|
|
72
|
+
default:
|
|
73
|
+
return msg('follow.notFollowed.reason.unspecified');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function runNotFollowedEndingUnknownRef() {
|
|
77
|
+
return msg('follow.notFollowed.endingUnknown');
|
|
78
|
+
}
|
|
79
|
+
export function runNotFollowedExitRef(cause) {
|
|
80
|
+
switch (cause) {
|
|
81
|
+
case 'reattach_exhausted':
|
|
82
|
+
case 'bad_input':
|
|
83
|
+
return msg('follow.notFollowed.exit.reattach');
|
|
84
|
+
case 'no_run_id':
|
|
85
|
+
return msg('follow.notFollowed.exit.checkRuns');
|
|
86
|
+
case 'run_unknown':
|
|
87
|
+
return msg('follow.notFollowed.exit.runUnknown');
|
|
88
|
+
default:
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function neutralNoticeRef() {
|
|
93
|
+
return runNotFollowedHeadRef(undefined, { reason: msg('follow.notFollowed.reason.unspecified'), ending: runNotFollowedEndingUnknownRef(), exit: '' });
|
|
94
|
+
}
|
|
95
|
+
export function runNotFollowedNoticeRef(fact) {
|
|
96
|
+
try {
|
|
97
|
+
const r = readFact(fact);
|
|
98
|
+
if (r === undefined)
|
|
99
|
+
return neutralNoticeRef();
|
|
100
|
+
return runNotFollowedHeadRef(r.taskId, {
|
|
101
|
+
reason: runNotFollowedReasonRef(r.cause, r.lastBreak),
|
|
102
|
+
ending: r.cause === 'run_unknown' ? '' : runNotFollowedEndingUnknownRef(),
|
|
103
|
+
exit: runNotFollowedExitRef(r.cause),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return neutralNoticeRef();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function runNotFollowedNotice(fact, lang) {
|
|
111
|
+
try {
|
|
112
|
+
return renderMessage(runNotFollowedNoticeRef(fact), lang);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return renderMessage(neutralNoticeRef(), lang);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RunLedgerSnapshot<F = never> {
|
|
2
|
+
readonly highWater: string;
|
|
3
|
+
readonly openToolStart?: object;
|
|
4
|
+
readonly folded?: F;
|
|
5
|
+
}
|
|
6
|
+
export interface RunLedgerFold<F> {
|
|
7
|
+
readonly init: () => F;
|
|
8
|
+
readonly step: (acc: F, ev: object) => F;
|
|
9
|
+
readonly strictOrder?: boolean;
|
|
10
|
+
readonly agree?: (a: F, b: F) => boolean;
|
|
11
|
+
readonly substantive?: (ev: object) => boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface RunLedgerSnapshotDeps<F = never> {
|
|
14
|
+
readonly isParked: (taskId: string, signal: AbortSignal) => Promise<boolean>;
|
|
15
|
+
readonly frames: (taskId: string, signal: AbortSignal) => AsyncIterable<object>;
|
|
16
|
+
readonly budgetMs?: number;
|
|
17
|
+
readonly log?: (msg: string) => void;
|
|
18
|
+
readonly fold?: RunLedgerFold<F>;
|
|
19
|
+
}
|
|
20
|
+
export declare function readRunLedgerSnapshot<F = never>(taskId: string, callId: string | undefined, deps: RunLedgerSnapshotDeps<F>, signal?: AbortSignal): Promise<RunLedgerSnapshot<F> | null>;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { createStreamDrainingLegs } from './streamDrainingLegs.js';
|
|
2
|
+
import { abortableSleep, settleWithinDeadline } from './abortableSleep.js';
|
|
3
|
+
import { readOwnDataValue } from './ownKey.js';
|
|
4
|
+
const RUN_LEDGER_SNAPSHOT_BUDGET_MS = 8_000;
|
|
5
|
+
const UNNUMBERED_FRAME_CAP = 64;
|
|
6
|
+
const PARK_TRAILER_MAX = 4;
|
|
7
|
+
function frameSeq(ev) {
|
|
8
|
+
try {
|
|
9
|
+
const id = ev?.id;
|
|
10
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
const n = Number(id);
|
|
13
|
+
return Number.isSafeInteger(n) && n >= 0 ? n : undefined;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function isParkMarker(ev) {
|
|
20
|
+
try {
|
|
21
|
+
const t = ev?.type;
|
|
22
|
+
return t === 'suspended' || t === 'needs_review';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isOwnToolStart(ev, callId) {
|
|
29
|
+
if (callId === undefined || callId.length === 0)
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
const e = ev;
|
|
33
|
+
return e !== null && typeof e === 'object' && e.type === 'tool_start' && e.toolCallId === callId;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function errorNameOf(e) {
|
|
40
|
+
try {
|
|
41
|
+
const name = e?.name;
|
|
42
|
+
return typeof name === 'string' ? name : 'Error';
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return 'Error';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function budgetMsOf(deps) {
|
|
49
|
+
try {
|
|
50
|
+
const raw = readOwnDataValue(deps, 'budgetMs');
|
|
51
|
+
return typeof raw === 'number' && Number.isFinite(raw) ? Math.max(0, raw) : RUN_LEDGER_SNAPSHOT_BUDGET_MS;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return RUN_LEDGER_SNAPSHOT_BUDGET_MS;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function readRunLedgerSnapshot(taskId, callId, deps, signal) {
|
|
58
|
+
const log = (msg) => {
|
|
59
|
+
try {
|
|
60
|
+
deps.log?.(msg);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (typeof taskId !== 'string' || taskId.length === 0)
|
|
66
|
+
return null;
|
|
67
|
+
const ac = new AbortController();
|
|
68
|
+
const onOuterAbort = () => ac.abort();
|
|
69
|
+
let outerWatched = false;
|
|
70
|
+
try {
|
|
71
|
+
if (signal?.aborted === true)
|
|
72
|
+
return null;
|
|
73
|
+
signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
74
|
+
outerWatched = signal !== undefined;
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
log(`[ledgerSnapshot] signal-unreadable task=${taskId} name=${errorNameOf(e)}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const budget = budgetMsOf(deps);
|
|
81
|
+
let onExpire;
|
|
82
|
+
const expired = new Promise((resolve) => {
|
|
83
|
+
onExpire = () => resolve(null);
|
|
84
|
+
if (ac.signal.aborted)
|
|
85
|
+
resolve(null);
|
|
86
|
+
else
|
|
87
|
+
ac.signal.addEventListener('abort', onExpire, { once: true });
|
|
88
|
+
});
|
|
89
|
+
const parkedNow = async () => {
|
|
90
|
+
try {
|
|
91
|
+
return (await deps.isParked(taskId, ac.signal)) === true;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
log(`[ledgerSnapshot] parked-read-threw task=${taskId} name=${errorNameOf(e)}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const waitAbortable = (ms) => abortableSleep(Math.max(0, ms), ac.signal).then(() => !ac.signal.aborted);
|
|
99
|
+
const readOnce = async (drainLegs) => {
|
|
100
|
+
let max;
|
|
101
|
+
let folded;
|
|
102
|
+
let numbered = 0;
|
|
103
|
+
let lastParkAt = -1;
|
|
104
|
+
let openToolStart;
|
|
105
|
+
let unnumbered = 0;
|
|
106
|
+
let preambleLen = 0;
|
|
107
|
+
let unnumberedRun = 0;
|
|
108
|
+
let complete = false;
|
|
109
|
+
let cappedOut = false;
|
|
110
|
+
let disordered = false;
|
|
111
|
+
const passAc = new AbortController();
|
|
112
|
+
const onOuter = () => passAc.abort();
|
|
113
|
+
if (ac.signal.aborted)
|
|
114
|
+
passAc.abort();
|
|
115
|
+
else
|
|
116
|
+
ac.signal.addEventListener('abort', onOuter, { once: true });
|
|
117
|
+
try {
|
|
118
|
+
if (deps.fold !== undefined)
|
|
119
|
+
folded = deps.fold.init();
|
|
120
|
+
for await (const ev of deps.frames(taskId, passAc.signal)) {
|
|
121
|
+
if (ac.signal.aborted)
|
|
122
|
+
break;
|
|
123
|
+
if (drainLegs.see(ev))
|
|
124
|
+
continue;
|
|
125
|
+
const seq = frameSeq(ev);
|
|
126
|
+
if (seq === undefined && deps.fold?.substantive?.(ev) === true) {
|
|
127
|
+
disordered = true;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (seq === undefined) {
|
|
131
|
+
unnumbered += 1;
|
|
132
|
+
if (unnumbered > UNNUMBERED_FRAME_CAP + 3 * preambleLen) {
|
|
133
|
+
cappedOut = true;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (max === undefined) {
|
|
137
|
+
preambleLen += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
unnumberedRun += 1;
|
|
141
|
+
if (preambleLen > 0 && unnumberedRun >= 2 * preambleLen) {
|
|
142
|
+
complete = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
unnumberedRun = 0;
|
|
148
|
+
if (deps.fold?.strictOrder === true && max !== undefined && seq <= max) {
|
|
149
|
+
disordered = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
numbered += 1;
|
|
153
|
+
if (max === undefined || seq > max)
|
|
154
|
+
max = seq;
|
|
155
|
+
if (isParkMarker(ev))
|
|
156
|
+
lastParkAt = numbered;
|
|
157
|
+
if (isOwnToolStart(ev, callId))
|
|
158
|
+
openToolStart = ev;
|
|
159
|
+
if (deps.fold !== undefined)
|
|
160
|
+
folded = deps.fold.step(folded, ev);
|
|
161
|
+
}
|
|
162
|
+
if (!complete && !cappedOut && !disordered && !ac.signal.aborted) {
|
|
163
|
+
const drained = drainLegs.legEnded('returned');
|
|
164
|
+
if (drained !== undefined)
|
|
165
|
+
return { kind: 'drained', decision: drained };
|
|
166
|
+
complete = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
drainLegs.legEnded('threw');
|
|
171
|
+
const name = errorNameOf(e);
|
|
172
|
+
if (!ac.signal.aborted && name === 'StreamEndedWithoutTerminalError')
|
|
173
|
+
complete = true;
|
|
174
|
+
else
|
|
175
|
+
log(`[ledgerSnapshot] read-ended-unproven task=${taskId} name=${name}`);
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
passAc.abort();
|
|
179
|
+
ac.signal.removeEventListener('abort', onOuter);
|
|
180
|
+
}
|
|
181
|
+
if (!complete || disordered || max === undefined || ac.signal.aborted) {
|
|
182
|
+
if (disordered)
|
|
183
|
+
log(`[ledgerSnapshot] read-unordered task=${taskId}`);
|
|
184
|
+
return { kind: 'read', result: null };
|
|
185
|
+
}
|
|
186
|
+
const nearParkTail = lastParkAt > 0 && numbered - lastParkAt <= PARK_TRAILER_MAX;
|
|
187
|
+
return {
|
|
188
|
+
kind: 'read',
|
|
189
|
+
result: { max, nearParkTail, ...(openToolStart !== undefined ? { openToolStart } : {}), ...(deps.fold !== undefined ? { folded: folded } : {}) },
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
const readPass = async () => {
|
|
193
|
+
const drainLegs = createStreamDrainingLegs();
|
|
194
|
+
for (;;) {
|
|
195
|
+
const once = await readOnce(drainLegs);
|
|
196
|
+
if (once.kind === 'read')
|
|
197
|
+
return once.result;
|
|
198
|
+
if (!('reconnectAfterMs' in once.decision)) {
|
|
199
|
+
log(`[ledgerSnapshot] drained-give-up task=${taskId}`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
log(`[ledgerSnapshot] drained-reread task=${taskId} ms=${String(once.decision.reconnectAfterMs)}`);
|
|
203
|
+
if (!(await waitAbortable(once.decision.reconnectAfterMs)))
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const body = async () => {
|
|
208
|
+
try {
|
|
209
|
+
if (!(await parkedNow())) {
|
|
210
|
+
log(`[ledgerSnapshot] not-parked-before task=${taskId}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const [first, second] = await Promise.all([readPass(), readPass()]);
|
|
214
|
+
if (first === null) {
|
|
215
|
+
log(`[ledgerSnapshot] no-proof task=${taskId}`);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (!first.nearParkTail) {
|
|
219
|
+
log(`[ledgerSnapshot] tail-not-at-park task=${taskId}`);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (second === null || second.max !== first.max || !second.nearParkTail) {
|
|
223
|
+
log(`[ledgerSnapshot] passes-disagree-on-end task=${taskId}`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const agree = deps.fold?.agree;
|
|
227
|
+
if (agree !== undefined && !(first.folded !== undefined && second.folded !== undefined && agree(first.folded, second.folded) === true)) {
|
|
228
|
+
log(`[ledgerSnapshot] passes-disagree-on-fold task=${taskId}`);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const chosen = second.openToolStart !== undefined ? second : first;
|
|
232
|
+
if (!(await parkedNow())) {
|
|
233
|
+
log(`[ledgerSnapshot] not-parked-after task=${taskId}`);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (ac.signal.aborted)
|
|
237
|
+
return null;
|
|
238
|
+
return {
|
|
239
|
+
highWater: String(chosen.max),
|
|
240
|
+
...(chosen.openToolStart !== undefined ? { openToolStart: chosen.openToolStart } : {}),
|
|
241
|
+
...(deps.fold !== undefined && chosen.folded !== undefined ? { folded: chosen.folded } : {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
log(`[ledgerSnapshot] internal-error task=${taskId} name=${errorNameOf(e)}`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
const bounded = settleWithinDeadline(budget, (deadline) => {
|
|
251
|
+
if (deadline.aborted)
|
|
252
|
+
ac.abort();
|
|
253
|
+
else
|
|
254
|
+
deadline.addEventListener('abort', () => ac.abort(), { once: true });
|
|
255
|
+
return body();
|
|
256
|
+
}).then((settled) => (settled.kind === 'value' ? settled.value : null));
|
|
257
|
+
return await Promise.race([bounded, expired]);
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
if (outerWatched) {
|
|
261
|
+
try {
|
|
262
|
+
signal?.removeEventListener('abort', onOuterAbort);
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
log(`[ledgerSnapshot] signal-unlisten-threw task=${taskId} name=${errorNameOf(e)}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (onExpire !== undefined)
|
|
269
|
+
ac.signal.removeEventListener('abort', onExpire);
|
|
270
|
+
ac.abort();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
2
|
+
import { type PermissionModeReceiptView } from './adapter/downstream/wiringManifestView.js';
|
|
3
|
+
import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
|
|
4
|
+
import { type PermissionModeWriteReading } from './permissionModeWriteCapability.js';
|
|
5
|
+
import type { PermissionMode } from './permissionWireCaps.js';
|
|
6
|
+
export type { PermissionModeReceiptView };
|
|
7
|
+
type AdmissionFold = (typeof SDK_PERMISSION_MODE_ADMISSION_FOLDS)[number];
|
|
8
|
+
type PersistMiss = (typeof SDK_PERMISSION_MODE_PERSIST_MISSES)[number];
|
|
9
|
+
export interface PermissionModeAdmissionView {
|
|
10
|
+
readonly requested: string;
|
|
11
|
+
readonly admitted: string;
|
|
12
|
+
readonly foldedBy: AdmissionFold;
|
|
13
|
+
}
|
|
14
|
+
export type RunPermissionModeSwitchOutcome = {
|
|
15
|
+
readonly kind: 'switched';
|
|
16
|
+
readonly taskId: string;
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly permissionMode: PermissionModeReceiptView;
|
|
19
|
+
readonly admission?: PermissionModeAdmissionView;
|
|
20
|
+
readonly persisted: true;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'switched';
|
|
23
|
+
readonly taskId: string;
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly permissionMode: PermissionModeReceiptView;
|
|
26
|
+
readonly admission?: PermissionModeAdmissionView;
|
|
27
|
+
readonly persisted: false;
|
|
28
|
+
readonly persistMiss: PersistMiss;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'not-running';
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: 'not-found';
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'refused';
|
|
35
|
+
readonly status: number;
|
|
36
|
+
readonly errorCode?: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'unconfirmed';
|
|
39
|
+
readonly why: 'transport' | 'server-error' | 'unreadable-answer';
|
|
40
|
+
readonly status?: number;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: 'unavailable';
|
|
43
|
+
};
|
|
44
|
+
export interface RunPermissionModeSwitchRuns {
|
|
45
|
+
setPermissionMode(taskId: string, mode: PermissionMode, opts?: {
|
|
46
|
+
session?: string;
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}): Promise<object>;
|
|
49
|
+
}
|
|
50
|
+
export interface RunPermissionModeSwitchOptions {
|
|
51
|
+
readonly permissionModeWrite?: PermissionModeWriteReading | undefined;
|
|
52
|
+
readonly sessionId?: string | undefined;
|
|
53
|
+
readonly signal?: AbortSignal | undefined;
|
|
54
|
+
}
|
|
55
|
+
export declare const PERMISSION_MODE_SWITCH_RESULT_KEYS: readonly ["taskId", "sessionId", "status", "permissionMode", "admission", "persisted", "persistMiss"];
|
|
56
|
+
export declare function setRunPermissionMode(runs: RunPermissionModeSwitchRuns | null | undefined, taskId: string, mode: PermissionMode, opts?: RunPermissionModeSwitchOptions): Promise<RunPermissionModeSwitchOutcome>;
|
|
57
|
+
export declare function runPermissionModeSwitchLine(outcome: RunPermissionModeSwitchOutcome, lang?: UiLanguage): string;
|