@sema-agent/client-core 0.92.0 → 0.93.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 (125) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +25 -16
  3. package/dist/adapt/arms.js +2 -0
  4. package/dist/adapt/toolCards.js +6 -4
  5. package/dist/adapt.d.ts +1 -1
  6. package/dist/adapt.js +1 -1
  7. package/dist/adapter/runStream.js +3 -1
  8. package/dist/agentsWireCaps.js +1 -1
  9. package/dist/approvalDecidedByCapability.d.ts +16 -0
  10. package/dist/approvalDecidedByCapability.js +47 -0
  11. package/dist/approvalsStreamLiveCapability.js +3 -3
  12. package/dist/askSurvivesStandingAnswer.d.ts +1 -1
  13. package/dist/centerWiringCapability.js +1 -1
  14. package/dist/cloudConfigWireCaps.js +5 -5
  15. package/dist/compensations.js +2 -2
  16. package/dist/decideReceipt.d.ts +2 -0
  17. package/dist/decideReceipt.js +34 -9
  18. package/dist/detachWire.d.ts +2 -14
  19. package/dist/detachWire.js +1 -47
  20. package/dist/deviceExecutorManagementCapability.d.ts +6 -2
  21. package/dist/deviceExecutorManagementCapability.js +61 -15
  22. package/dist/engineConfigDrift.d.ts +20 -0
  23. package/dist/engineConfigDrift.js +54 -0
  24. package/dist/engineErrorCodes.d.ts +3 -0
  25. package/dist/engineErrorCodes.js +12 -4
  26. package/dist/engineInlineTaskStats.d.ts +1 -0
  27. package/dist/engineNoticeCodes.d.ts +2 -2
  28. package/dist/engineWireFor.d.ts +22 -0
  29. package/dist/engineWireFor.js +154 -9
  30. package/dist/engineWireSdk.d.ts +0 -1
  31. package/dist/engineWireSdk.js +0 -3
  32. package/dist/executionLaneCapability.js +2 -2
  33. package/dist/fileHistoryCaptureCapability.d.ts +1 -1
  34. package/dist/fileHistoryCaptureCapability.js +1 -6
  35. package/dist/fileHistoryLegNotice.d.ts +1 -1
  36. package/dist/fileHistoryLegNotice.js +1 -6
  37. package/dist/gateVocabulary.d.ts +1 -5
  38. package/dist/gateVocabulary.js +4 -22
  39. package/dist/generated/engineFactTables.d.ts +4 -3
  40. package/dist/generated/engineFactTables.js +10 -0
  41. package/dist/generated/engineNoticeTables.d.ts +1 -1
  42. package/dist/generated/engineNoticeTables.js +1 -1
  43. package/dist/goalStopHook.d.ts +1 -8
  44. package/dist/goalStopHook.js +2 -58
  45. package/dist/headlessPermissionModeWire.d.ts +3 -2
  46. package/dist/headlessPermissionModeWire.js +46 -5
  47. package/dist/hitl/approvalResolution.d.ts +3 -2
  48. package/dist/hitl/approvalResolution.js +13 -0
  49. package/dist/hitl/askParkRowRouting.js +2 -2
  50. package/dist/hitl/frameRouter.js +7 -2
  51. package/dist/hitl/gateLedger.d.ts +1 -0
  52. package/dist/hitl/gateLedger.js +3 -0
  53. package/dist/hitl/hitlBridge.d.ts +1 -0
  54. package/dist/hitl/hitlBridge.js +6 -1
  55. package/dist/hitl/hitlHostSurface.d.ts +6 -2
  56. package/dist/hitl/hitlHostSurface.js +29 -3
  57. package/dist/hitl/parkResolver.js +22 -4
  58. package/dist/hitl/persistedRulesWire.js +29 -17
  59. package/dist/hitl/planReviewWire.d.ts +0 -2
  60. package/dist/hitl/planReviewWire.js +1 -18
  61. package/dist/hitl/sessionPolicyDeliverable.js +3 -3
  62. package/dist/hitl/sessionPolicyWire.d.ts +2 -0
  63. package/dist/hitl/sessionPolicyWire.js +7 -4
  64. package/dist/hitl/toolApprovalWire.d.ts +32 -7
  65. package/dist/hitl/toolApprovalWire.js +282 -59
  66. package/dist/hooksWireCaps.d.ts +1 -1
  67. package/dist/hooksWireCaps.js +1 -1
  68. package/dist/index.d.ts +7 -2
  69. package/dist/index.js +6 -2
  70. package/dist/inheritEnvWire.d.ts +1 -4
  71. package/dist/inheritEnvWire.js +0 -18
  72. package/dist/limitsWire.d.ts +0 -6
  73. package/dist/limitsWire.js +0 -38
  74. package/dist/mcpProbeCapability.js +1 -1
  75. package/dist/mcpProbeWire.js +10 -12
  76. package/dist/mcpReconnect.d.ts +5 -0
  77. package/dist/mcpReconnect.js +28 -0
  78. package/dist/memoryComplianceCapability.js +2 -2
  79. package/dist/memoryOriginCapability.js +2 -2
  80. package/dist/memoryVerbsWire.js +14 -13
  81. package/dist/ownKey.d.ts +2 -0
  82. package/dist/ownKey.js +15 -0
  83. package/dist/peerLaneCapability.js +2 -2
  84. package/dist/permissionModesCapability.d.ts +16 -0
  85. package/dist/permissionModesCapability.js +71 -0
  86. package/dist/permissionRuleIssue.js +1 -1
  87. package/dist/permissionRulesWriteCapability.js +2 -2
  88. package/dist/permissionWireCaps.d.ts +2 -0
  89. package/dist/permissionWireCaps.js +8 -1
  90. package/dist/permissionsAllowGrantCapability.js +7 -2
  91. package/dist/postureKnob.js +1 -1
  92. package/dist/protocolSegment.d.ts +2 -0
  93. package/dist/protocolSegment.js +3 -0
  94. package/dist/readFacePosture.js +1 -1
  95. package/dist/resumeRefusalCopy.d.ts +1 -0
  96. package/dist/resumeRefusalCopy.js +26 -3
  97. package/dist/retryStatus.js +2 -1
  98. package/dist/rewindArchiveCapability.js +2 -2
  99. package/dist/runTerminal.d.ts +16 -0
  100. package/dist/runTerminal.js +32 -3
  101. package/dist/seam.d.ts +1 -0
  102. package/dist/sessionPolicyCapability.js +1 -1
  103. package/dist/sessionSyncFileHistoryCapability.js +2 -2
  104. package/dist/skillToolListsCapability.js +1 -1
  105. package/dist/sqlEngineCapability.js +1 -1
  106. package/dist/steerPriorityCapability.js +1 -1
  107. package/dist/storePostureCapability.js +2 -2
  108. package/dist/supportFloor.d.ts +1 -0
  109. package/dist/supportFloor.js +13 -3
  110. package/dist/taskRequestWords.d.ts +4 -2
  111. package/dist/taskRequestWords.js +1 -1
  112. package/dist/toolHistoryBadTurn.js +1 -2
  113. package/dist/toolResult.d.ts +2 -1
  114. package/dist/toolResult.js +33 -3
  115. package/dist/userInvokedSkillsCapability.js +1 -1
  116. package/dist/webSearchBackendCapability.js +2 -2
  117. package/dist/webSearchWireCaps.d.ts +1 -1
  118. package/dist/webSearchWireCaps.js +40 -2
  119. package/dist/wireFailureShape.js +2 -2
  120. package/dist/wireRefusalCopy.d.ts +9 -3
  121. package/dist/wireRefusalCopy.js +41 -4
  122. package/dist/workflowsJournalCapability.js +1 -1
  123. package/dist/writeProtectionCapability.js +1 -1
  124. package/docs/INTEGRATION-CLIENTS.md +999 -15
  125. package/package.json +2 -2
@@ -1,8 +1,5 @@
1
1
  import { type InheritEnvWord, type TaskRequestLike } from './request/taskRequest.js';
2
- export declare const INHERIT_ENV_MIN_SERVER: readonly [number, number, number];
3
- export type InheritEnvServerSupport = 'supported' | 'unsupported' | 'unknown';
4
- export declare function inheritEnvServerSupport(version: string | undefined): InheritEnvServerSupport;
5
- export type InheritEnvWithheldCause = 'server_version_unknown' | 'server_too_old';
2
+ export type InheritEnvWithheldCause = 'server_version_unknown';
6
3
  export interface InheritEnvWithheld {
7
4
  readonly cause: InheritEnvWithheldCause;
8
5
  readonly word: InheritEnvWord;
@@ -1,20 +1,6 @@
1
1
  import { INHERIT_ENV_WORDS, isInheritEnvWord } from './request/taskRequest.js';
2
2
  import { settingsPermissionBucketRefusalFromError } from './wireRefusalCopy.js';
3
3
  import { engineVersionTier } from './supportFloor.js';
4
- export const INHERIT_ENV_MIN_SERVER = Object.freeze([7, 106, 0]);
5
- export function inheritEnvServerSupport(version) {
6
- const mm = typeof version === 'string' ? /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version) : null;
7
- if (!mm)
8
- return 'unknown';
9
- const got = [Number(mm[1]), Number(mm[2]), Number(mm[3])];
10
- for (let i = 0; i < 3; i++) {
11
- const g = got[i];
12
- const need = INHERIT_ENV_MIN_SERVER[i];
13
- if (g !== need)
14
- return g > need ? 'supported' : 'unsupported';
15
- }
16
- return 'supported';
17
- }
18
4
  const EMPTY_DECLARATION = Object.freeze({ field: Object.freeze({}) });
19
5
  export function inheritEnvDeclaration(word, serverVersion) {
20
6
  if (word === undefined || word === null)
@@ -27,10 +13,6 @@ export function inheritEnvDeclaration(word, serverVersion) {
27
13
  return Object.freeze({ field: Object.freeze({}), withheld: Object.freeze({ cause: 'server_version_unknown', word }) });
28
14
  }
29
15
  const WITHHELD_NOTICES = Object.freeze({
30
- server_too_old: Object.freeze({
31
- all: "Not sent with this request: this engine is too old to accept the setting that lets the shell inherit this machine's whole environment, so this run's shell gets the default environment, with credential-like variables removed.",
32
- scrub: 'Not sent with this request: this engine is too old to accept the default-environment setting, so it was left out; that has the same effect, because the default environment (credential-like variables removed) is what this run gets anyway.',
33
- }),
34
16
  server_version_unknown: Object.freeze({
35
17
  all: "Not sent with this request: the engine's version is not known yet, and an engine that does not know the setting that lets the shell inherit this machine's whole environment refuses the whole request, so it was held back; this run's shell gets the default environment, with credential-like variables removed, and on an engine that does know the setting, leaving it out also clears an earlier whole-environment choice for this session.",
36
18
  scrub: "Not sent with this request: the engine's version is not known yet, so the default-environment setting was left out; that has the same effect, because leaving it out also gives this run the default environment (credential-like variables removed).",
@@ -8,7 +8,6 @@ export declare const MAX_TURNS_MIN = 1;
8
8
  export declare const MAX_TURNS_MAX: number;
9
9
  export declare const MAX_TOKENS_MIN = 1;
10
10
  export declare const MAX_TOKENS_MAX: number;
11
- export declare const LIMITS_WIRE_MIN_ENGINE: readonly [number, number, number];
12
11
  export interface HeadlessLimits {
13
12
  maxWalltimeMs?: number;
14
13
  maxTurns?: number;
@@ -32,8 +31,3 @@ export type HeadlessEnvLimitsResult = {
32
31
  };
33
32
  export declare function headlessLimitsFromEnv(env?: EnvLike): HeadlessEnvLimitsResult;
34
33
  export declare function limitsForPrint(argv: string[], env?: EnvLike): LimitsParseResult;
35
- export declare function versionSupportsLimits(v: string | undefined): boolean;
36
- export declare function engineLimitsSupportWarning(baseUrl: string, flaggedFlags: string[], opts?: {
37
- fetchImpl?: typeof fetch;
38
- timeoutMs?: number;
39
- }): Promise<string | undefined>;
@@ -10,7 +10,6 @@ export const MAX_TURNS_MAX = Number.MAX_SAFE_INTEGER;
10
10
  export const MAX_TOKENS_MIN = 1;
11
11
  export const MAX_TOKENS_MAX = Number.MAX_SAFE_INTEGER;
12
12
  const SEC_TO_MS = 1_000;
13
- export const LIMITS_WIRE_MIN_ENGINE = [1, 196, 0];
14
13
  const DEADLINE_USAGE = `sema: --deadline expects a whole number of seconds between ${DEADLINE_SEC_MIN} and ${DEADLINE_SEC_MAX}.\n` +
15
14
  ' --deadline <sec> wall-clock budget for this run (sema superset — no upstream equivalent):\n' +
16
15
  ' reaching it ends the run LOUDLY (failed / limits.max_walltime_exceeded)\n' +
@@ -112,40 +111,3 @@ export function limitsForPrint(argv, env = hostEnv()) {
112
111
  const merged = { ...(fromEnv.limits ?? {}), ...(parsed.limits ?? {}) };
113
112
  return { ok: true, ...(hasAnyLimit(merged) ? { limits: merged } : {}), flaggedFlags: parsed.flaggedFlags };
114
113
  }
115
- export function versionSupportsLimits(v) {
116
- const m = v ? /^(\d+)\.(\d+)\.(\d+)/.exec(v) : null;
117
- if (!m)
118
- return false;
119
- const [a, b, c] = [Number(m[1]), Number(m[2]), Number(m[3])];
120
- const [ma, mb, mc] = LIMITS_WIRE_MIN_ENGINE;
121
- if (a !== ma)
122
- return a > ma;
123
- if (b !== mb)
124
- return b > mb;
125
- return c >= mc;
126
- }
127
- export async function engineLimitsSupportWarning(baseUrl, flaggedFlags, opts) {
128
- const fetchImpl = opts?.fetchImpl ?? fetch;
129
- const controller = new AbortController();
130
- const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1500);
131
- try {
132
- const res = await fetchImpl(`${baseUrl.replace(/\/+$/, '')}/health`, {
133
- method: 'GET',
134
- signal: controller.signal,
135
- });
136
- if (!res.ok)
137
- return undefined;
138
- const body = (await res.json());
139
- const version = typeof body?.version === 'string' ? body.version : undefined;
140
- if (versionSupportsLimits(version))
141
- return undefined;
142
- const flags = flaggedFlags.length > 0 ? flaggedFlags.join('/') : '--deadline/--max-turns/--max-tokens';
143
- return `[sema] engine ${version ?? '(unversioned, pre-1.96)'} does not support ${flags} (needs ≥1.196.0); the flag will be ignored by the engine`;
144
- }
145
- catch {
146
- return undefined;
147
- }
148
- finally {
149
- clearTimeout(timer);
150
- }
151
- }
@@ -34,7 +34,7 @@ export function mcpProbeDoctorDetail(reading) {
34
34
  switch (reading.kind) {
35
35
  case 'unobserved':
36
36
  return ('per-server MCP status face not observed — the engine reports whether it has one on /v1/capabilities; ' +
37
- 'this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)');
37
+ 'this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)');
38
38
  case 'not_reported':
39
39
  return ('per-server MCP status face not reported by this engine — this binary predates the face, so that path is not expected to exist here ' +
40
40
  '(asking it usually answers with a machine-readable route-not-found, though a timeout or an uncoded gateway answer still says nothing either way); ' +
@@ -111,32 +111,32 @@ function narrowProbeFace(body) {
111
111
  let servers;
112
112
  try {
113
113
  if (!plainObject(body))
114
- return { ok: false, why: 'result_unreadable' };
114
+ return { ok: false, why: 'result_unreadable', what: 'the body is not a plain object' };
115
115
  probedAt = body.probedAt;
116
116
  ttlSec = body.ttlSec;
117
117
  servers = body.servers;
118
118
  }
119
119
  catch {
120
- return { ok: false, why: 'result_unreadable' };
120
+ return { ok: false, why: 'result_unreadable', what: 'the body threw when read' };
121
121
  }
122
122
  if (!nonEmptyString(probedAt))
123
- return { ok: false, why: 'result_unreadable' };
123
+ return { ok: false, why: 'result_unreadable', what: 'the reading time is missing or empty' };
124
124
  if (typeof ttlSec !== 'number' || !Number.isFinite(ttlSec) || ttlSec < 0)
125
- return { ok: false, why: 'result_unreadable' };
125
+ return { ok: false, why: 'result_unreadable', what: 'the reuse window is not a non-negative number' };
126
126
  const raw = snapshotArray(servers);
127
127
  if (raw === null)
128
- return { ok: false, why: 'result_unreadable' };
128
+ return { ok: false, why: 'result_unreadable', what: 'the per-server rows are not a readable list' };
129
129
  let rows;
130
130
  try {
131
131
  rows = projectMcpSection(raw);
132
132
  }
133
133
  catch {
134
- return { ok: false, why: 'result_unreadable' };
134
+ return { ok: false, why: 'result_unreadable', what: 'the per-server rows could not be read' };
135
135
  }
136
136
  if (rows === undefined)
137
- return { ok: false, why: 'result_unreadable' };
137
+ return { ok: false, why: 'result_unreadable', what: 'none of the per-server rows could be read' };
138
138
  if (rows.length !== raw.length)
139
- return { ok: false, why: 'result_unreadable' };
139
+ return { ok: false, why: 'result_unreadable', what: 'some of the per-server rows could not be read' };
140
140
  return { ok: true, face: { probedAt, ttlSec, servers: rows } };
141
141
  }
142
142
  const FACE_ABSENT_MESSAGE = 'this engine reports no per-server MCP status face on this deployment, so nothing was sent; ' +
@@ -172,8 +172,7 @@ export async function readMcpCapabilities(facade, call, opts) {
172
172
  }
173
173
  const narrowed = narrowProbeFace(res);
174
174
  if (!narrowed.ok) {
175
- return unknownBy(narrowed.why, 'the per-server MCP status call answered 2xx with a body this client cannot stand behind ' +
176
- '(the reading time, the reuse window, or the per-server rows) — refusing to present it as a reading, ' +
175
+ return unknownBy(narrowed.why, `the per-server MCP status call answered 2xx with a body this client cannot stand behind (${narrowed.what}) — refusing to present it as a reading, ` +
177
176
  'because a normal-looking empty list is exactly the false answer this face exists to remove');
178
177
  }
179
178
  return { status: 'face', face: narrowed.face };
@@ -206,8 +205,7 @@ export async function probeMcpServers(facade, call, opts) {
206
205
  }
207
206
  const narrowed = narrowProbeFace(res);
208
207
  if (!narrowed.ok) {
209
- return unknownBy(narrowed.why, 'the declaration probe answered 2xx with a body this client cannot stand behind ' +
210
- '(the reading time, the reuse window, or the per-server rows) — refusing to present it as a reading');
208
+ return unknownBy(narrowed.why, `the declaration probe answered 2xx with a body this client cannot stand behind (${narrowed.what}) — refusing to present it as a reading`);
211
209
  }
212
210
  if (narrowed.face.servers.length !== submitted.length) {
213
211
  return unknownBy('index_misaligned', `the declaration probe answered with ${narrowed.face.servers.length} row(s) for ${submitted.length} declaration(s) — ` +
@@ -1,5 +1,6 @@
1
1
  export declare const MCP_RECONNECT_OUTCOMES: readonly string[];
2
2
  export declare const MCP_RECONNECT_TRANSACTION_NOTICE: string;
3
+ export declare const MCP_RECONNECT_UNSETTLED_NOTICE: string;
3
4
  export interface McpReconnectStatusView {
4
5
  name: string;
5
6
  status: string;
@@ -51,6 +52,10 @@ export type McpReconnectVerdict = {
51
52
  readonly reason: 'capability' | 'route' | 'feature';
52
53
  } | {
53
54
  readonly kind: 'not_found';
55
+ } | {
56
+ readonly kind: 'unsettled';
57
+ readonly errorCode: 'state.mcp_reconnect_unsettled';
58
+ readonly error?: string;
54
59
  } | {
55
60
  readonly kind: 'failed';
56
61
  readonly error: unknown;
@@ -2,6 +2,9 @@ import { capForDisplay } from './fleetTaskDesc.js';
2
2
  export const MCP_RECONNECT_OUTCOMES = Object.freeze(['accepted', 'refused', 'unsupported']);
3
3
  export const MCP_RECONNECT_TRANSACTION_NOTICE = 'Reconnecting closes the current connection before dialing again. On a healthy server this is a transaction, ' +
4
4
  'not a refresh: a failed re-dial leaves the server down and withdraws its tools from the model until a later re-dial succeeds.';
5
+ export const MCP_RECONNECT_UNSETTLED_NOTICE = 'The MCP re-dial did not settle within the time this request waits, so there is no result yet: the re-dial was not cancelled and may still be running. ' +
6
+ "Check the server's state before re-dialing it again — a second request that arrives after the dial has settled closes the connection and dials once more.";
7
+ const UNSETTLED_CODE = 'state.mcp_reconnect_unsettled';
5
8
  const RECONNECT_TEXT_MAX = 200;
6
9
  const RECONNECT_WORD_MAX = 40;
7
10
  const SERVER_NAME_MAX = 190;
@@ -88,6 +91,27 @@ export function projectMcpReconnectResult(body) {
88
91
  }
89
92
  return view;
90
93
  }
94
+ function unsettledVerdict(carrier) {
95
+ let text;
96
+ try {
97
+ const r = carrier;
98
+ text = nonEmpty(r.error) ? r.error : r.message;
99
+ }
100
+ catch {
101
+ text = undefined;
102
+ }
103
+ return nonEmpty(text)
104
+ ? { kind: 'unsettled', errorCode: UNSETTLED_CODE, error: capForDisplay(text, RECONNECT_TEXT_MAX) }
105
+ : { kind: 'unsettled', errorCode: UNSETTLED_CODE };
106
+ }
107
+ function isUnsettledBody(raw) {
108
+ try {
109
+ return isRecord(raw) && !('outcome' in raw) && raw.errorCode === UNSETTLED_CODE;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
91
115
  export function classifyMcpReconnectFailure(e) {
92
116
  let code;
93
117
  try {
@@ -96,6 +120,8 @@ export function classifyMcpReconnectFailure(e) {
96
120
  catch {
97
121
  return { kind: 'failed', error: e };
98
122
  }
123
+ if (code === UNSETTLED_CODE)
124
+ return unsettledVerdict(e);
99
125
  if (code === 'steering.invalid_content')
100
126
  return { kind: 'invalid' };
101
127
  if (code === 'steering.not_running')
@@ -123,6 +149,8 @@ export async function reconnectMcpServer(client, sessionId, server, opts) {
123
149
  return { kind: 'failed', error: opts.signal.reason ?? new Error('mcpReconnect: aborted before the verb was called') };
124
150
  }
125
151
  const raw = await client.sessions.mcpReconnect(sessionId, { server }, opts?.signal ? { signal: opts.signal } : undefined);
152
+ if (isUnsettledBody(raw))
153
+ return unsettledVerdict(raw);
126
154
  const view = projectMcpReconnectResult(raw);
127
155
  if (view === undefined)
128
156
  return { kind: 'failed', error: new Error('mcpReconnect: malformed body') };
@@ -35,9 +35,9 @@ export function memoryComplianceVerbsAvailable(reading) {
35
35
  export function memoryComplianceDoctorDetail(reading) {
36
36
  switch (reading.kind) {
37
37
  case 'unobserved':
38
- return 'memory compliance face not observed — the engine reports it on /v1/capabilities (memoryCompliance); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
38
+ return 'memory compliance face not observed — the engine reports it on /v1/capabilities (memoryCompliance); this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)';
39
39
  case 'not_reported':
40
- return 'memory compliance face not reported by this engine — only newer engines advertise it; this does not say whether the provenance and erasure endpoints exist, probe them directly';
40
+ return 'memory compliance face not reported by this engine; this does not say whether the provenance and erasure endpoints exist, probe them directly';
41
41
  case 'absent':
42
42
  return `memory compliance face off on this deployment — the entry-provenance and erasure endpoints are unavailable (${MEMORY_ENGINE_REQUIRED_CODE}); the engine does not say which half is missing (a memory backend that does not own the engine control plane, or an empty operator roster), so do not guess`;
43
43
  case 'present':
@@ -35,9 +35,9 @@ export function memoryOriginVerbsAvailable(reading) {
35
35
  export function memoryOriginDoctorDetail(reading) {
36
36
  switch (reading.kind) {
37
37
  case 'unobserved':
38
- return 'external-origin face not observed — the engine reports it on /v1/capabilities (memoryOrigin); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
38
+ return 'external-origin face not observed — the engine reports it on /v1/capabilities (memoryOrigin); this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)';
39
39
  case 'not_reported':
40
- return 'external-origin face not reported by this engine — only newer engines advertise it; this does not say whether the origin listing and clearance endpoints exist, probe them directly';
40
+ return 'external-origin face not reported by this engine; this does not say whether the origin listing and clearance endpoints exist, probe them directly';
41
41
  case 'absent':
42
42
  return `external-origin face off on this deployment — the origin listing and clearance endpoints are unavailable (${MEMORY_ENGINE_REQUIRED_CODE}); the engine does not say which half is missing (a memory backend that does not own the engine control plane, or an empty operator roster), so do not guess`;
43
43
  case 'present':
@@ -176,7 +176,7 @@ function narrowProvenanceAccount(body, entryId) {
176
176
  let exclusionCode;
177
177
  try {
178
178
  if (!plainObject(body))
179
- return { ok: false, why: 'result_unreadable' };
179
+ return { ok: false, why: 'result_unreadable', what: 'the body is not a plain object' };
180
180
  v = body.v;
181
181
  id = body.id;
182
182
  binding = body.binding;
@@ -191,27 +191,28 @@ function narrowProvenanceAccount(body, entryId) {
191
191
  exclusionCode = plainObject(exclusion) ? exclusion.code : undefined;
192
192
  }
193
193
  catch {
194
- return { ok: false, why: 'result_unreadable' };
194
+ return { ok: false, why: 'result_unreadable', what: 'the body threw when read' };
195
195
  }
196
196
  if (typeof v !== 'number' || !Number.isFinite(v))
197
- return { ok: false, why: 'result_unreadable' };
197
+ return { ok: false, why: 'result_unreadable', what: 'the envelope version is missing or not a number' };
198
198
  if (v !== PROVENANCE_ACCOUNT_VERSION)
199
- return { ok: false, why: 'unsupported_version' };
199
+ return { ok: false, why: 'unsupported_version', what: 'the envelope version is newer than this client knows' };
200
200
  if (!nonEmptyString(id))
201
- return { ok: false, why: 'result_unreadable' };
201
+ return { ok: false, why: 'result_unreadable', what: 'the entry id is missing or empty' };
202
202
  if (!plainObject(binding) || !nonEmptyString(bindingState))
203
- return { ok: false, why: 'result_unreadable' };
203
+ return { ok: false, why: 'result_unreadable', what: 'the binding is missing or has no usable state' };
204
204
  if (!plainObject(custody) || !nonEmptyString(custodyState) || !Array.isArray(custodyEvents)) {
205
- return { ok: false, why: 'result_unreadable' };
206
- }
207
- if (!Array.isArray(contributors) || !nonEmptyString(contentState)) {
208
- return { ok: false, why: 'result_unreadable' };
205
+ return { ok: false, why: 'result_unreadable', what: 'the custody chain is missing or has no usable state or event list' };
209
206
  }
207
+ if (!Array.isArray(contributors))
208
+ return { ok: false, why: 'result_unreadable', what: 'the contributor list is missing or not a list' };
209
+ if (!nonEmptyString(contentState))
210
+ return { ok: false, why: 'result_unreadable', what: 'the content state is missing or empty' };
210
211
  if (hasExclusion && !(plainObject(exclusion) && nonEmptyString(exclusionCode))) {
211
- return { ok: false, why: 'result_unreadable' };
212
+ return { ok: false, why: 'result_unreadable', what: 'the exclusion record is present but unreadable' };
212
213
  }
213
214
  if (id !== entryId)
214
- return { ok: false, why: 'identity_mismatch' };
215
+ return { ok: false, why: 'identity_mismatch', what: 'the account is for a different entry id' };
215
216
  return { ok: true, account: body };
216
217
  }
217
218
  export async function readEntryProvenance(facade, call, opts) {
@@ -238,7 +239,7 @@ export async function readEntryProvenance(facade, call, opts) {
238
239
  ? 'the provenance account came back stamped with a newer envelope version than this client knows — refusing to reinterpret it; upgrade the client'
239
240
  : narrowed.why === 'identity_mismatch'
240
241
  ? 'the provenance account came back for a different entry id than the one that was asked about — refusing to present it as this entry account'
241
- : 'the provenance call answered 2xx with a body this client cannot branch on (envelope, entry id, binding or custody) — refusing to present it as an account');
242
+ : `the provenance call answered 2xx with a body this client cannot branch on (${narrowed.what}) — refusing to present it as an account`);
242
243
  }
243
244
  return { status: 'account', account: narrowed.account };
244
245
  }
package/dist/ownKey.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export declare function putOwnKey<T>(table: Record<string, T>, key: string, value: T): void;
2
2
  export declare function readOwnNonEmptyString(v: object | null | undefined, key: string): string | undefined;
3
+ export declare function readOwnDataNonEmptyString(v: object | null | undefined, key: string): string | undefined;
4
+ export declare function readOwnDataValue<T>(v: object | null | undefined, key: string): T | undefined;
package/dist/ownKey.js CHANGED
@@ -7,3 +7,18 @@ export function readOwnNonEmptyString(v, key) {
7
7
  const raw = v[key];
8
8
  return typeof raw === 'string' && raw !== '' ? raw : undefined;
9
9
  }
10
+ export function readOwnDataNonEmptyString(v, key) {
11
+ if (typeof v !== 'object' || v === null)
12
+ return undefined;
13
+ const d = Object.getOwnPropertyDescriptor(v, key);
14
+ if (d === undefined || !Object.hasOwn(d, 'value'))
15
+ return undefined;
16
+ const raw = d.value;
17
+ return typeof raw === 'string' && raw !== '' ? raw : undefined;
18
+ }
19
+ export function readOwnDataValue(v, key) {
20
+ if (typeof v !== 'object' || v === null)
21
+ return undefined;
22
+ const d = Object.getOwnPropertyDescriptor(v, key);
23
+ return d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
24
+ }
@@ -35,9 +35,9 @@ export function peerLaneAvailable(reading) {
35
35
  export function peerLaneDoctorDetail(reading) {
36
36
  switch (reading.kind) {
37
37
  case 'unobserved':
38
- return 'cross-session lane not observed — the engine reports it on /v1/capabilities (peerLane); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
38
+ return 'cross-session lane not observed — the engine reports it on /v1/capabilities (peerLane); this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)';
39
39
  case 'not_reported':
40
- return 'cross-session lane not reported by this engine — the engine predates the lane, so this deployment has no cross-session discovery or delivery (the key itself is the version signal; addressing another session by id is refused as unknown)';
40
+ return 'cross-session lane not reported by this engine — its capabilities reading carries no entry for the lane, so whether this deployment has cross-session discovery or delivery is unknown to this client';
41
41
  case 'absent':
42
42
  return 'no cross-session lane on this deployment — cross-session discovery and delivery (listing other sessions, addressing one by id) are unavailable';
43
43
  case 'present':
@@ -0,0 +1,16 @@
1
+ export type PermissionModesReading = {
2
+ kind: 'unobserved';
3
+ } | {
4
+ kind: 'not_reported';
5
+ } | {
6
+ kind: 'present';
7
+ modes: readonly string[];
8
+ };
9
+ export declare function projectPermissionModesCapability(caps: unknown): PermissionModesReading | undefined;
10
+ export declare function noteEngineCapsForPermissionModes(baseUrl: string, caps: unknown, opts?: {
11
+ generation?: number;
12
+ }): void;
13
+ export declare function observedPermissionModes(baseUrl?: string | undefined): PermissionModesReading;
14
+ export declare function permissionModeAdmitted(reading: PermissionModesReading | undefined, mode: string): boolean;
15
+ export declare function forgetPermissionModesReading(baseUrl: string | undefined): void;
16
+ export declare function __resetPermissionModesReadingsForTests(): void;
@@ -0,0 +1,71 @@
1
+ import { engineWireTarget } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ import { readOwnDataValue } from './ownKey.js';
4
+ function readModesStrict(v) {
5
+ try {
6
+ if (!Array.isArray(v))
7
+ return undefined;
8
+ const length = v.length;
9
+ if (length === 0)
10
+ return undefined;
11
+ const modes = [];
12
+ for (let i = 0; i < length; i++) {
13
+ const item = readOwnDataValue(v, String(i));
14
+ if (typeof item !== 'string' || item.length === 0)
15
+ return undefined;
16
+ modes.push(item);
17
+ }
18
+ return modes;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ export function projectPermissionModesCapability(caps) {
25
+ try {
26
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
27
+ return undefined;
28
+ if (!Object.hasOwn(caps, 'permissionModes'))
29
+ return { kind: 'not_reported' };
30
+ const v = caps.permissionModes;
31
+ if (v === undefined)
32
+ return { kind: 'not_reported' };
33
+ const modes = readModesStrict(v);
34
+ return modes === undefined ? undefined : { kind: 'present', modes };
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ const reader = createEngineCapReader({
41
+ name: 'permissionModes',
42
+ project: projectPermissionModesCapability,
43
+ makeUnobserved: () => ({ kind: 'unobserved' }),
44
+ });
45
+ export function noteEngineCapsForPermissionModes(baseUrl, caps, opts) {
46
+ reader.note(baseUrl, caps, opts);
47
+ }
48
+ export function observedPermissionModes(baseUrl = engineWireTarget()?.baseUrl) {
49
+ return reader.observed(baseUrl);
50
+ }
51
+ export function permissionModeAdmitted(reading, mode) {
52
+ try {
53
+ if (typeof mode !== 'string' || mode.length === 0)
54
+ return false;
55
+ if (reading === null || reading === undefined || typeof reading !== 'object' || Array.isArray(reading))
56
+ return false;
57
+ if (readOwnDataValue(reading, 'kind') !== 'present')
58
+ return false;
59
+ const modes = readModesStrict(readOwnDataValue(reading, 'modes'));
60
+ return modes !== undefined && modes.includes(mode);
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ export function forgetPermissionModesReading(baseUrl) {
67
+ reader.forget(baseUrl);
68
+ }
69
+ export function __resetPermissionModesReadingsForTests() {
70
+ reader.__resetForTests();
71
+ }
@@ -23,7 +23,7 @@ const ISSUE_SENTENCES = Object.freeze({
23
23
  'invalid.paren': 'this rule is invalid: its parentheses do not parse (escape a literal one as \\( or \\))',
24
24
  'invalid.param_split': 'this rule is invalid: its parentheses do not hold a parameter rule written name:pattern',
25
25
  'invalid.primary_field': "this rule is invalid: it patterns the tool's primary field, which matches the whole trimmed string rather than a prefix",
26
- 'invalid.cap_exceeded': 'this rule set is invalid: it is over one of the deployment caps (rule count, rule length, pattern length, or star count)',
26
+ 'invalid.cap_exceeded': 'this rule set is invalid: it is over one of the deployment caps (rule count, rule length, pattern length, or star count); this client cannot tell which from the code alone',
27
27
  'invalid.behavior': 'this rule is invalid: its behavior is neither deny nor ask, so it would never fire',
28
28
  'invalid.command_rule': 'this rule is invalid: the command pattern was rejected by the command-rule validator',
29
29
  'invalid.entry_type': 'this allow list is invalid: it must be a list of rule strings, and it or one of its entries is not a string',
@@ -31,11 +31,11 @@ export function permissionRulesWriteAvailable(reading) {
31
31
  export function permissionRulesWriteDoctorDetail(reading) {
32
32
  switch (reading.kind) {
33
33
  case 'unobserved':
34
- return 'permission-rule write entry not observed — the engine reports it on /v1/capabilities (permissionRulesWrite); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
34
+ return 'permission-rule write entry not observed — the engine reports it on /v1/capabilities (permissionRulesWrite); this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)';
35
35
  case 'not_reported':
36
36
  return 'permission-rule write entry not reported by this engine — this binary predates the entry itself; hide it rather than calling it';
37
37
  case 'absent':
38
- return 'permission-rule write entry unreachable for this call — either the rules lane is not open on this deployment, or this call cannot bring a verifiable identity';
38
+ return 'permission-rule write entry unreachable for this call — either the rules lane is not open on this deployment, or this call cannot bring a verifiable identity; the engine reports one bit for both, so this client cannot tell which';
39
39
  case 'present':
40
40
  return 'permission-rule write entry reachable for this call — a per-call attempt still answers for itself whether the direction, text, and scope are accepted';
41
41
  }
@@ -1,8 +1,10 @@
1
1
  import { type TaskRequestPermissionMode } from './taskRequestWords.js';
2
+ import { type PermissionModesReading } from './permissionModesCapability.js';
2
3
  export type PermissionMode = TaskRequestPermissionMode;
3
4
  export interface PermissionModeField {
4
5
  permissionMode?: PermissionMode;
5
6
  }
6
7
  export declare function permissionModeField(mode: string | undefined, opts?: {
7
8
  stampDefault?: boolean;
9
+ permissionModes?: PermissionModesReading | undefined;
8
10
  }): PermissionModeField;
@@ -1,9 +1,16 @@
1
1
  import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
2
- const KNOWN = new Set(PERMISSION_MODE_WORDS);
2
+ import { permissionModeAdmitted } from './permissionModesCapability.js';
3
+ const BIT_GATED_MODE = 'dontAsk';
4
+ const KNOWN = new Set(PERMISSION_MODE_WORDS.filter((w) => w !== BIT_GATED_MODE));
3
5
  export function permissionModeField(mode, opts) {
4
6
  if (typeof mode === 'string' && mode !== 'default' && KNOWN.has(mode)) {
5
7
  return { permissionMode: mode };
6
8
  }
9
+ if (mode === BIT_GATED_MODE) {
10
+ if (permissionModeAdmitted(opts?.permissionModes, mode))
11
+ return { permissionMode: mode };
12
+ return opts?.stampDefault === true ? { permissionMode: 'default' } : {};
13
+ }
7
14
  if (opts?.stampDefault === true && (mode === undefined || mode === 'default')) {
8
15
  return { permissionMode: 'default' };
9
16
  }
@@ -65,7 +65,12 @@ export function permissionsAllowGrantDoctorDetail(reading, settingsRead) {
65
65
  return 'allow rules sent to the engine — it reads them as rules, not as an exclusive list: a matching call counts as answered for, and the engine still raises its own prompts where it requires them';
66
66
  }
67
67
  if (gate === 'settings_read_not_clean') {
68
- return 'allow rules from settings files kept on this side — this engine reads allow rules as rules, but the settings read that produced them was not reported clean (a settings file was unreadable or had a problem, or no read health was given), so they are not sent; command-line and session allow rules are still sent, and matching approvals are still answered here';
68
+ const why = settingsRead === 'partial'
69
+ ? 'was reported partial (at least one settings file carried a bad field)'
70
+ : settingsRead === 'unreadable'
71
+ ? 'was reported unreadable (at least one settings file could not be read)'
72
+ : 'came with no recognized read health';
73
+ return `allow rules from settings files kept on this side — this engine reads allow rules as rules, but the settings read that produced them ${why}, so they are not sent; command-line and session allow rules are still sent, and matching approvals are still answered here`;
69
74
  }
70
75
  const kind = ownReadingField(reading, 'kind');
71
76
  if (kind === 'present' && ownReadingField(reading, 'grants') === false) {
@@ -74,7 +79,7 @@ export function permissionsAllowGrantDoctorDetail(reading, settingsRead) {
74
79
  if (kind === 'not_reported') {
75
80
  return 'allow rules kept on this side — this engine does not say how it reads allow rules; sending them to an engine that reads them as an exclusive list would block every tool not on it, so they are not sent and matching approvals are still answered here';
76
81
  }
77
- return 'allow rules kept on this side — no usable capabilities reading is cached for this engine (none received, or the last one was unreadable), so allow rules are not sent and matching approvals are still answered here';
82
+ return 'allow rules kept on this side — no usable capabilities reading is cached for this engine (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which), so allow rules are not sent and matching approvals are still answered here';
78
83
  }
79
84
  export function forgetPermissionsAllowGrantReading(baseUrl) {
80
85
  reader.forget(baseUrl);
@@ -87,7 +87,7 @@ export function postureKnobDetail(knob, opts) {
87
87
  ? `${label} ${word} (source: ${source} — ${note})`
88
88
  : `${label} ${word} (source: ${source})`;
89
89
  }
90
- return `${label} not reported by this engine (an engine below 7.67.0, or a response this end could not parse)`;
90
+ return `${label} not reported by this engine (this end found no usable reading for it: the engine does not send one, or what it sent could not be parsed here; this end cannot tell which)`;
91
91
  }
92
92
  const _postureKnobShapePin = (w) => w;
93
93
  void _postureKnobShapePin;
@@ -1,5 +1,7 @@
1
1
  export type ProtocolSegment = {
2
2
  readonly kind: 'ok';
3
+ } | {
4
+ readonly kind: 'empty';
3
5
  } | {
4
6
  readonly kind: 'unminted';
5
7
  readonly minted?: string;
@@ -1,5 +1,8 @@
1
1
  import { hasCoreValuePort, mintNamespacePrefix, protocolOf } from './coreValuePorts.js';
2
+ import { CORE_PROTOCOL_PREFIXES } from './generated/toolNameTables.js';
2
3
  export function protocolSegmentOf(name) {
4
+ if (CORE_PROTOCOL_PREFIXES.some((prefix) => name.startsWith(prefix) && name.slice(prefix.length).split('__')[0] === ''))
5
+ return { kind: 'empty' };
3
6
  const ns = protocolOf(name);
4
7
  if (ns === undefined)
5
8
  return hasCoreValuePort('protocolOf') ? { kind: 'ok' } : { kind: 'unknown' };
@@ -30,7 +30,7 @@ export function readFacePostureDetail(view, opts) {
30
30
  if (!opts.reachable) {
31
31
  return "read face not observed (this end could not read the engine's diagnostics)";
32
32
  }
33
- return 'read face not reported by this engine (an engine below 7.65.0, or a response this end could not parse)';
33
+ return 'read face not reported by this engine (this end found no usable reading for it: the engine does not send one, or what it sent could not be parsed here; this end cannot tell which)';
34
34
  }
35
35
  export function readFaceDisagreement(capFace, posture) {
36
36
  if (posture === undefined)
@@ -41,6 +41,7 @@ export interface ParkedResumeStartupDetail {
41
41
  code: string;
42
42
  cause: ParkedResumeStartupCause;
43
43
  taskId?: string;
44
+ startupCause?: string;
44
45
  resendable: false;
45
46
  denyStaysOpen: true;
46
47
  }