@sema-agent/client-core 0.81.1 → 0.82.1

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +12 -8
  3. package/dist/adapt/toolCards.d.ts +15 -0
  4. package/dist/adapt/toolCards.js +34 -19
  5. package/dist/adapter/activeRunSelfHeal.d.ts +3 -0
  6. package/dist/adapter/activeRunSelfHeal.js +42 -9
  7. package/dist/adapter/downstream/eventToSdkMessage.js +7 -4
  8. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  9. package/dist/adapter/downstream/terminalToSdkResult.js +44 -5
  10. package/dist/adapter/runStream.js +33 -4
  11. package/dist/adapter/types.d.ts +2 -0
  12. package/dist/agentsWireCaps.d.ts +1 -0
  13. package/dist/agentsWireCaps.js +110 -29
  14. package/dist/decideFailureNote.d.ts +1 -0
  15. package/dist/decideFailureNote.js +8 -0
  16. package/dist/engineErrorCodes.d.ts +1 -0
  17. package/dist/engineErrorCodes.js +1 -0
  18. package/dist/engineNoticeCodes.js +2 -0
  19. package/dist/executionLaneCapability.js +3 -0
  20. package/dist/fileHistoryCaptureCapability.d.ts +18 -0
  21. package/dist/fileHistoryCaptureCapability.js +58 -0
  22. package/dist/gateOutcome.js +22 -2
  23. package/dist/hitl/askGateWire.d.ts +1 -1
  24. package/dist/hitl/askGateWire.js +1 -1
  25. package/dist/hitl/hitlHostSurface.js +6 -4
  26. package/dist/hitl/parkResolver.d.ts +1 -0
  27. package/dist/hitl/parkResolver.js +11 -2
  28. package/dist/hitl/planReviewWire.js +14 -1
  29. package/dist/hitl/toolApprovalWire.js +3 -2
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.js +4 -0
  32. package/dist/modelIdentityResolvability.d.ts +31 -0
  33. package/dist/modelIdentityResolvability.js +145 -0
  34. package/dist/printToolResultFrame.js +15 -1
  35. package/dist/registryQuotaUsage.d.ts +44 -0
  36. package/dist/registryQuotaUsage.js +111 -0
  37. package/dist/resumeRefusalCopy.d.ts +12 -0
  38. package/dist/resumeRefusalCopy.js +45 -1
  39. package/dist/rewindArchiveCapability.d.ts +24 -0
  40. package/dist/rewindArchiveCapability.js +125 -0
  41. package/dist/seam.d.ts +2 -0
  42. package/dist/toolResult.d.ts +2 -2
  43. package/dist/toolResult.js +7 -6
  44. package/dist/toolRoster.d.ts +12 -0
  45. package/dist/toolRoster.js +35 -0
  46. package/dist/wireErrorTriage.d.ts +4 -0
  47. package/dist/wireErrorTriage.js +59 -5
  48. package/dist/wireFailureShape.js +27 -22
  49. package/docs/INTEGRATION-CLIENTS.md +192 -13
  50. package/package.json +3 -3
@@ -0,0 +1,111 @@
1
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
2
+ function amountOf(holder, key) {
3
+ if (!Object.hasOwn(holder, key))
4
+ return { kind: 'unknown' };
5
+ const v = holder[key];
6
+ if (v === null)
7
+ return { kind: 'unlimited' };
8
+ if (typeof v === 'number' && Number.isFinite(v))
9
+ return { kind: 'value', value: v };
10
+ return { kind: 'unknown' };
11
+ }
12
+ function usedOf(holder) {
13
+ const v = holder['used'];
14
+ if (typeof v === 'number' && Number.isFinite(v))
15
+ return { kind: 'value', value: v };
16
+ return { kind: 'unknown' };
17
+ }
18
+ function boolOf(holder, key) {
19
+ const v = holder[key];
20
+ return typeof v === 'boolean' ? v : undefined;
21
+ }
22
+ function resetAtOf(holder, exhausted) {
23
+ if (!Object.hasOwn(holder, 'resetAt'))
24
+ return { kind: 'unknown' };
25
+ const v = holder['resetAt'];
26
+ if (typeof v === 'string' && v !== '')
27
+ return { kind: 'at', iso: v };
28
+ if (v === null)
29
+ return exhausted === false ? { kind: 'not-exhausted' } : { kind: 'unknown' };
30
+ return { kind: 'unknown' };
31
+ }
32
+ function windowOf(body, window) {
33
+ const raw = body[window];
34
+ if (!isObj(raw)) {
35
+ return {
36
+ window,
37
+ limit: { kind: 'unknown' },
38
+ used: { kind: 'unknown' },
39
+ remaining: { kind: 'unknown' },
40
+ exhausted: undefined,
41
+ resetAt: { kind: 'unknown' },
42
+ isEstimate: undefined,
43
+ };
44
+ }
45
+ const exhausted = boolOf(raw, 'exhausted');
46
+ return {
47
+ window,
48
+ limit: amountOf(raw, 'limit'),
49
+ used: usedOf(raw),
50
+ remaining: amountOf(raw, 'remaining'),
51
+ exhausted,
52
+ resetAt: resetAtOf(raw, exhausted),
53
+ isEstimate: boolOf(raw, 'isEstimate'),
54
+ };
55
+ }
56
+ export function projectRegistryQuotaUsage(body) {
57
+ if (!isObj(body))
58
+ return undefined;
59
+ const principal = typeof body['principal'] === 'string' ? body['principal'] : undefined;
60
+ const tq = body['tokenQuota'];
61
+ const configured = !Object.hasOwn(body, 'tokenQuota')
62
+ ? 'unknown'
63
+ : tq === null
64
+ ? 'not-configured'
65
+ : isObj(tq)
66
+ ? 'configured'
67
+ : 'unknown';
68
+ const source = isObj(tq) && typeof tq['source'] === 'string' ? tq['source'] : undefined;
69
+ const lastRaw = body['lastResetAt'];
70
+ const lastResetAt = !Object.hasOwn(body, 'lastResetAt')
71
+ ? { kind: 'unknown' }
72
+ : lastRaw === null
73
+ ? { kind: 'never' }
74
+ : typeof lastRaw === 'string' && lastRaw !== ''
75
+ ? { kind: 'at', iso: lastRaw }
76
+ : { kind: 'unknown' };
77
+ return {
78
+ principal,
79
+ configured,
80
+ source,
81
+ fiveHour: windowOf(body, 'fiveHour'),
82
+ weekly: windowOf(body, 'weekly'),
83
+ lastResetAt,
84
+ };
85
+ }
86
+ export function quotaAmountText(amount) {
87
+ switch (amount.kind) {
88
+ case 'value':
89
+ return String(amount.value);
90
+ case 'unlimited':
91
+ return 'no cap';
92
+ case 'unknown':
93
+ return 'unknown';
94
+ }
95
+ }
96
+ export function registryQuotaDoctorDetail(reading) {
97
+ if (reading === undefined) {
98
+ return 'token quota not readable — the quota response could not be read as an object, so nothing is known about this principal (this is not the same as "no quota configured")';
99
+ }
100
+ switch (reading.configured) {
101
+ case 'not-configured':
102
+ return 'no token quota configured for this principal on this instance — requests are not capped by a token quota here, so a missing number is the truth rather than a zero';
103
+ case 'configured': {
104
+ const five = quotaAmountText(reading.fiveHour.remaining);
105
+ const week = quotaAmountText(reading.weekly.remaining);
106
+ return `token quota configured${reading.source === undefined ? '' : ` by ${reading.source}`} — remaining this five-hour window: ${five}; remaining this week: ${week} (estimates)`;
107
+ }
108
+ case 'unknown':
109
+ return 'token quota not reported in a readable shape — the response carried no readable quota field, so whether this principal has a quota is unknown; do not render it as "none"';
110
+ }
111
+ }
@@ -7,6 +7,18 @@ export interface ResumeRefusalDetail {
7
7
  }
8
8
  export declare function resumeRefusalFromError(err: unknown): ResumeRefusalDetail | null;
9
9
  export declare function resumeRefusalContent(detail: ResumeRefusalDetail): string;
10
+ export interface ResumePolicyBlockDetail {
11
+ blockedBy: {
12
+ kind: 'named';
13
+ code: string;
14
+ } | {
15
+ kind: 'absent';
16
+ } | {
17
+ kind: 'unreadable';
18
+ };
19
+ }
20
+ export declare function resumePolicyBlockFromError(err: unknown): ResumePolicyBlockDetail | null;
21
+ export declare function resumePolicyBlockContent(detail: ResumePolicyBlockDetail): string;
10
22
  export interface ResumeReopenDetail {
11
23
  code: string;
12
24
  reason: 'env_failed' | 'tool_unavailable' | 'tool_contract_mismatch';
@@ -1,5 +1,6 @@
1
1
  import { readErrorNumber, readErrorString } from './wireFailureShape.js';
2
- import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, } from './engineErrorCodes.js';
2
+ import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, RESUME_POLICY_FOLD_CODE, } from './engineErrorCodes.js';
3
+ import { capForDisplay } from './fleetTaskDesc.js';
3
4
  import { resumeRetryLaterFromError } from './wireErrorTriage.js';
4
5
  export const RESUME_REFUSAL_CODES = Object.freeze([
5
6
  RESUME_PLACEMENT_MISMATCH,
@@ -39,6 +40,49 @@ export function resumeRefusalContent(detail) {
39
40
  'The engine supplied no wait, so this refusal does not say whether the token stays redeemable');
40
41
  return parts.join(' · ');
41
42
  }
43
+ export function resumePolicyBlockFromError(err) {
44
+ if (typeof err !== 'object' || err === null)
45
+ return null;
46
+ const o = err;
47
+ let outer;
48
+ try {
49
+ outer = o['errorCode'];
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ if (outer !== RESUME_POLICY_FOLD_CODE)
55
+ return null;
56
+ const judge = (v) => ({
57
+ blockedBy: typeof v === 'string' && v.length > 0 ? { kind: 'named', code: v } : { kind: 'unreadable' },
58
+ });
59
+ try {
60
+ const top = o['blockedBy'];
61
+ if (top !== undefined)
62
+ return judge(top);
63
+ const extra = o['extra'];
64
+ if (typeof extra === 'object' && extra !== null && !Array.isArray(extra) && Object.hasOwn(extra, 'blockedBy')) {
65
+ return judge(extra['blockedBy']);
66
+ }
67
+ return { blockedBy: { kind: 'absent' } };
68
+ }
69
+ catch {
70
+ return { blockedBy: { kind: 'unreadable' } };
71
+ }
72
+ }
73
+ export function resumePolicyBlockContent(detail) {
74
+ const b = detail.blockedBy;
75
+ const reason = b.kind === 'named'
76
+ ? `The check that refused it: ${capForDisplay(b.code, 80)}`
77
+ : b.kind === 'absent'
78
+ ? 'The refusal did not name a more specific check'
79
+ : 'The refusal carried a more specific reason that could not be read';
80
+ return [
81
+ 'The engine refused to resume this parked work at its admission check (the engine reports that its admission policy changed since the work was first accepted)',
82
+ reason,
83
+ "This refusal does not say whether anything was consumed before it or whether a retry would pass — check the task's current state before deciding again, and ask whoever operates this deployment if it keeps happening",
84
+ ].join(' · ');
85
+ }
42
86
  export function resumeReopenFromError(err) {
43
87
  if (typeof err !== 'object' || err === null)
44
88
  return null;
@@ -0,0 +1,24 @@
1
+ export type RewindArchiveBit = 'on' | 'off' | 'not_reported' | 'malformed';
2
+ export type RewindArchiveReading = {
3
+ kind: 'unobserved';
4
+ } | {
5
+ kind: 'present';
6
+ conversationAnchor: RewindArchiveBit;
7
+ forkRestore: RewindArchiveBit;
8
+ codeOnly: RewindArchiveBit;
9
+ restoreFilesEpoch: RewindArchiveBit;
10
+ };
11
+ export type RewindArchiveMode = 'both' | 'code';
12
+ export type RewindCodeArchive = 'available' | 'unavailable' | 'unknown';
13
+ export declare function projectRewindArchiveCapability(caps: unknown): RewindArchiveReading | undefined;
14
+ export declare function noteEngineCapsForRewindArchive(baseUrl: string, caps: unknown, opts?: {
15
+ generation?: number;
16
+ }): void;
17
+ export declare function observedRewindArchive(baseUrl?: string | undefined): RewindArchiveReading;
18
+ export declare function rewindCodeArchiveAvailability(reading: RewindArchiveReading, mode: RewindArchiveMode): RewindCodeArchive;
19
+ export declare function rewindArchiveDoctorDetail(reading: RewindArchiveReading, mode: RewindArchiveMode): string;
20
+ export declare function rewindConversationAvailability(reading: RewindArchiveReading): RewindCodeArchive;
21
+ export declare function rewindConversationDoctorDetail(reading: RewindArchiveReading): string;
22
+ export declare function rewindRestoreFilesEpochDetail(reading: RewindArchiveReading): string;
23
+ export declare function forgetRewindArchiveReading(baseUrl: string | undefined): void;
24
+ export declare function __resetRewindArchiveReadingsForTests(): void;
@@ -0,0 +1,125 @@
1
+ import { engineWireTarget } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ const CAPS_KEYS = {
4
+ conversationAnchor: 'resumeAt',
5
+ forkRestore: 'rewindFiles',
6
+ codeOnly: 'rewindFilesTo',
7
+ restoreFilesEpoch: 'restoreFiles',
8
+ };
9
+ const _capsKeyPin = true;
10
+ void _capsKeyPin;
11
+ function bitOf(caps, key) {
12
+ if (!Object.hasOwn(caps, key))
13
+ return 'not_reported';
14
+ const v = caps[key];
15
+ if (v === undefined)
16
+ return 'not_reported';
17
+ if (v === true)
18
+ return 'on';
19
+ if (v === false)
20
+ return 'off';
21
+ return 'malformed';
22
+ }
23
+ export function projectRewindArchiveCapability(caps) {
24
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
25
+ return undefined;
26
+ return {
27
+ kind: 'present',
28
+ conversationAnchor: bitOf(caps, CAPS_KEYS.conversationAnchor),
29
+ forkRestore: bitOf(caps, CAPS_KEYS.forkRestore),
30
+ codeOnly: bitOf(caps, CAPS_KEYS.codeOnly),
31
+ restoreFilesEpoch: bitOf(caps, CAPS_KEYS.restoreFilesEpoch),
32
+ };
33
+ }
34
+ const reader = createEngineCapReader({
35
+ name: 'rewindArchive',
36
+ project: projectRewindArchiveCapability,
37
+ makeUnobserved: () => ({ kind: 'unobserved' }),
38
+ });
39
+ export function noteEngineCapsForRewindArchive(baseUrl, caps, opts) {
40
+ reader.note(baseUrl, caps, opts);
41
+ }
42
+ export function observedRewindArchive(baseUrl = engineWireTarget()?.baseUrl) {
43
+ return reader.observed(baseUrl);
44
+ }
45
+ export function rewindCodeArchiveAvailability(reading, mode) {
46
+ if (reading.kind !== 'present')
47
+ return 'unknown';
48
+ const bits = mode === 'both' ? [reading.conversationAnchor, reading.forkRestore] : [reading.codeOnly];
49
+ if (bits.some((b) => b === 'off'))
50
+ return 'unavailable';
51
+ if (bits.every((b) => b === 'on'))
52
+ return 'available';
53
+ return 'unknown';
54
+ }
55
+ export function rewindArchiveDoctorDetail(reading, mode) {
56
+ const lane = mode === 'both' ? 'rewind with code restore' : 'code-only rewind';
57
+ if (reading.kind !== 'present') {
58
+ return `${lane} not observed — the engine reports it on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether the code archive can be restored is unknown`;
59
+ }
60
+ if (mode === 'both' && reading.conversationAnchor === 'off') {
61
+ return `${lane} off on this deployment (no conversation anchors) — rewinding to a message cannot run here at all, with or without the code`;
62
+ }
63
+ const bit = mode === 'both' ? reading.forkRestore : reading.codeOnly;
64
+ if (mode === 'both' && bit === 'on' && reading.conversationAnchor !== 'on') {
65
+ return `${lane}: the file history is on, but this engine does not report conversation anchors in a readable way, so whether a rewind to a message can run is unknown`;
66
+ }
67
+ switch (bit) {
68
+ case 'on':
69
+ return `${lane} on — the engine keeps a file history for this deployment and restores the tracked set on request`;
70
+ case 'off':
71
+ return mode === 'both'
72
+ ? `${lane} off on this deployment (no file history store) — the working tree cannot be restored here; whether the conversation alone can be rewound is a separate reading`
73
+ : `${lane} off on this deployment — it needs both a file history store and conversation anchors, and the engine reports at least one missing (it does not say which)`;
74
+ case 'not_reported':
75
+ return `${lane} not reported by this engine — only newer engines advertise it, so this does not say whether the code archive can be restored`;
76
+ case 'malformed':
77
+ return `${lane} reported in a shape this reader cannot read — the value is neither true nor false, so the answer is unknown, not "no"`;
78
+ }
79
+ }
80
+ export function rewindConversationAvailability(reading) {
81
+ if (reading.kind !== 'present')
82
+ return 'unknown';
83
+ if (reading.conversationAnchor === 'on')
84
+ return 'available';
85
+ if (reading.conversationAnchor === 'off')
86
+ return 'unavailable';
87
+ return 'unknown';
88
+ }
89
+ export function rewindConversationDoctorDetail(reading) {
90
+ const lane = 'rewind to a message';
91
+ if (reading.kind !== 'present') {
92
+ return `${lane} not observed — the engine reports conversation anchors on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether the conversation can be rewound is unknown`;
93
+ }
94
+ switch (reading.conversationAnchor) {
95
+ case 'on':
96
+ return `${lane} on — this deployment keeps conversation anchors, so the conversation can be forked back to an earlier message`;
97
+ case 'off':
98
+ return `${lane} off on this deployment (no conversation anchors) — the conversation cannot be moved back to an earlier message here`;
99
+ case 'not_reported':
100
+ return `conversation anchors not reported by this engine — only newer engines advertise them, so this does not say whether the conversation can be rewound`;
101
+ case 'malformed':
102
+ return `conversation anchors reported in a shape this reader cannot read — the value is neither true nor false, so the answer is unknown, not "no"`;
103
+ }
104
+ }
105
+ export function rewindRestoreFilesEpochDetail(reading) {
106
+ if (reading.kind !== 'present') {
107
+ return 'restore-files request spelling not observed — this process has no usable capabilities reading for this engine';
108
+ }
109
+ switch (reading.restoreFilesEpoch) {
110
+ case 'on':
111
+ return 'this engine announces the restore-files request key (tracked-set restore semantics)';
112
+ case 'off':
113
+ return 'this engine does not announce the restore-files request key; the client sends it anyway because the older spelling is rejected outright by current engines';
114
+ case 'not_reported':
115
+ return 'this engine does not report the restore-files epoch bit (older engine); the client sends the current spelling regardless';
116
+ case 'malformed':
117
+ return 'the restore-files epoch bit is reported in an unreadable shape; the client sends the current spelling regardless';
118
+ }
119
+ }
120
+ export function forgetRewindArchiveReading(baseUrl) {
121
+ reader.forget(baseUrl);
122
+ }
123
+ export function __resetRewindArchiveReadingsForTests() {
124
+ reader.__resetForTests();
125
+ }
package/dist/seam.d.ts CHANGED
@@ -130,6 +130,7 @@ export type ChromeEvent = {
130
130
  engineUsage?: EngineTurnUsage;
131
131
  usageMissing?: true;
132
132
  stopReason?: string;
133
+ responseModel?: string;
133
134
  } | {
134
135
  kind: 'subagent_turn_usage';
135
136
  laneProof: LaneProof;
@@ -140,6 +141,7 @@ export type ChromeEvent = {
140
141
  engineUsage?: EngineTurnUsage;
141
142
  usageMissing?: true;
142
143
  stopReason?: string;
144
+ responseModel?: string;
143
145
  } | {
144
146
  kind: 'plan_review_park';
145
147
  laneProof: LaneProof;
@@ -79,8 +79,8 @@ export interface ToolResultBody {
79
79
  degraded?: typeof TOOL_RESULT_DEGRADED_WIRE_NO_OUTPUT;
80
80
  }
81
81
  export declare function degradedToolResultBody(isError: boolean): ToolResultBody;
82
- export declare function wireOutputToBody(toolName: string, output: unknown, isError: boolean, truncated: boolean, flatten: FlattenWireOutput, structured?: unknown): ToolResultBody;
83
- export declare function structuredToToolUseResult(structured: unknown, modelText?: string): {
82
+ export declare function wireOutputToBody(toolName: string, output: unknown, isError: boolean, truncated: boolean, flatten: FlattenWireOutput, structured?: unknown, leaderLedger?: boolean): ToolResultBody;
83
+ export declare function structuredToToolUseResult(structured: unknown, modelText?: string, leaderLedger?: boolean): {
84
84
  toolUseResult: unknown;
85
85
  isError?: boolean;
86
86
  } | null;
@@ -229,7 +229,7 @@ export const TOOL_RESULT_DEGRADED_WIRE_NO_OUTPUT = 'wire_carried_no_output';
229
229
  export function degradedToolResultBody(isError) {
230
230
  return { content: '', isError, degraded: TOOL_RESULT_DEGRADED_WIRE_NO_OUTPUT };
231
231
  }
232
- export function wireOutputToBody(toolName, output, isError, truncated, flatten, structured) {
232
+ export function wireOutputToBody(toolName, output, isError, truncated, flatten, structured, leaderLedger = true) {
233
233
  const text = flatten(output);
234
234
  const name = (toolName || '').toLowerCase();
235
235
  const sType = structuredDetailType(structured);
@@ -320,7 +320,7 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
320
320
  }
321
321
  const wfEnvelope = parseWorkflowPollEnvelope(text);
322
322
  if (wfEnvelope) {
323
- if (WF_TERMINAL_STATUSES.has(wfEnvelope.status))
323
+ if (leaderLedger && WF_TERMINAL_STATUSES.has(wfEnvelope.status))
324
324
  markEngineWorkflowNotified(wfEnvelope.taskId);
325
325
  return wrap({
326
326
  task_id: wfEnvelope.taskId,
@@ -340,7 +340,7 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
340
340
  }
341
341
  return { content: truncated ? `${text}\n…(truncated)` : text, isError };
342
342
  }
343
- export function structuredToToolUseResult(structured, modelText) {
343
+ export function structuredToToolUseResult(structured, modelText, leaderLedger = true) {
344
344
  if (typeof structured !== 'object' || structured === null)
345
345
  return null;
346
346
  const s = structured;
@@ -348,7 +348,7 @@ export function structuredToToolUseResult(structured, modelText) {
348
348
  if (typeof s[k] === 'string' && s[k].length > MAX_STRUCTURED_FIELD_CHARS)
349
349
  return null;
350
350
  }
351
- if (s.type === 'workflow-run' && typeof s.workflowRunId === 'string') {
351
+ if (leaderLedger && s.type === 'workflow-run' && typeof s.workflowRunId === 'string') {
352
352
  registerOutstandingWorkflowRun(s.workflowRunId);
353
353
  }
354
354
  switch (s.type) {
@@ -586,7 +586,8 @@ export function structuredToToolUseResult(structured, modelText) {
586
586
  if (typeof s.description !== 'string' || typeof s.prompt !== 'string') {
587
587
  return null;
588
588
  }
589
- registerOutstandingBgTask(agentId, s.description, s.prompt, receipt.seq);
589
+ if (leaderLedger)
590
+ registerOutstandingBgTask(agentId, s.description, s.prompt, receipt.seq);
590
591
  return {
591
592
  toolUseResult: {
592
593
  isAsync: true,
@@ -653,7 +654,7 @@ export function structuredToToolUseResult(structured, modelText) {
653
654
  const runErrorCode = typeof s.errorCode === 'string' && s.errorCode.length > 0 ? s.errorCode : undefined;
654
655
  let output = bodyText ?? '';
655
656
  if (taskType === 'workflow') {
656
- if (WF_TERMINAL_STATUSES.has(status))
657
+ if (leaderLedger && WF_TERMINAL_STATUSES.has(status))
657
658
  markEngineWorkflowNotified(taskId);
658
659
  const jsonSrc = output.length > 0 ? output : (modelText ?? '');
659
660
  output = projectWorkflowTaskOutput(jsonSrc);
@@ -38,6 +38,7 @@ export interface ToolRosterEntryView {
38
38
  contentOrigin?: string;
39
39
  contentOriginProvenance?: string;
40
40
  family?: string;
41
+ mountedBy?: string;
41
42
  pathTarget?: ToolRosterPathTargetView;
42
43
  renderHints?: ToolRosterRenderHintsView;
43
44
  ruleFace?: {
@@ -52,6 +53,17 @@ export interface ToolRosterView {
52
53
  entries: readonly ToolRosterEntryView[];
53
54
  }
54
55
  export declare function projectToolRoster(manifest: unknown): ToolRosterView | undefined;
56
+ export type HandsMountedReading = {
57
+ kind: 'mounted';
58
+ rows: number;
59
+ } | {
60
+ kind: 'not-mounted';
61
+ } | {
62
+ kind: 'unknown';
63
+ why: 'no-manifest' | 'no-roster' | 'roster-unreadable' | 'mount-condition-absent';
64
+ };
65
+ export declare function handsMountedFromManifest(manifest: unknown): HandsMountedReading;
66
+ export declare function handsMountedDetail(reading: HandsMountedReading): string;
55
67
  export declare function toolRosterNames(roster: ToolRosterView | undefined): readonly string[];
56
68
  export interface ToolShim {
57
69
  name: string;
@@ -64,6 +64,7 @@ function readEntry(raw) {
64
64
  ? { contentOriginProvenance: str(o.contentOriginProvenance) }
65
65
  : {}),
66
66
  ...(str(o.family) !== undefined ? { family: str(o.family) } : {}),
67
+ ...(str(o.mountedBy) !== undefined ? { mountedBy: str(o.mountedBy) } : {}),
67
68
  ...(readPathTarget(o.pathTarget) !== undefined
68
69
  ? { pathTarget: readPathTarget(o.pathTarget) }
69
70
  : {}),
@@ -119,6 +120,40 @@ export function projectToolRoster(manifest) {
119
120
  return undefined;
120
121
  return readRoster(o.tools);
121
122
  }
123
+ export function handsMountedFromManifest(manifest) {
124
+ const o = obj(manifest);
125
+ if (o === undefined)
126
+ return { kind: 'unknown', why: 'no-manifest' };
127
+ if (!('tools' in o))
128
+ return { kind: 'unknown', why: 'no-roster' };
129
+ const roster = readRoster(o.tools);
130
+ if (roster === undefined)
131
+ return { kind: 'unknown', why: 'roster-unreadable' };
132
+ if (roster.entries.length > 0 && roster.entries.every((e) => e.mountedBy === undefined)) {
133
+ return { kind: 'unknown', why: 'mount-condition-absent' };
134
+ }
135
+ const rows = roster.entries.filter((e) => e.mountedBy === 'hands').length;
136
+ return rows > 0 ? { kind: 'mounted', rows } : { kind: 'not-mounted' };
137
+ }
138
+ export function handsMountedDetail(reading) {
139
+ switch (reading.kind) {
140
+ case 'mounted':
141
+ return `this run was assembled with file-and-shell tools mounted (${reading.rows} of them)`;
142
+ case 'not-mounted':
143
+ return 'this leg of the run was assembled with NO built-in file-and-shell tools — the engine reports a roster and not one row was mounted by that condition, so on this leg the model had no tool of the engine\'s own for editing files or running commands (tools mounted by other conditions, such as external servers, and sub-runs delegated to other legs are not covered by this reading)';
144
+ case 'unknown':
145
+ switch (reading.why) {
146
+ case 'no-manifest':
147
+ return 'whether this run has file-and-shell tools is unknown — no assembly manifest was observed for it';
148
+ case 'no-roster':
149
+ return 'whether this run has file-and-shell tools is unknown — the manifest carries no tool roster (the static half never does; only an engine-minted effective manifest reports one)';
150
+ case 'roster-unreadable':
151
+ return 'whether this run has file-and-shell tools is unknown — the tool roster could not be read in the shape this reader knows';
152
+ case 'mount-condition-absent':
153
+ return 'whether this run has file-and-shell tools is unknown — this engine does not report which condition mounted each tool, so an empty count here would be a fact about the reader, not about the run';
154
+ }
155
+ }
156
+ }
122
157
  export function toolRosterNames(roster) {
123
158
  return roster?.entries.map((e) => e.name) ?? [];
124
159
  }
@@ -4,6 +4,7 @@ export type TurnWireErrorVerdict = {
4
4
  status: number;
5
5
  errorCode?: string;
6
6
  message: string;
7
+ storedRowWhere?: string;
7
8
  } | {
8
9
  kind: 'stream-ended-without-terminal';
9
10
  } | {
@@ -17,6 +18,9 @@ export type TurnWireErrorVerdict = {
17
18
  export declare function classifyTurnWireError(err: unknown): TurnWireErrorVerdict;
18
19
  export declare function isWireTransportError(err: unknown): boolean;
19
20
  export declare function isPreStreamDrainingReject(err: unknown): boolean;
21
+ export declare function effectiveWireErrorCode(err: unknown): string | undefined;
22
+ export declare function corruptStoredRowWhere(err: unknown): string | undefined;
23
+ export declare function corruptStoredRowContent(where: string): string;
20
24
  export declare function isResumeAtRejection(err: unknown): boolean;
21
25
  export declare function drainingRetryDelayMs(err: unknown, attempt: number, opts?: {
22
26
  ladder?: readonly number[];
@@ -1,5 +1,6 @@
1
- import { readErrorNumber, readErrorStringList } from './wireFailureShape.js';
2
- import { DRAINING_ERROR_CODE, RESUME_AT_ERROR_CODE_PREFIX, RESUME_RETRY_LATER_CODES, RESUME_USAGE_WINDOW_EXHAUSTED, SCENARIO_NOT_ALLOWED_ERROR_CODE, } from './engineErrorCodes.js';
1
+ import { readErrorNumber, readErrorString, readErrorStringList } from './wireFailureShape.js';
2
+ import { capForDisplay } from './fleetTaskDesc.js';
3
+ import { DRAINING_ERROR_CODE, RESUME_AT_ERROR_CODE_PREFIX, RESUME_POLICY_FOLD_CODE, RESUME_RETRY_LATER_CODES, RESUME_USAGE_WINDOW_EXHAUSTED, SCENARIO_NOT_ALLOWED_ERROR_CODE, } from './engineErrorCodes.js';
3
4
  export const WIRE_NETWORK_ERROR_PATTERN = /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|EHOSTUNREACH|ENETUNREACH|EPIPE|socket hang up|UND_ERR|terminated|other side closed/i;
4
5
  function wireErrorDetail(err) {
5
6
  const e = err;
@@ -13,7 +14,14 @@ export function classifyTurnWireError(err) {
13
14
  if (typeof e?.status === 'number') {
14
15
  const errorCode = typeof e.errorCode === 'string' && e.errorCode.length > 0 ? e.errorCode : undefined;
15
16
  const message = typeof e.message === 'string' && e.message.length > 0 ? e.message : String(err);
16
- return { kind: 'http', status: e.status, ...(errorCode !== undefined ? { errorCode } : {}), message };
17
+ const storedRowWhere = corruptStoredRowWhere(err);
18
+ return {
19
+ kind: 'http',
20
+ status: e.status,
21
+ ...(errorCode !== undefined ? { errorCode } : {}),
22
+ message,
23
+ ...(storedRowWhere !== undefined ? { storedRowWhere } : {}),
24
+ };
17
25
  }
18
26
  const errName = typeof err === 'object' && err !== null && typeof err.name === 'string'
19
27
  ? err.name
@@ -41,9 +49,35 @@ const RESUME_AT_TEXT_COMPAT = [
41
49
  { status: 404, marker: `(${RESUME_AT_ERROR_CODE_PREFIX}unknown_event)` },
42
50
  { status: 422, marker: `(${RESUME_AT_ERROR_CODE_PREFIX}no_session)` },
43
51
  ];
52
+ export function effectiveWireErrorCode(err) {
53
+ if (typeof err !== 'object' || err === null)
54
+ return undefined;
55
+ let outerRaw;
56
+ try {
57
+ outerRaw = err.errorCode;
58
+ }
59
+ catch {
60
+ outerRaw = undefined;
61
+ }
62
+ const outer = typeof outerRaw === 'string' && outerRaw.length > 0 ? outerRaw : undefined;
63
+ if (outer !== RESUME_POLICY_FOLD_CODE)
64
+ return outer;
65
+ const inner = readErrorString(err, 'blockedBy');
66
+ return inner !== undefined && inner.length > 0 ? inner : outer;
67
+ }
68
+ export function corruptStoredRowWhere(err) {
69
+ if (effectiveWireErrorCode(err) !== 'corrupt')
70
+ return undefined;
71
+ const where = readErrorString(err, 'where');
72
+ return where !== undefined && where.trim().length > 0 ? where : undefined;
73
+ }
74
+ export function corruptStoredRowContent(where) {
75
+ return `The engine could not read a stored record this session needs in order to continue. The unreadable row is ${capForDisplay(where, 200)}; whoever operates this engine has to repair or remove that row before this session can continue.`;
76
+ }
44
77
  export function isResumeAtRejection(err) {
45
78
  const e = err;
46
- if (typeof e?.errorCode === 'string' && e.errorCode.startsWith(RESUME_AT_ERROR_CODE_PREFIX))
79
+ const code = effectiveWireErrorCode(err);
80
+ if (code !== undefined && code.startsWith(RESUME_AT_ERROR_CODE_PREFIX))
47
81
  return true;
48
82
  const marker = RESUME_AT_TEXT_COMPAT.find((c) => c.status === e?.status)?.marker;
49
83
  if (marker === undefined)
@@ -62,7 +96,27 @@ export function scenarioDenyFromError(err) {
62
96
  if (typeof err !== 'object' || err === null)
63
97
  return null;
64
98
  const e = err;
65
- if (e.status !== 400 || e.errorCode !== SCENARIO_NOT_ALLOWED_ERROR_CODE)
99
+ let status;
100
+ try {
101
+ status = e.status;
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ if (status !== 400 && status !== 409)
107
+ return null;
108
+ if (status === 409) {
109
+ let outer;
110
+ try {
111
+ outer = e.errorCode;
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ if (outer !== RESUME_POLICY_FOLD_CODE)
117
+ return null;
118
+ }
119
+ if (effectiveWireErrorCode(err) !== SCENARIO_NOT_ALLOWED_ERROR_CODE)
66
120
  return null;
67
121
  const allowlist = (readErrorStringList(err, 'allowlist') ?? []).filter((x) => x.length > 0);
68
122
  return { allowlist };