@sema-agent/client-core 0.84.0 → 0.84.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 (38) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +12 -5
  3. package/dist/abortableSleep.d.ts +10 -0
  4. package/dist/abortableSleep.js +37 -0
  5. package/dist/adapter/downstream/terminalToSdkResult.d.ts +1 -0
  6. package/dist/adapter/downstream/terminalToSdkResult.js +10 -5
  7. package/dist/agentsWireCaps.js +8 -0
  8. package/dist/decideFailureNote.d.ts +2 -1
  9. package/dist/decideFailureNote.js +5 -3
  10. package/dist/detachWire.d.ts +1 -0
  11. package/dist/detachWire.js +13 -5
  12. package/dist/displayUntrusted.js +114 -18
  13. package/dist/engineErrorCodes.d.ts +2 -0
  14. package/dist/engineErrorCodes.js +3 -0
  15. package/dist/engineNoticeCodes.d.ts +30 -4
  16. package/dist/engineNoticeCodes.js +73 -5
  17. package/dist/engineWireSdk.d.ts +2 -0
  18. package/dist/hitl/askGateWire.d.ts +1 -1
  19. package/dist/hitl/askGateWire.js +1 -1
  20. package/dist/hitl/hitlHostSurface.js +1 -1
  21. package/dist/hitl/parkResolver.d.ts +1 -0
  22. package/dist/hitl/parkResolver.js +9 -3
  23. package/dist/hitl/planReviewWire.d.ts +30 -3
  24. package/dist/hitl/planReviewWire.js +139 -32
  25. package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
  26. package/dist/hitl/sessionPolicyDeliverable.js +14 -3
  27. package/dist/hitl/sessionPolicyWire.d.ts +40 -0
  28. package/dist/hitl/sessionPolicyWire.js +210 -0
  29. package/dist/hitl/toolApprovalWire.js +2 -2
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/liveInitToolFace.js +28 -10
  33. package/dist/resumeRefusalCopy.d.ts +11 -0
  34. package/dist/resumeRefusalCopy.js +39 -1
  35. package/dist/systemReminderTag.d.ts +5 -0
  36. package/dist/systemReminderTag.js +20 -9
  37. package/docs/INTEGRATION-CLIENTS.md +461 -16
  38. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { wireFailureShapeOf } from '../wireFailureShape.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
3
4
  const RULE_FIELD_PRESENCE = {
4
5
  toolAllow: true,
5
6
  toolDeny: true,
@@ -306,3 +307,212 @@ export function sessionPolicyTightenNotice(outcome) {
306
307
  }
307
308
  }
308
309
  }
310
+ function rosterMountedNames(roster) {
311
+ if (roster === undefined || roster === null)
312
+ return 'absent';
313
+ try {
314
+ if (typeof roster !== 'object' || Array.isArray(roster))
315
+ return 'unreadable';
316
+ const entries = roster.entries;
317
+ if (!Array.isArray(entries))
318
+ return 'unreadable';
319
+ const names = new Set();
320
+ const n = entries.length;
321
+ for (let i = 0; i < n; i++) {
322
+ if (!Object.hasOwn(entries, i))
323
+ return 'unreadable';
324
+ const row = entries[i];
325
+ if (typeof row !== 'object' || row === null)
326
+ return 'unreadable';
327
+ const name = row.name;
328
+ if (typeof name !== 'string' || name.length === 0)
329
+ return 'unreadable';
330
+ names.add(name);
331
+ const aliases = row.aliases;
332
+ if (aliases === undefined)
333
+ continue;
334
+ if (!Array.isArray(aliases))
335
+ return 'unreadable';
336
+ for (const a of aliases) {
337
+ if (typeof a !== 'string')
338
+ return 'unreadable';
339
+ names.add(a);
340
+ }
341
+ }
342
+ return names;
343
+ }
344
+ catch {
345
+ return 'unreadable';
346
+ }
347
+ }
348
+ function withoutRefusedEntries(prior, mounted) {
349
+ const REFUSABLE_NAME_FIELDS = ['toolDeny', 'toolAllow'];
350
+ const isRefusable = (f) => REFUSABLE_NAME_FIELDS.includes(f);
351
+ const next = {};
352
+ const removed = [];
353
+ for (const field of SESSION_POLICY_RULE_FIELDS) {
354
+ const list = Object.hasOwn(prior, field) ? prior[field] : undefined;
355
+ if (list === undefined)
356
+ continue;
357
+ if (!isRefusable(field)) {
358
+ next[field] = [...list];
359
+ continue;
360
+ }
361
+ const kept = [];
362
+ const seen = new Set();
363
+ for (const x of list) {
364
+ if (!isEngineRefusedToolName(x) || mounted.has(x)) {
365
+ kept.push(x);
366
+ continue;
367
+ }
368
+ if (seen.has(x))
369
+ continue;
370
+ seen.add(x);
371
+ removed.push({ field, rule: x });
372
+ }
373
+ next[field] = kept;
374
+ }
375
+ return { next, removed };
376
+ }
377
+ function removalFailed(failure, leg) {
378
+ const u = unknownFrom(failure, leg);
379
+ const message = u.message;
380
+ switch (u.why) {
381
+ case 'write_indeterminate':
382
+ return { kind: 'write_unconfirmed', why: 'write_indeterminate', ...(message !== undefined ? { message } : {}) };
383
+ case 'read_failed':
384
+ case 'read_unreadable':
385
+ return { kind: 'read_failed', why: u.why, ...(message !== undefined ? { message } : {}) };
386
+ case 'write_refused':
387
+ return { kind: 'write_refused', why: 'write_refused', ...(message !== undefined ? { message } : {}) };
388
+ case 'face_unavailable':
389
+ case 'session_not_found':
390
+ case 'session_ownerless':
391
+ case 'unauthorized':
392
+ case 'request_rejected':
393
+ case 'store_corrupt':
394
+ return leg === 'read'
395
+ ? { kind: 'read_failed', why: u.why, ...(message !== undefined ? { message } : {}) }
396
+ : { kind: 'write_refused', why: u.why, ...(message !== undefined ? { message } : {}) };
397
+ default:
398
+ return leg === 'read'
399
+ ? { kind: 'read_failed', why: 'read_failed', ...(message !== undefined ? { message } : {}) }
400
+ : { kind: 'write_unconfirmed', why: 'write_indeterminate', ...(message !== undefined ? { message } : {}) };
401
+ }
402
+ }
403
+ async function attemptRemoval(facade, sessionId, mounted, opts) {
404
+ const read = await readSessionPolicy(facade, sessionId, opts);
405
+ if (read.kind === 'failed')
406
+ return { done: removalFailed(read.failure, 'read') };
407
+ if (read.kind === 'malformed')
408
+ return { done: { kind: 'read_failed', why: 'read_unreadable', message: read.detail } };
409
+ const { next, removed } = withoutRefusedEntries(read.rules, mounted);
410
+ if (removed.length === 0)
411
+ return { done: { kind: 'nothing_to_remove', rev: read.rev } };
412
+ let receipt;
413
+ try {
414
+ receipt = await facade.putPolicy(sessionId, { rules: next, expectedRev: read.rev }, opts);
415
+ }
416
+ catch (e) {
417
+ const failure = classifySessionPolicyFailure(e);
418
+ if (failure.kind === 'loosen-forbidden')
419
+ return { done: { kind: 'loosen_forbidden', message: failure.message, refused: removed } };
420
+ if (failure.kind === 'cas-conflict')
421
+ return { retryAtRev: read.rev };
422
+ return { done: removalFailed(failure, 'write') };
423
+ }
424
+ let narrowed;
425
+ try {
426
+ narrowed = narrowStoredRecord(receipt);
427
+ }
428
+ catch {
429
+ narrowed = { ok: false, why: 'envelope', detail: 'policy write receipt could not be read' };
430
+ }
431
+ if (!narrowed.ok)
432
+ return { done: { kind: 'write_unconfirmed', why: 'write_receipt_unreadable', message: narrowed.detail } };
433
+ const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev) ??
434
+ (removed.some((e) => {
435
+ const r = Object.hasOwn(narrowed.rules, e.field) ? narrowed.rules[e.field] : undefined;
436
+ return r !== undefined && r.includes(e.rule);
437
+ })
438
+ ? 'the write receipt still holds an entry that was sent for removal'
439
+ : null);
440
+ if (mismatch !== null)
441
+ return { done: { kind: 'write_unconfirmed', why: 'write_receipt_mismatch', message: mismatch } };
442
+ return { done: { kind: 'removed', rev: narrowed.rev, removed } };
443
+ }
444
+ export async function removeRefusedSessionRules(facade, sessionId, roster, opts) {
445
+ const mounted = rosterMountedNames(roster);
446
+ if (typeof mounted === 'string')
447
+ return { kind: 'roster_required', why: mounted };
448
+ const first = await attemptRemoval(facade, sessionId, mounted, opts);
449
+ if ('done' in first)
450
+ return first.done;
451
+ hostLog('debug', 'sessionPolicyWire: the session rule record moved between this read and this removal — re-reading once and judging the other writer’s record again');
452
+ const second = await attemptRemoval(facade, sessionId, mounted, opts);
453
+ if ('done' in second)
454
+ return second.done;
455
+ return { kind: 'conflict', rev: second.retryAtRev };
456
+ }
457
+ export function sessionPolicyRemovalNotice(outcome) {
458
+ const generic = 'The session’s rule record may not have changed — re-read it before doing anything else.';
459
+ if (outcome === null || typeof outcome !== 'object')
460
+ return generic;
461
+ switch (outcome.kind) {
462
+ case 'roster_required':
463
+ return outcome.why === 'unreadable'
464
+ ? 'The tool roster supplied for this removal could not be read, so nothing was changed: without it an entry that looks refused may name a tool this deployment mounts itself, and removing it would lift a restriction that is in effect. Supply the tool roster exactly as read from a run’s assembly manifest and try again.'
465
+ : 'No tool roster was supplied for this removal, so nothing was changed: without it an entry that looks refused may name a tool this deployment mounts itself, and removing it would lift a restriction that is in effect. Supply the tool roster from a run of this session on this deployment (a run that fails at startup reports none) and try again.';
466
+ case 'removed': {
467
+ const list = Array.isArray(outcome.removed) ? outcome.removed : [];
468
+ const n = list.length;
469
+ const what = n === 1 ? '1 entry' : `${n} entries`;
470
+ const fields = new Set(list.map((e) => (typeof e === 'object' && e !== null ? e.field : undefined)));
471
+ const deny = fields.has('toolDeny')
472
+ ? ' Removing names from the deny list lifts no block on any tool mounted for this session’s runs; to block such a tool under its current name, add a new deny rule with that name.'
473
+ : '';
474
+ const allow = fields.has('toolAllow')
475
+ ? ' Removing names from the allow list allows nothing new: the tools those names were meant to allow stay outside it, and an allow list left empty allows no tool at all; to allow such a tool under its current name, add its current name to the allow list.'
476
+ : '';
477
+ return `Removed ${what} from this session’s rule record (now at version ${outcome.rev}): ${n === 1 ? 'it names' : 'each names'} a tool the engine refuses, and no tool in the supplied roster answers to ${n === 1 ? 'it' : 'those names'}; this record no longer stops the session’s runs from starting.${deny}${allow}`;
478
+ }
479
+ case 'nothing_to_remove':
480
+ return 'This session’s rule record holds no entry naming a tool the engine refuses that the supplied roster does not mount, so nothing was changed. If runs still fail at startup over a refused tool name, that entry is set somewhere this record does not cover — the settings sent with the run, a parent session’s rule record, or the deployment’s own tool rules — or it is a name the engine refuses that this version does not yet recognise, or the failing run does not mount a tool the supplied roster has under that name.';
481
+ case 'loosen_forbidden':
482
+ return 'The engine refused to take these entries out of this session’s rule record: on this engine only an operator can remove entries from it, so the record is unchanged. Ask an operator to remove them, or continue in a new session.';
483
+ case 'conflict':
484
+ return 'This session’s rule record was changed by someone else while the refused entries were being removed, twice in a row — nothing was changed. Try again once the other change has finished.';
485
+ case 'read_failed':
486
+ switch (outcome.why) {
487
+ case 'face_unavailable':
488
+ return 'This deployment does not keep per-session tool rules, so there is no session rule record to remove entries from — nothing was changed.';
489
+ case 'session_not_found':
490
+ return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was changed.';
491
+ case 'session_ownerless':
492
+ return 'This session has no owner, so it has no session rule record to change; nothing was changed.';
493
+ case 'unauthorized':
494
+ return 'This call could not present a verifiable identity, so nothing was changed.';
495
+ case 'store_corrupt':
496
+ return 'The engine could not establish which version this session’s rules are at, so nothing was changed. This needs an operator to look at the rule store.';
497
+ case 'read_unreadable':
498
+ return 'The session’s current rules could not be read, so nothing was changed — rewriting a record that cannot be read could replace real restrictions.';
499
+ default:
500
+ return 'The session’s current rules could not be fetched, so nothing was changed.';
501
+ }
502
+ case 'write_refused':
503
+ switch (outcome.why) {
504
+ case 'store_corrupt':
505
+ return 'The engine could not establish which version this session’s rules are at and refused to stamp a new one, so nothing was changed. This needs an operator to look at the rule store.';
506
+ case 'session_not_found':
507
+ return 'This session is not writable from here — it may not exist, or it may belong to someone else; nothing was changed.';
508
+ case 'unauthorized':
509
+ return 'This call could not present a verifiable identity, so nothing was changed.';
510
+ default:
511
+ return 'The engine refused this change, so nothing was changed. Re-read the session’s rules before trying again.';
512
+ }
513
+ case 'write_unconfirmed':
514
+ return 'The removal was sent but no clear answer came back from the engine — it may already be in effect. Run the removal again: it re-reads the record, and reports nothing left to remove if it did take effect.';
515
+ default:
516
+ return generic;
517
+ }
518
+ }
@@ -282,7 +282,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
282
282
  kind: 'failed',
283
283
  stage: 'decide',
284
284
  gatedCallId,
285
- reason: `decide(approve) failed: ${String(e)}${decideFailureNote(e)}`,
285
+ reason: `decide(approve) failed: ${String(e)}${decideFailureNote(e, 'approve')}`,
286
286
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
287
287
  ...(currentPending !== undefined ? { currentPending } : {}),
288
288
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
@@ -339,7 +339,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
339
339
  kind: 'failed',
340
340
  stage: 'decide',
341
341
  gatedCallId,
342
- reason: `decide(deny) failed: ${String(e)}${decideFailureNote(e)}`,
342
+ reason: `decide(deny) failed: ${String(e)}${decideFailureNote(e, 'deny')}`,
343
343
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
344
344
  ...(currentPending !== undefined ? { currentPending } : {}),
345
345
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
package/dist/index.d.ts CHANGED
@@ -70,7 +70,8 @@ export * from './sseIdleTriage.js';
70
70
  export * from './engineWireSdk.js';
71
71
  export * from './classifierVerdictWire.js';
72
72
  export { stripSystemReminderBlocks, unwrapSystemReminder } from './systemReminderTag.js';
73
- export { ACTIVE_RUN_BUSY_ERROR_CODE, asModelFallbackReason, CAPABILITY_SELF_ORCHESTRATION_REQUIRED, CONFIG_DELEGATION_ENTRY_CAPS, CONFIG_LIMIT_INVALID, CONFIG_LIMIT_UNKNOWN_KEY, CONFIG_MEMORY_PROJECT_KEY_SPELLING, CONFIG_MEMORY_WRITE_SCOPE_MISMATCH, CONFIG_REFUSAL_CODES, CONFIG_STRATEGY_FIND_LIMIT_INVALID, CONFIG_STRATEGY_MAX_SIZE_INVALID, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, DELEGATION_CAP_CODES, DELEGATION_CONCURRENCY_CAP, DELEGATION_SESSION_CAP, delegationCapDispositionOf, DRAINING_ERROR_CODE, ENGINE_STOP_REASONS, engineStopReasonToCc, ENV_LIFETIME_EXPIRED, GATE_PARKED_ERROR_CODE, isConfigRefusalCode, isDelegationCapCode, isInterruptedToolEndCode, isLimitsExceededCode, isRewindFamilyCode, isWorkflowParkRefusalCode, LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, MCP_SERVER_REVOKED, MODEL_FALLBACK_INHERIT_NO_TIER_BINDING, OUTPUT_INVALID, RESUME_AT_ERROR_CODE_PREFIX, RESUME_CONTEXT_UNAVAILABLE, RESUME_ENV_FAILED, RESUME_PLACEMENT_MISMATCH, RESUME_POLICY_FOLD_CODE, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_RETRY_LATER_CODES, RESUME_TOOL_CONTRACT_MISMATCH, RESUME_TOOL_UNAVAILABLE, RESUME_USAGE_WINDOW_EXHAUSTED, REWIND_ERROR_CODE_PREFIXES, SCENARIO_NOT_ALLOWED_ERROR_CODE, STOP_CONFLICT_CODES, STREAM_MAX_DURATION, TOOL_END_INTERRUPTED_NEVER_STARTED, TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN, USAGE_WINDOW_EXHAUSTED, WORKFLOW_PARK_REFUSAL_CODES, } from './engineErrorCodes.js';
73
+ export { findSystemReminderOpenTag, type SystemReminderOpenTagSpan } from './systemReminderTag.js';
74
+ export { ACTIVE_RUN_BUSY_ERROR_CODE, asModelFallbackReason, CAPABILITY_SELF_ORCHESTRATION_REQUIRED, CONFIG_DELEGATION_ENTRY_CAPS, CONFIG_LEGACY_TOOL_NAME, CONFIG_LIMIT_INVALID, CONFIG_LIMIT_UNKNOWN_KEY, CONFIG_MEMORY_PROJECT_KEY_SPELLING, CONFIG_MEMORY_WRITE_SCOPE_MISMATCH, CONFIG_REFUSAL_CODES, CONFIG_STRATEGY_FIND_LIMIT_INVALID, CONFIG_STRATEGY_MAX_SIZE_INVALID, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, DELEGATION_CAP_CODES, DELEGATION_CONCURRENCY_CAP, DELEGATION_SESSION_CAP, delegationCapDispositionOf, DRAINING_ERROR_CODE, ENGINE_STOP_REASONS, engineStopReasonToCc, ENV_LIFETIME_EXPIRED, GATE_PARKED_ERROR_CODE, isConfigRefusalCode, isDelegationCapCode, isInterruptedToolEndCode, isLimitsExceededCode, isRewindFamilyCode, isWorkflowParkRefusalCode, LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, MCP_SERVER_REVOKED, MODEL_FALLBACK_INHERIT_NO_TIER_BINDING, OUTPUT_INVALID, PARKED_RESUME_STARTUP_FAILED, RESUME_AT_ERROR_CODE_PREFIX, RESUME_CONTEXT_UNAVAILABLE, RESUME_ENV_FAILED, RESUME_PLACEMENT_MISMATCH, RESUME_POLICY_FOLD_CODE, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_RETRY_LATER_CODES, RESUME_TOOL_CONTRACT_MISMATCH, RESUME_TOOL_UNAVAILABLE, RESUME_USAGE_WINDOW_EXHAUSTED, REWIND_ERROR_CODE_PREFIXES, SCENARIO_NOT_ALLOWED_ERROR_CODE, STOP_CONFLICT_CODES, STREAM_MAX_DURATION, TOOL_END_INTERRUPTED_NEVER_STARTED, TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN, USAGE_WINDOW_EXHAUSTED, WORKFLOW_PARK_REFUSAL_CODES, } from './engineErrorCodes.js';
74
75
  export type { DelegationCapDisposition, ModelFallbackReason, } from './engineErrorCodes.js';
75
76
  export * from './agentsWireCaps.js';
76
77
  export * from './attachmentsWireCaps.js';
package/dist/index.js CHANGED
@@ -64,7 +64,8 @@ export * from './sseIdleTriage.js';
64
64
  export * from './engineWireSdk.js';
65
65
  export * from './classifierVerdictWire.js';
66
66
  export { stripSystemReminderBlocks, unwrapSystemReminder } from './systemReminderTag.js';
67
- export { ACTIVE_RUN_BUSY_ERROR_CODE, asModelFallbackReason, CAPABILITY_SELF_ORCHESTRATION_REQUIRED, CONFIG_DELEGATION_ENTRY_CAPS, CONFIG_LIMIT_INVALID, CONFIG_LIMIT_UNKNOWN_KEY, CONFIG_MEMORY_PROJECT_KEY_SPELLING, CONFIG_MEMORY_WRITE_SCOPE_MISMATCH, CONFIG_REFUSAL_CODES, CONFIG_STRATEGY_FIND_LIMIT_INVALID, CONFIG_STRATEGY_MAX_SIZE_INVALID, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, DELEGATION_CAP_CODES, DELEGATION_CONCURRENCY_CAP, DELEGATION_SESSION_CAP, delegationCapDispositionOf, DRAINING_ERROR_CODE, ENGINE_STOP_REASONS, engineStopReasonToCc, ENV_LIFETIME_EXPIRED, GATE_PARKED_ERROR_CODE, isConfigRefusalCode, isDelegationCapCode, isInterruptedToolEndCode, isLimitsExceededCode, isRewindFamilyCode, isWorkflowParkRefusalCode, LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, MCP_SERVER_REVOKED, MODEL_FALLBACK_INHERIT_NO_TIER_BINDING, OUTPUT_INVALID, RESUME_AT_ERROR_CODE_PREFIX, RESUME_CONTEXT_UNAVAILABLE, RESUME_ENV_FAILED, RESUME_PLACEMENT_MISMATCH, RESUME_POLICY_FOLD_CODE, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_RETRY_LATER_CODES, RESUME_TOOL_CONTRACT_MISMATCH, RESUME_TOOL_UNAVAILABLE, RESUME_USAGE_WINDOW_EXHAUSTED, REWIND_ERROR_CODE_PREFIXES, SCENARIO_NOT_ALLOWED_ERROR_CODE, STOP_CONFLICT_CODES, STREAM_MAX_DURATION, TOOL_END_INTERRUPTED_NEVER_STARTED, TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN, USAGE_WINDOW_EXHAUSTED, WORKFLOW_PARK_REFUSAL_CODES, } from './engineErrorCodes.js';
67
+ export { findSystemReminderOpenTag } from './systemReminderTag.js';
68
+ export { ACTIVE_RUN_BUSY_ERROR_CODE, asModelFallbackReason, CAPABILITY_SELF_ORCHESTRATION_REQUIRED, CONFIG_DELEGATION_ENTRY_CAPS, CONFIG_LEGACY_TOOL_NAME, CONFIG_LIMIT_INVALID, CONFIG_LIMIT_UNKNOWN_KEY, CONFIG_MEMORY_PROJECT_KEY_SPELLING, CONFIG_MEMORY_WRITE_SCOPE_MISMATCH, CONFIG_REFUSAL_CODES, CONFIG_STRATEGY_FIND_LIMIT_INVALID, CONFIG_STRATEGY_MAX_SIZE_INVALID, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, DELEGATION_CAP_CODES, DELEGATION_CONCURRENCY_CAP, DELEGATION_SESSION_CAP, delegationCapDispositionOf, DRAINING_ERROR_CODE, ENGINE_STOP_REASONS, engineStopReasonToCc, ENV_LIFETIME_EXPIRED, GATE_PARKED_ERROR_CODE, isConfigRefusalCode, isDelegationCapCode, isInterruptedToolEndCode, isLimitsExceededCode, isRewindFamilyCode, isWorkflowParkRefusalCode, LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, MCP_SERVER_REVOKED, MODEL_FALLBACK_INHERIT_NO_TIER_BINDING, OUTPUT_INVALID, PARKED_RESUME_STARTUP_FAILED, RESUME_AT_ERROR_CODE_PREFIX, RESUME_CONTEXT_UNAVAILABLE, RESUME_ENV_FAILED, RESUME_PLACEMENT_MISMATCH, RESUME_POLICY_FOLD_CODE, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_RETRY_LATER_CODES, RESUME_TOOL_CONTRACT_MISMATCH, RESUME_TOOL_UNAVAILABLE, RESUME_USAGE_WINDOW_EXHAUSTED, REWIND_ERROR_CODE_PREFIXES, SCENARIO_NOT_ALLOWED_ERROR_CODE, STOP_CONFLICT_CODES, STREAM_MAX_DURATION, TOOL_END_INTERRUPTED_NEVER_STARTED, TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN, USAGE_WINDOW_EXHAUSTED, WORKFLOW_PARK_REFUSAL_CODES, } from './engineErrorCodes.js';
68
69
  export * from './agentsWireCaps.js';
69
70
  export * from './attachmentsWireCaps.js';
70
71
  export * from './clientContextWireCaps.js';
@@ -1,4 +1,5 @@
1
1
  import { makeEngineWireClient, resolveWireAuth } from './engineWireSdk.js';
2
+ import { settleWithinDeadline } from './abortableSleep.js';
2
3
  import { hostLog } from './host.js';
3
4
  import { scenarioForPrint } from './scenarioWire.js';
4
5
  import { resolveHeadlessInteractiveTools } from './interactiveToolsWire.js';
@@ -58,17 +59,34 @@ export function _resetLiveInitToolFaceForTest() {
58
59
  cached = null;
59
60
  }
60
61
  export async function probeScenarioTools(baseUrl, scenario, opts) {
61
- const client = makeEngineWireClient({
62
- baseUrl,
63
- ...(opts?.authToken ? { token: opts.authToken } : {}),
64
- ...(opts?.principal ? { principal: opts.principal } : {}),
65
- timeoutMs: opts?.timeoutMs ?? 2000,
66
- ...(opts?.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
67
- });
68
- if (!client)
69
- return null;
62
+ const injected = opts?.client;
63
+ let body;
64
+ if (injected !== undefined) {
65
+ if (typeof injected?.scenarioCapabilities !== 'function')
66
+ return null;
67
+ const got = await settleWithinDeadline(opts?.timeoutMs ?? 2000, (signal) => injected.scenarioCapabilities(scenario, { signal }));
68
+ if (got.kind !== 'value')
69
+ return null;
70
+ body = got.value;
71
+ }
72
+ else {
73
+ const client = makeEngineWireClient({
74
+ baseUrl,
75
+ ...(opts?.authToken ? { token: opts.authToken } : {}),
76
+ ...(opts?.principal ? { principal: opts.principal } : {}),
77
+ timeoutMs: opts?.timeoutMs ?? 2000,
78
+ ...(opts?.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
79
+ });
80
+ if (!client)
81
+ return null;
82
+ try {
83
+ body = await client.scenarioCapabilities(scenario);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
70
89
  try {
71
- const body = await client.scenarioCapabilities(scenario);
72
90
  if (!Array.isArray(body?.tools))
73
91
  return null;
74
92
  const names = body.tools.filter((t) => typeof t === 'string' && t.length > 0);
@@ -33,3 +33,14 @@ export interface ResumeContextUnavailableDetail {
33
33
  }
34
34
  export declare function resumeContextUnavailableFromError(err: unknown): ResumeContextUnavailableDetail | null;
35
35
  export declare function resumeContextUnavailableContent(d: ResumeContextUnavailableDetail): string;
36
+ export type ParkedResumeStartupDecision = 'approve' | 'deny';
37
+ export type ParkedResumeStartupCause = 'resume_rejected' | 'undetermined';
38
+ export interface ParkedResumeStartupDetail {
39
+ code: string;
40
+ cause: ParkedResumeStartupCause;
41
+ taskId?: string;
42
+ resendable: false;
43
+ denyStaysOpen: true;
44
+ }
45
+ export declare function parkedResumeStartupFromError(err: unknown, decision?: ParkedResumeStartupDecision): ParkedResumeStartupDetail | null;
46
+ export declare function parkedResumeStartupContent(detail: ParkedResumeStartupDetail): string;
@@ -1,5 +1,5 @@
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, RESUME_POLICY_FOLD_CODE, } 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, PARKED_RESUME_STARTUP_FAILED, } from './engineErrorCodes.js';
3
3
  import { capForDisplay } from './fleetTaskDesc.js';
4
4
  import { resumeRetryLaterFromError } from './wireErrorTriage.js';
5
5
  export const RESUME_REFUSAL_CODES = Object.freeze([
@@ -134,3 +134,41 @@ export function resumeContextUnavailableContent(d) {
134
134
  third,
135
135
  ].join(' · ');
136
136
  }
137
+ export function parkedResumeStartupFromError(err, decision) {
138
+ if (typeof err !== 'object' || err === null)
139
+ return null;
140
+ let code;
141
+ try {
142
+ code = err.errorCode;
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ if (code !== PARKED_RESUME_STARTUP_FAILED)
148
+ return null;
149
+ const rawTaskId = readErrorString(err, 'taskId');
150
+ const taskId = rawTaskId !== undefined && rawTaskId.length > 0 ? rawTaskId : undefined;
151
+ return {
152
+ code: PARKED_RESUME_STARTUP_FAILED,
153
+ cause: decision === 'deny' ? 'resume_rejected' : 'undetermined',
154
+ ...(taskId !== undefined ? { taskId } : {}),
155
+ resendable: false,
156
+ denyStaysOpen: true,
157
+ };
158
+ }
159
+ const PARKED_RESUME_STARTUP_STATE = 'This refusal does not say whether the card is still waiting — re-fetch the pending approvals';
160
+ const PARKED_RESUME_STARTUP_SENTENCES = Object.freeze({
161
+ resume_rejected: [
162
+ 'The engine could not start the parked work to apply this decision (its session may be gone, or the engine rejected the decision itself)',
163
+ PARKED_RESUME_STARTUP_STATE,
164
+ 'If the card is still there, decide it again from the fresh list',
165
+ ].join(' · '),
166
+ undetermined: [
167
+ 'The engine could not start the parked work to apply this decision',
168
+ PARKED_RESUME_STARTUP_STATE,
169
+ 'If the card is still there: when the engine rejected the decision itself (for example an answer given to the wrong question), correct it and decide again; when the approved tool is one the agent inherits from the task that started it, approving it again will not work on this server — deny the card, or wait for a server upgrade',
170
+ ].join(' · '),
171
+ });
172
+ export function parkedResumeStartupContent(detail) {
173
+ return detail.cause === 'resume_rejected' ? PARKED_RESUME_STARTUP_SENTENCES.resume_rejected : PARKED_RESUME_STARTUP_SENTENCES.undetermined;
174
+ }
@@ -1,2 +1,7 @@
1
+ export interface SystemReminderOpenTagSpan {
2
+ readonly start: number;
3
+ readonly end: number;
4
+ }
5
+ export declare function findSystemReminderOpenTag(text: string, from?: number): SystemReminderOpenTagSpan | null;
1
6
  export declare function unwrapSystemReminder(text: string): string;
2
7
  export declare function stripSystemReminderBlocks(text: string): string;
@@ -1,29 +1,40 @@
1
1
  const OPEN_TAG_SOURCE = '<system-reminder(?: mark="[A-Za-z0-9_-]{22}")?>';
2
2
  const CLOSE_TAG = '</system-reminder>';
3
- const OPEN_TAG_AT_START_RE = new RegExp(`^${OPEN_TAG_SOURCE}`);
3
+ function matchOpenTag(text, from, anchored) {
4
+ const re = new RegExp(OPEN_TAG_SOURCE, anchored ? 'y' : 'g');
5
+ re.lastIndex = from;
6
+ const m = re.exec(text);
7
+ return m === null ? null : { start: m.index, end: m.index + m[0].length };
8
+ }
9
+ export function findSystemReminderOpenTag(text, from) {
10
+ if (typeof text !== 'string')
11
+ return null;
12
+ const at = from === undefined ? 0 : from;
13
+ if (!Number.isInteger(at) || at < 0 || at > text.length)
14
+ return null;
15
+ return matchOpenTag(text, at, false);
16
+ }
4
17
  export function unwrapSystemReminder(text) {
5
18
  const head = text.trimStart();
6
- const open = OPEN_TAG_AT_START_RE.exec(head);
19
+ const open = matchOpenTag(head, 0, true);
7
20
  if (open === null)
8
21
  return text;
9
22
  const whole = head.trimEnd();
10
- if (!whole.endsWith(CLOSE_TAG) || whole.length - CLOSE_TAG.length < open[0].length)
23
+ if (!whole.endsWith(CLOSE_TAG) || whole.length - CLOSE_TAG.length < open.end)
11
24
  return text;
12
- return whole.slice(open[0].length, whole.length - CLOSE_TAG.length).trim();
25
+ return whole.slice(open.end, whole.length - CLOSE_TAG.length).trim();
13
26
  }
14
27
  export function stripSystemReminderBlocks(text) {
15
- const openRe = new RegExp(OPEN_TAG_SOURCE, 'g');
16
28
  let out = '';
17
29
  let pos = 0;
18
30
  for (;;) {
19
- openRe.lastIndex = pos;
20
- const open = openRe.exec(text);
31
+ const open = matchOpenTag(text, pos, false);
21
32
  if (open === null)
22
33
  break;
23
- const close = text.indexOf(CLOSE_TAG, open.index + open[0].length);
34
+ const close = text.indexOf(CLOSE_TAG, open.end);
24
35
  if (close === -1)
25
36
  break;
26
- out += text.slice(pos, open.index);
37
+ out += text.slice(pos, open.start);
27
38
  pos = close + CLOSE_TAG.length;
28
39
  }
29
40
  return pos === 0 ? text : out + text.slice(pos);