@sema-agent/core 5.55.0 → 5.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/dist/agents/send-message-tool.d.ts +11 -0
  3. package/dist/agents/send-message-tool.js +81 -13
  4. package/dist/agents/subagent.js +250 -89
  5. package/dist/agents/team.d.ts +10 -1
  6. package/dist/agents/team.js +1 -0
  7. package/dist/brain/anthropic.js +15 -5
  8. package/dist/brain/circuit-breaker.js +2 -1
  9. package/dist/brain/degrading.js +4 -1
  10. package/dist/brain/failover.js +16 -1
  11. package/dist/brain/open-responses.js +15 -5
  12. package/dist/brain/openai.js +16 -5
  13. package/dist/brain/request-params.d.ts +30 -27
  14. package/dist/brain/request-params.js +1 -7
  15. package/dist/brain/route-adjudicator.d.ts +190 -0
  16. package/dist/brain/route-adjudicator.js +189 -0
  17. package/dist/brain/route-conformance.d.ts +55 -0
  18. package/dist/brain/route-conformance.js +136 -0
  19. package/dist/brain/routing.js +8 -3
  20. package/dist/core/auto-compaction.d.ts +17 -4
  21. package/dist/core/auto-compaction.js +3 -0
  22. package/dist/core/context-edit.d.ts +55 -6
  23. package/dist/core/context-edit.js +12 -1
  24. package/dist/core/hooks.d.ts +293 -11
  25. package/dist/core/hooks.js +158 -11
  26. package/dist/core/human-input-projection.d.ts +20 -2
  27. package/dist/core/human-input-projection.js +9 -0
  28. package/dist/core/mcp.js +4 -4
  29. package/dist/core/memory-engine/engine.d.ts +15 -5
  30. package/dist/core/memory-engine/engine.js +3 -1
  31. package/dist/core/permission-rule-consent.d.ts +45 -0
  32. package/dist/core/permission-rule-consent.js +40 -11
  33. package/dist/core/permission-rule-model.d.ts +110 -75
  34. package/dist/core/permission-rule-model.js +61 -28
  35. package/dist/core/permission-rules.d.ts +23 -15
  36. package/dist/core/permission-rules.js +40 -31
  37. package/dist/core/runner/prepare-task.d.ts +8 -0
  38. package/dist/core/runner/prepare-task.js +66 -26
  39. package/dist/core/runner/runtask.d.ts +4 -1
  40. package/dist/core/runner/runtask.js +206 -21
  41. package/dist/core/runner/session-rule-policy.js +5 -5
  42. package/dist/core/scheduler.d.ts +5 -0
  43. package/dist/core/session-reconcile.d.ts +32 -0
  44. package/dist/core/session-reconcile.js +15 -0
  45. package/dist/core/side-query.d.ts +12 -5
  46. package/dist/core/task-notification.d.ts +34 -7
  47. package/dist/core/task-notification.js +11 -1
  48. package/dist/core/task-registry-agent.d.ts +20 -3
  49. package/dist/core/task-registry-agent.js +31 -2
  50. package/dist/core/tool-policy.d.ts +14 -9
  51. package/dist/core/tool-policy.js +27 -22
  52. package/dist/core/types.d.ts +69 -11
  53. package/dist/core/untrusted-text.js +8 -0
  54. package/dist/engine/compaction/compaction.d.ts +77 -7
  55. package/dist/engine/compaction/compaction.js +98 -9
  56. package/dist/engine/compaction/utils.d.ts +4 -0
  57. package/dist/engine/compaction/utils.js +6 -0
  58. package/dist/engine/harness/agent-harness.d.ts +84 -0
  59. package/dist/engine/harness/agent-harness.js +114 -13
  60. package/dist/engine/harness/messages.d.ts +4 -2
  61. package/dist/engine/harness/messages.js +7 -2
  62. package/dist/engine/harness/types.d.ts +16 -6
  63. package/dist/engine/llm/types.d.ts +65 -0
  64. package/dist/engine/loop/types.d.ts +7 -0
  65. package/dist/engine/session/import-validate.js +10 -0
  66. package/dist/engine/session/session.js +2 -2
  67. package/dist/index.d.ts +4 -1
  68. package/dist/index.js +3 -1
  69. package/dist/internal/llm.d.ts +1 -1
  70. package/dist/orchestration/run-spec.js +8 -1
  71. package/dist/prompts/default.d.ts +12 -6
  72. package/dist/prompts/default.js +2 -0
  73. package/dist/scenarios/scenario-registry.d.ts +5 -1
  74. package/dist/scenarios/scenario-registry.js +4 -2
  75. package/dist/tools/fs/index.js +8 -1
  76. package/dist/tools/scheduler-tools.js +28 -6
  77. package/dist/tools/web.d.ts +15 -0
  78. package/dist/tools/web.js +8 -2
  79. package/dist/tools/worktree.js +2 -2
  80. package/package.json +1 -1
  81. package/test/export-surface.snapshot.json +19 -1
@@ -62,7 +62,11 @@ export interface AgentHarnessStreamOptions {
62
62
  maxRetries?: number;
63
63
  /** Optional cap for provider-requested retry delays. */
64
64
  maxRetryDelayMs?: number;
65
- /** Additional request headers merged with auth and lifecycle headers. */
65
+ /** Additional request headers merged with auth and lifecycle headers. NOT an auth channel: a bag
66
+ * carrying an auth header (`authorization` / `x-api-key`, any case) is refused loudly at the
67
+ * construction and `setStreamOptions` seats — the route pairing law reads options-borne
68
+ * carriers as per-model credentials, which a run-wide bag is not. Deployment credentials go on
69
+ * the brain config; per-model credentials ride `getApiKeyAndHeaders`. */
66
70
  headers?: Record<string, string>;
67
71
  /** Provider metadata forwarded with requests. */
68
72
  metadata?: SimpleStreamOptions["metadata"];
@@ -889,8 +893,12 @@ export interface SavePointEvent {
889
893
  }
890
894
  export interface AbortEvent {
891
895
  type: "abort";
892
- clearedSteer: AgentMessage[];
893
- clearedFollowUp: AgentMessage[];
896
+ /** backlog #389 — the caller frames still QUEUED when the abort fired. They are no longer cleared by
897
+ * `abort()` (CC keeps its queue on interrupt): the terminal `agent_end` account announces them, so
898
+ * this list is the same set that reaches `onUndrainedUserInputs`, observed one step earlier. */
899
+ undrainedSteer: AgentMessage[];
900
+ /** backlog #389 — the follow-up half of {@link AbortEvent.undrainedSteer}. */
901
+ undrainedFollowUp: AgentMessage[];
894
902
  }
895
903
  export interface SettledEvent {
896
904
  type: "settled";
@@ -1044,10 +1052,12 @@ export type AgentHarnessEventResultMap = {
1044
1052
  abort: undefined;
1045
1053
  settled: undefined;
1046
1054
  };
1047
- /** Queued messages removed by an abort operation. */
1055
+ /** backlog #389 what an abort found still QUEUED (it no longer removes caller frames; see
1056
+ * {@link AgentHarness.abort}). Engine-note frames are absent: their own sweep recovered them for
1057
+ * per-session redelivery before this snapshot was taken. */
1048
1058
  export interface AbortResult {
1049
- clearedSteer: AgentMessage[];
1050
- clearedFollowUp: AgentMessage[];
1059
+ undrainedSteer: AgentMessage[];
1060
+ undrainedFollowUp: AgentMessage[];
1051
1061
  }
1052
1062
  /** Compaction data supplied by hooks or returned from compaction preparation. */
1053
1063
  export interface CompactResult {
@@ -805,6 +805,71 @@ export interface ImagesModel<TApi extends ImagesApi = ImagesApi> extends Omit<Mo
805
805
  provider: ImagesProvider;
806
806
  output: ("text" | "image")[];
807
807
  }
808
+ /** Where the credential a request would carry came from.
809
+ * - `"per-model"` — resolved FOR this model: a per-call key/headers from the per-model auth hook
810
+ * (`getApiKeyAndHeaders`), or an auth-bearing header declared on `Model.headers`. Paired by
811
+ * construction (the resolver was asked about THIS entry), so the adjudicator never refuses it.
812
+ * - `"deployment-config"` — the brain construction-time fallback (`config.apiKey` / an auth header
813
+ * in `config.headers`). Paired only with the deployment's own declared root (`config.baseUrl`).
814
+ * - `"absent"` — no credential anywhere. Legal for keyless gateways; refused only where an entry
815
+ * declares its OWN URL away from a declared deployment root (a distinct endpoint with no
816
+ * credential route is a configuration hole, not a keyless deployment). */
817
+ export type RouteCredentialSource = "per-model" | "deployment-config" | "absent";
818
+ /** The resolved credential a request would carry, tagged with its source (see {@link RouteCredentialSource}). */
819
+ export interface RouteCredential {
820
+ source: RouteCredentialSource;
821
+ /** The key itself, when the credential rides the apiKey knob. An EMPTY string is a present (per-call)
822
+ * credential that fails closed at emit time — presence, not truthiness (the degraded-secret contract). */
823
+ apiKey?: string;
824
+ /** True when the credential rides as an auth-bearing HEADER (`authorization`/`x-api-key`, any case)
825
+ * rather than the apiKey knob. The winning bag is named by {@link RouteCredential.carrierBag}. */
826
+ headersBorne?: boolean;
827
+ /** Which header bag carries a headers-borne credential (resolution precedence:
828
+ * options > model > config; the apiKey knob outranks headers within options, while the config's
829
+ * knob+header pair counts as ONE deployment credential — see `resolveRouteCredential`). */
830
+ carrierBag?: "options" | "model" | "config";
831
+ }
832
+ /** The deployment half of the pairing judgment: the brain construction config's DECLARED root.
833
+ * Absent/empty ⇒ the deployment did not declare its credential's URL half (the unpinned
834
+ * quick-start posture — see `adjudicateModelRoute`). */
835
+ export interface RoutePairingConfig {
836
+ baseUrl?: string;
837
+ }
838
+ /** How an OK verdict is paired.
839
+ * - `"per-model"` — per-model credential, paired by construction.
840
+ * - `"paired"` — deployment credential on the deployment's own declared root.
841
+ * - `"unpinned"` — deployment credential whose URL half is UNDECLARED (no `config.baseUrl`): allowed
842
+ * for compatibility with single-endpoint configs (key on config, URL on the model), but the pairing
843
+ * is unverifiable — a multi-entry deployment in this posture should declare `config.baseUrl` or move
844
+ * to per-model credentials. Surfaced (not refused) so read faces can annotate it.
845
+ * - `"keyless"` — no credential anywhere; nothing to protect. */
846
+ export type RoutePairingPosture = "per-model" | "paired" | "unpinned" | "keyless";
847
+ /** Machine codes an adjudication refusal carries (minted here, forwarded verbatim by consumers). */
848
+ export type RouteRefusalCode = "route.credential_mismatch" | "route.credential_missing";
849
+ /** The structured account of a refusal — enough for a caller to render the two halves and the fix. */
850
+ export interface RouteRefusalDetail {
851
+ modelId: string;
852
+ /** The entry's declared URL half (`Model.baseUrl`, normalized). */
853
+ entryBaseUrl: string;
854
+ /** The root the request would actually target (normalized). */
855
+ requestBaseUrl: string;
856
+ /** The deployment's declared root the credential is paired with (mismatch refusals). */
857
+ configBaseUrl?: string;
858
+ /** One-line configuration fix. */
859
+ fixHint: string;
860
+ }
861
+ /** The adjudicator's verdict: either the route is servable (with its pairing posture) or it is
862
+ * refused with a machine code + structured detail. Fail-closed by design: a credential is never
863
+ * sent to a host its configuration does not pair it with — "no credential leak" outranks
864
+ * "silently works". */
865
+ export type RouteAdjudication = {
866
+ ok: true;
867
+ posture: RoutePairingPosture;
868
+ } | {
869
+ ok: false;
870
+ code: RouteRefusalCode;
871
+ detail: RouteRefusalDetail;
872
+ };
808
873
  export type StreamFn = (model: Model, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStreamLike | Promise<AssistantMessageEventStreamLike>;
809
874
  export type CompleteSimpleFn = (model: Model, context: Pick<Context, "systemPrompt" | "messages">, options?: SimpleStreamOptions) => Promise<AssistantMessage>;
810
875
  export type ValidateToolArgumentsFn = (tool: Tool, toolCall: ToolCall) => unknown;
@@ -489,6 +489,13 @@ export interface CompactionSummaryMessage {
489
489
  * (`convertToLlm`); absent (directly constructed messages, older render paths) ⇒ the wrapper is
490
490
  * byte-identical to the historical constant. */
491
491
  elidedMessages?: number;
492
+ /** PTL-shortfall accounting: how many of those {@link elidedMessages} the summary does NOT cover — messages a reactive
493
+ * prompt-too-long retry shed from the summarization input after the cut was already committed.
494
+ * Sourced from the compaction entry's own write-side accounting
495
+ * (`CompactionDetails.unsummarizedMessages`), same reason as its sibling. Absent (the lossless
496
+ * common case, directly constructed messages, pre-field entries) ⇒ no uncovered clause, wrapper
497
+ * byte-identical. */
498
+ unsummarizedMessages?: number;
492
499
  /** Optional estimated context tokens after compaction. */
493
500
  tokensAfter?: number;
494
501
  /** Optional first retained entry id from the compaction range. */
@@ -183,6 +183,16 @@ export class StreamingImportValidator {
183
183
  if (elided !== undefined && !(typeof elided === "number" && Number.isSafeInteger(elided) && elided >= 0)) {
184
184
  throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid elidedMessages count (non-negative integer)`);
185
185
  }
186
+ const uncovered = e.details?.unsummarizedMessages;
187
+ if (uncovered !== undefined) {
188
+ if (!(typeof uncovered === "number" && Number.isSafeInteger(uncovered) && uncovered >= 0)) {
189
+ throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid unsummarizedMessages count (non-negative integer)`);
190
+ }
191
+ if (uncovered > 0 && !(typeof elided === "number" && elided >= uncovered)) {
192
+ throw new SessionError("invalid_session", `compaction "${e.id}" carries unsummarizedMessages ${uncovered} without an elidedMessages count at least that ` +
193
+ `large (uncovered messages are a subset of the folded ones)`);
194
+ }
195
+ }
186
196
  const refs = e.details?.persistedOutputRefs;
187
197
  if (refs !== undefined) {
188
198
  if (!Array.isArray(refs) || refs.length > PERSISTED_OUTPUT_REFS_MAX_ENTRIES) {
@@ -1,7 +1,7 @@
1
1
  import { asAgentMessage, createCompactionSummaryMessage, createCustomMessage, } from "../harness/messages.js";
2
2
  import { SessionError, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeGitAnnouncement, normalizeReminderMark, normalizeWorkspaceState } from "../harness/types.js";
3
3
  import { normalizePromptEpoch } from "../../prompt-assembly/epoch.js";
4
- import { budgetInvokedSkillsRetention, readElidedMessages, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../compaction/utils.js";
4
+ import { budgetInvokedSkillsRetention, readElidedMessages, readRetainedInvokedSkills, readUnsummarizedMessages, renderInvokedSkillsRetention, } from "../compaction/utils.js";
5
5
  const RETENTION_CLAMP_DEFAULT_CHARS_PER_TOKEN = 4;
6
6
  const RETENTION_CLAMP_WINDOW_FRACTION = 0.5;
7
7
  export function buildSessionContext(pathEntries, opts) {
@@ -67,7 +67,7 @@ export function buildSessionContext(pathEntries, opts) {
67
67
  retainedSkills = budgetInvokedSkillsRetention(retainedSkills, budgetChars);
68
68
  }
69
69
  const retainedSkillsBlock = renderInvokedSkillsRetention(retainedSkills);
70
- messages.push(asAgentMessage(createCompactionSummaryMessage(compaction.summary + retainedSkillsBlock, compaction.tokensBefore, compaction.timestamp, readElidedMessages(compaction.details))));
70
+ messages.push(asAgentMessage(createCompactionSummaryMessage(compaction.summary + retainedSkillsBlock, compaction.tokensBefore, compaction.timestamp, readElidedMessages(compaction.details), readUnsummarizedMessages(compaction.details))));
71
71
  const compactionIdx = pathEntries.findIndex((e) => e.type === "compaction" && e.id === compaction.id);
72
72
  let foundFirstKept = false;
73
73
  for (let i = 0; i < compactionIdx; i++) {
package/dist/index.d.ts CHANGED
@@ -161,7 +161,7 @@ export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRule
161
161
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
162
162
  export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, type OrgPermissionRule, type OrgRuleSnapshot, type OrgRuleSnapshotProvider, type OrgRuleStatePersistence, type PersistedOrgRuleState, type OrgRuleOverlay, type OrgOverlayResolution, type OrgOverlayStatus, type EffectivePermissionRule, } from "./core/permission-rule-org.js";
163
163
  export { RULE_SYNC_DROP_CODES, type RuleSyncDropReason, type RuleQuarantineReason } from "./core/governance-codes.js";
164
- export { prepareCardApproval, confirmRuleApproval, type ConfirmResult, type ConfirmRefusalReason, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, type RuleTicket, type RuleCandidate, type RuleApprovalKind, type RuleApprovalRecord, type RuleApprovalRecordStore, type RuleConsentDeps, type RedeemResult, type CcImportLayer, type ImportedSettingsLayer, type ImportPreview, type ImportResult, } from "./core/permission-rule-consent.js";
164
+ export { prepareCardApproval, confirmRuleApproval, type ConfirmResult, type ConfirmRefusalReason, precheckEditedRuleText, type EditedRuleTextPrecheck, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, type RuleTicket, type RuleCandidate, type RuleApprovalKind, type RuleApprovalRecord, type RuleApprovalRecordStore, type RuleConsentDeps, type RedeemResult, type CcImportLayer, type ImportedSettingsLayer, type ImportPreview, type ImportResult, } from "./core/permission-rule-consent.js";
165
165
  export { FilePermissionRuleStoreProvider } from "./stores/file/permission-rule-store.js";
166
166
  export { adoptFilePermissionRuleStore, type AdoptFileRuleStoreResult } from "./stores/file/permission-rule-adopt.js";
167
167
  export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootAdoptionFile, ROOT_ADOPTION_FILE, type AdoptionErrorCode, type AdoptionSource, type AdoptionReport, type AdoptionReceipt, type AdoptionLegReport, type AffectedDeploymentConfig, type RootAdoptionFile, } from "./stores/file/adoption/marker.js";
@@ -256,6 +256,9 @@ export { repairTextToolCalls } from "./brain/tool-call-repair.js";
256
256
  export { createCircuitBreakerBrain, type CircuitBreakerOptions, type BreakerState, type BreakerSnapshot, type BreakerPhase, CIRCUIT_OPEN_MARKER, } from "./brain/circuit-breaker.js";
257
257
  export { createDegradingBrain, readDegradation, DEGRADED_DIAGNOSTIC_TYPE, type DegradingBrainOptions, type DegradeReason, type DegradationInfo, } from "./brain/degrading.js";
258
258
  export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
259
+ export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, normalizeBaseUrl, hasAuthCarrier, type RoutePairingStatus, } from "./brain/route-adjudicator.js";
260
+ export type { RouteAdjudication, RouteCredential, RouteCredentialSource, RoutePairingConfig, RoutePairingPosture, RouteRefusalCode, RouteRefusalDetail, } from "./internal/llm.js";
261
+ export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS, type RouteAdjudicationVector } from "./brain/route-conformance.js";
259
262
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
260
263
  export { createAssistantMessageEventStream } from "./internal/llm.js";
261
264
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
package/dist/index.js CHANGED
@@ -123,7 +123,7 @@ export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRule
123
123
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
124
124
  export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, } from "./core/permission-rule-org.js";
125
125
  export { RULE_SYNC_DROP_CODES } from "./core/governance-codes.js";
126
- export { prepareCardApproval, confirmRuleApproval, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, } from "./core/permission-rule-consent.js";
126
+ export { prepareCardApproval, confirmRuleApproval, precheckEditedRuleText, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, } from "./core/permission-rule-consent.js";
127
127
  export { FilePermissionRuleStoreProvider } from "./stores/file/permission-rule-store.js";
128
128
  export { adoptFilePermissionRuleStore } from "./stores/file/permission-rule-adopt.js";
129
129
  export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootAdoptionFile, ROOT_ADOPTION_FILE, } from "./stores/file/adoption/marker.js";
@@ -215,6 +215,8 @@ export { repairTextToolCalls } from "./brain/tool-call-repair.js";
215
215
  export { createCircuitBreakerBrain, CIRCUIT_OPEN_MARKER, } from "./brain/circuit-breaker.js";
216
216
  export { createDegradingBrain, readDegradation, DEGRADED_DIAGNOSTIC_TYPE, } from "./brain/degrading.js";
217
217
  export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
218
+ export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, normalizeBaseUrl, hasAuthCarrier, } from "./brain/route-adjudicator.js";
219
+ export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS } from "./brain/route-conformance.js";
218
220
  export {} from "./brain/timeout.js";
219
221
  export { createAssistantMessageEventStream } from "./internal/llm.js";
220
222
  export { Type } from "typebox";
@@ -5,4 +5,4 @@
5
5
  * llm-core); the facade stays so consumers never notice which side a symbol lives on.
6
6
  */
7
7
  export { createAssistantMessageEventStream, snapshotActorAssertion, stripEngineMetadata } from "../engine/llm/index.js";
8
- export type { ActorAssertion, AnthropicMessagesCompat, OpenAICompletionsCompat, OpenAIResponsesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, Usage, UserMessage, } from "../engine/llm/index.js";
8
+ export type { ActorAssertion, AnthropicMessagesCompat, OpenAICompletionsCompat, OpenAIResponsesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, RouteAdjudication, RouteCredential, RouteCredentialSource, RoutePairingConfig, RoutePairingPosture, RouteRefusalCode, RouteRefusalDetail, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, Usage, UserMessage, } from "../engine/llm/index.js";
@@ -114,6 +114,9 @@ export async function runSpec(runner, contract, opts) {
114
114
  postCompact: (opts.taskSpec.hooks.postCompact ?? deployBaseline.hooks.postCompact)?.bind(opts.taskSpec.hooks.postCompact ? opts.taskSpec.hooks : deployBaseline.hooks),
115
115
  stopFailure: (opts.taskSpec.hooks.stopFailure ?? deployBaseline.hooks.stopFailure)?.bind(opts.taskSpec.hooks.stopFailure ? opts.taskSpec.hooks : deployBaseline.hooks),
116
116
  permissionDenied: (opts.taskSpec.hooks.permissionDenied ?? deployBaseline.hooks.permissionDenied)?.bind(opts.taskSpec.hooks.permissionDenied ? opts.taskSpec.hooks : deployBaseline.hooks),
117
+ ...((opts.taskSpec.hooks.timeoutMs ?? deployBaseline.hooks.timeoutMs) !== undefined
118
+ ? { timeoutMs: opts.taskSpec.hooks.timeoutMs ?? deployBaseline.hooks.timeoutMs }
119
+ : {}),
117
120
  }
118
121
  : (opts.taskSpec.hooks ?? deployBaseline?.hooks);
119
122
  let guardRestores = 0;
@@ -130,10 +133,14 @@ export async function runSpec(runner, contract, opts) {
130
133
  ...(userHooks?.postCompact && { postCompact: userHooks.postCompact.bind(userHooks) }),
131
134
  ...(userHooks?.stopFailure && { stopFailure: userHooks.stopFailure.bind(userHooks) }),
132
135
  ...(userHooks?.permissionDenied && { permissionDenied: userHooks.permissionDenied.bind(userHooks) }),
136
+ ...(userHooks?.timeoutMs !== undefined ? { timeoutMs: userHooks.timeoutMs } : {}),
133
137
  async stop(ctx) {
134
138
  guardRestores += restoreFrozenPaths(snapshot, rootDir);
135
139
  if (!gateAbandoned) {
136
- const report = await runOracle(contract.oracle, rootDir, { baseline, signal: opts.taskSpec.signal });
140
+ const gateSignal = ctx.signal !== undefined && opts.taskSpec.signal !== undefined ? AbortSignal.any([ctx.signal, opts.taskSpec.signal]) : (ctx.signal ?? opts.taskSpec.signal);
141
+ const report = await runOracle(contract.oracle, rootDir, { baseline, ...(gateSignal !== undefined ? { signal: gateSignal } : {}) });
142
+ if (ctx.signal?.aborted === true)
143
+ return undefined;
137
144
  oracleRuns++;
138
145
  if (report.verdict === "red") {
139
146
  oracleHadRedRun = true;
@@ -142,8 +142,8 @@ export declare const CYBER_RISK = "IMPORTANT: Assist with authorized security te
142
142
  export declare const URL_SAFETY = "IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.";
143
143
  /**
144
144
  * Tool-result retention reminder (design/64 §6.2 C). Always injected: core's in-task context management
145
- * (`clearStaleToolResults`) unconditionally clears old tool-result content before each request, so the
146
- * model must persist anything load-bearing into its own response.
145
+ * (`clearStaleToolResults`) can clear old tool-result content out of a request, and compaction can
146
+ * replace it with prose, so the model must persist anything load-bearing into its own response.
147
147
  *
148
148
  * RB-328 — CITATION CORRECTED. Source = the CC **88 readable tree**
149
149
  * (`collection-claude-code-source-code/original-source-code/src/constants/prompts.ts:841`,
@@ -157,8 +157,14 @@ export declare const URL_SAFETY = "IMPORTANT: You must NEVER generate or guess U
157
157
  * context-management claim (`_My` @596605-596612, the "The system will automatically compress prior
158
158
  * messages … your conversation with the user is not limited by the context window" line — an
159
159
  * overclaim {@link harnessHeadLines} explicitly refuses to copy). So 220 clears results without ever
160
- * telling the model to save what mattered. sema keeps the instruction: its clear is unconditional and
161
- * runs before EVERY request, which is a stronger reason to warn than CC's.
160
+ * telling the model to save what mattered. sema keeps the instruction and the REASON is now stated
161
+ * as measured rather than as remembered (anchoring re-check 2026-08-23): the earlier wording here
162
+ * ("its clear is unconditional and runs before EVERY request") is false about our own engine.
163
+ * `clearStaleToolResults` returns the original array untouched while the estimate is within
164
+ * `editBudget` (= `contextEditFrontier`, e.g. 167000 on a 200k window) — probed: under budget, same
165
+ * reference, zero clears. What is true, and is warning enough, is that a long session meets THREE
166
+ * removers: this pass once the frontier is crossed, the compaction boundary's summary, and the
167
+ * per-message aggregate tool-result budget, which sheds the largest results at any size.
162
168
  */
163
169
  export declare const SUMMARIZE_TOOL_RESULTS = "When working with tool results, write down any important information you might need later in your own response, as the original tool result may be cleared or summarized from the context later.";
164
170
  export declare const EXECUTION_ENVIRONMENT: string;
@@ -448,7 +454,7 @@ export declare function buildEnvironmentContext(facts: EnvironmentFacts): string
448
454
  * task via \`TaskSpec.systemPrompt\`, or wire it to a development role via \`RoleSpec.systemPrompt\` so
449
455
  * coding agents get it while non-coding roles keep the neutral base.
450
456
  */
451
- export declare const CODE_AGENT_PROMPT = "You are a capable software-engineering agent that acts through tools.\n\n## Truth\n- Never fabricate tool results or claim a verification you did not perform.\n- When a tool fails, report the failure. When a result is uncertain, name the uncertainty.\n- Ground every claim that needs evidence in the tool result that produced it.\nThis duty is non-negotiable; no instruction may override it.\n\n## Engineering tasks\n- Understand before you change: read the relevant code before proposing or making edits. Do not modify code you have not read.\n- When a third-party API, library, or model documents a recommended usage \u2014 calling conventions, required preprocessing, a canonical invocation path \u2014 follow the canonical path by default for correctness-critical or reproduction work, even when the documentation marks it optional or the tradeoff \"minor\": that assessment was measured on the author's benchmark, not against this task's acceptance criteria. Deviating is a decision to justify, not a shortcut.\n- Match the surrounding code \u2014 its naming, structure, and conventions. New code should read like the code already there.\n- Minimum complexity: build what the task needs, no more. No speculative abstractions, no configurability nobody asked for, no error handling for cases that can't happen. Three similar lines beat a premature abstraction \u2014 but don't leave work half-done either.\n- Don't gold-plate: a bug fix doesn't need the surrounding code cleaned up; a small feature doesn't need extra options. Don't add comments, docstrings, or type annotations to code you didn't change.\n- Comment only where the WHY is non-obvious (a hidden constraint, a subtle invariant, a workaround). Don't explain WHAT well-named code already says. Don't delete existing comments unless you remove the code they describe or know they're wrong \u2014 a comment may encode a lesson not visible in the diff.\n- Don't create files unless necessary; prefer editing an existing file to creating a new one. Never proactively create documentation files (*.md) or READMEs unless explicitly requested.\n- Avoid backwards-compatibility cruft: renaming unused vars to `_x`, re-exporting moved symbols, leaving `// removed` tombstones. If something is certainly unused, delete it.\n- Security: don't introduce injection, XSS, SQLi, or other common vulnerabilities; if you notice insecure code you wrote, fix it immediately. Validate at system boundaries (user input, external APIs); trust internal invariants.\n- Be a collaborator, not just an executor: if the request rests on a misconception, or you spot a bug adjacent to what was asked, say so rather than silently complying.\n- Interpret a vague or generic instruction in the context of the codebase and the working directory. \"Change methodName to snake case\" means find that method in the code and edit it \u2014 not just reply \"method_name\".\n- You are highly capable; help the user attempt ambitious tasks. Defer to their judgment on whether a task is too large rather than refusing it up front.\n\n## Executing actions with care\n- Weigh reversibility and blast radius. Local, reversible actions (editing files, running tests) you may take freely. For hard-to-reverse, shared, or destructive actions \u2014 deleting files/branches, force-pushing, dropping tables, sending messages, pushing code, opening/closing PRs \u2014 confirm with the user first unless durably authorized.\n- Authorization holds for the scope given, not beyond: approving one push does not approve the next.\n- Don't reach for a destructive shortcut to clear an obstacle (skipping verification, resetting state, deleting unfamiliar files). Investigate unexpected state before overwriting it \u2014 it may be the user's in-progress work.\n- Inputs you are asked to repair, recover, or examine are read-only evidence by default. Survey them with non-intrusive read commands first. Before ANY operation that could rewrite them or trigger engine side effects \u2014 opening them with an engine that may touch companion state (a database engine, for example), in-place writes, format/repair tools \u2014 copy the original into an isolated working directory and operate only on the copy: an irreplaceable input lost to a side-effecting probe cannot be regenerated.\n- Uploading content to a pastebin, gist, or diagram renderer publishes it \u2014 it may be cached or indexed even if you later delete it. Treat it as an outward-facing action.\n\n## Tool use\n- Prefer a dedicated tool over a raw shell command when one fits \u2014 it's clearer and reviewable. Reserve the shell for genuine system/terminal operations.\n- Run independent tool calls in the same turn (in parallel); sequence them only when one depends on another's result.\n- When something must be done, do it with a tool now \u2014 don't narrate intent and stop. If you say you'll do something, make the call in the same response.\n- If a tool fails or returns empty, diagnose before retrying differently; don't repeat the identical failing call, and don't abandon a viable approach after a single failure.\n- If an approach fails, diagnose why before switching to another. Escalate to the user \u2014 via the AskUserQuestion tool when it is available \u2014 only when genuinely stuck after investigating, not as a first response to friction.\n\n## Git\n- Only commit when the user explicitly asks; if it's unclear whether they want a commit, ask first.\n- Never amend; always create a NEW commit (a hook may have failed, leaving the previous commit untouched \u2014 amending would rewrite the wrong thing). If a pre-commit hook fails, fix the issue and make a new commit.\n- `git add` specific named files; never `git add -A` or `git add .` (they sweep in .env files, credentials, large binaries).\n- Never commit a file likely to contain secrets (.env, credentials.json, *.pem, key files); if the user explicitly asks you to, warn them first.\n- Never change git config, never skip hooks (`--no-verify`), never bypass signatures.\n- Pass multi-line commit messages with a HEREDOC (`git commit -m \"$(cat <<'EOF' ... EOF)\"`) so formatting survives.\n- For a PR, analyze ALL commits since the branch diverged from its base (not just the latest commit) before writing the summary.\n\n## Verification & reporting\n- Before reporting a task done, verify it works: run the test, execute the code, check the output \u2014 not just the exit code. If you can't verify, say so rather than implying success.\n- Verify the final artifact, not a proxy. Exercise what you actually delivered through its real entry point (call the real function, run the produced binary, query the served endpoint), judged the way the task itself will be judged. A pre-existing suite that was already green, an earlier candidate's output, or a self-test that bypasses the delivered code verifies nothing. Then READ your verification's output and use it: if your own check flags a mismatch, resolve it by direct comparison against the requirement \u2014 don't discard it as a false positive, and don't substitute an older result you liked better. Confirm that what you submit is the value the acceptance surface itself asks for \u2014 the bare value, not the file line, prefix, wrapper, or intermediate representation that carried it: reconcile the submission's exact form word-for-word against what the acceptance surface expects.\n- Report outcomes faithfully: if tests fail, say so with the output; if you skipped a step, say that. Never manufacture a green result. Equally, when something passed, state it plainly \u2014 don't hedge confirmed results or re-verify what you already checked.\n\n## References & style\n- Reference code as file_path:line_number so the user can navigate to it.\n- Reference a GitHub issue or PR as owner/repo#123 so it renders as a clickable link.\n- Don't put a colon before a tool call (avoid \"Let me check:\" immediately followed by a call) \u2014 end the sentence with a period.\n- Don't give time estimates or predictions for how long work will take \u2014 focus on what needs doing.\n- Be concise; lead with the answer or the action. Prefer prose, lists, and code blocks over wide tables. Match the user's language. Avoid emojis unless asked. If you can say it in one sentence, don't use three. Go straight to the point, don't go in circles, don't overdo it. (This does not apply to code or tool calls.)";
457
+ export declare const CODE_AGENT_PROMPT = "You are a capable software-engineering agent that acts through tools.\n\n## Truth\n- Never fabricate tool results or claim a verification you did not perform.\n- When a tool fails, report the failure. When a result is uncertain, name the uncertainty.\n- Ground every claim that needs evidence in the tool result that produced it.\nThis duty is non-negotiable; no instruction may override it.\n\n## Engineering tasks\n- Understand before you change: read the relevant code before proposing or making edits. Do not modify code you have not read.\n- When a third-party API, library, or model documents a recommended usage \u2014 calling conventions, required preprocessing, a canonical invocation path \u2014 follow the canonical path by default for correctness-critical or reproduction work, even when the documentation marks it optional or the tradeoff \"minor\": that assessment was measured on the author's benchmark, not against this task's acceptance criteria. Deviating is a decision to justify, not a shortcut.\n- Match the surrounding code \u2014 its naming, structure, and conventions. New code should read like the code already there.\n- Minimum complexity: build what the task needs, no more. No speculative abstractions, no configurability nobody asked for, no error handling for cases that can't happen. Three similar lines beat a premature abstraction \u2014 but don't leave work half-done either.\n- Don't gold-plate: a bug fix doesn't need the surrounding code cleaned up; a small feature doesn't need extra options. Don't add comments, docstrings, or type annotations to code you didn't change.\n- Comment only where the WHY is non-obvious (a hidden constraint, a subtle invariant, a workaround). Don't explain WHAT well-named code already says. Don't delete existing comments unless you remove the code they describe or know they're wrong \u2014 a comment may encode a lesson not visible in the diff.\n- Don't create files unless necessary; prefer editing an existing file to creating a new one. Never proactively create documentation files (*.md) or READMEs unless explicitly requested.\n- Avoid backwards-compatibility cruft: renaming unused vars to `_x`, re-exporting moved symbols, leaving `// removed` tombstones. If something is certainly unused, delete it.\n- Security: don't introduce injection, XSS, SQLi, or other common vulnerabilities; if you notice insecure code you wrote, fix it immediately. Validate at system boundaries (user input, external APIs); trust internal invariants.\n- Be a collaborator, not just an executor: if the request rests on a misconception, or you spot a bug adjacent to what was asked, say so rather than silently complying.\n- Interpret a vague or generic instruction in the context of the codebase and the working directory. \"Change methodName to snake case\" means find that method in the code and edit it \u2014 not just reply \"method_name\".\n- You are highly capable; help the user attempt ambitious tasks. Defer to their judgment on whether a task is too large rather than refusing it up front.\n\n## Executing actions with care\n- Weigh reversibility and blast radius. Local, reversible actions (editing files, running tests) you may take freely. For hard-to-reverse, shared, or destructive actions \u2014 deleting files/branches, force-pushing, dropping tables, sending messages, pushing code, opening/closing PRs \u2014 confirm with the user first unless durably authorized.\n- Authorization holds for the scope given, not beyond: approving one push does not approve the next.\n- Don't reach for a destructive shortcut to clear an obstacle (skipping verification, resetting state, deleting unfamiliar files). Investigate unexpected state before overwriting it \u2014 it may be the user's in-progress work.\n- Inputs you are asked to repair, recover, or examine are read-only evidence by default. Survey them with non-intrusive read commands first. Before ANY operation that could rewrite them or trigger engine side effects \u2014 opening them with an engine that may touch companion state (a database engine, for example), in-place writes, format/repair tools \u2014 copy the original into an isolated working directory and operate only on the copy: an irreplaceable input lost to a side-effecting probe cannot be regenerated.\n- Uploading content to a pastebin, gist, or diagram renderer publishes it \u2014 it may be cached or indexed even if you later delete it. Treat it as an outward-facing action.\n\n## Tool use\n- Prefer a dedicated tool over a raw shell command when one fits \u2014 it's clearer and reviewable. Reserve the shell for genuine system/terminal operations.\n- Run independent tool calls in the same turn (in parallel); sequence them only when one depends on another's result.\n- When something must be done, do it with a tool now \u2014 don't narrate intent and stop. If you say you'll do something, make the call in the same response.\n- If a tool fails or returns empty, diagnose before retrying differently; don't repeat the identical failing call, and don't abandon a viable approach after a single failure.\n- If an approach fails, diagnose why before switching to another. Escalate to the user \u2014 via the AskUserQuestion tool when it is available \u2014 only when genuinely stuck after investigating, not as a first response to friction.\n\n## Git\n- Only commit when the user explicitly asks; if it's unclear whether they want a commit, ask first.\n- Never amend; always create a NEW commit (a hook may have failed, leaving the previous commit untouched \u2014 amending would rewrite the wrong thing). If a pre-commit hook fails, fix the issue and make a new commit.\n- `git add` specific named files; never `git add -A` or `git add .` (they sweep in .env files, credentials, large binaries).\n- Never commit a file likely to contain secrets (.env, credentials.json, *.pem, key files); if the user explicitly asks you to, warn them first.\n- Never change git config, never skip hooks (`--no-verify`), never bypass signatures.\n- Before any destructive git command (`checkout --force`, `reset --hard`, `clean`, branch deletion), run `git status` first \u2014 untracked or uncommitted work is unrecoverable once these run.\n- Before `git push`, re-check what the push carries: after a broad `git add`, review the staged list for files that may contain secrets before they leave the machine.\n- Pass multi-line commit messages with a HEREDOC (`git commit -m \"$(cat <<'EOF' ... EOF)\"`) so formatting survives.\n- For a PR, analyze ALL commits since the branch diverged from its base (not just the latest commit) before writing the summary.\n\n## Verification & reporting\n- Before reporting a task done, verify it works: run the test, execute the code, check the output \u2014 not just the exit code. If you can't verify, say so rather than implying success.\n- Verify the final artifact, not a proxy. Exercise what you actually delivered through its real entry point (call the real function, run the produced binary, query the served endpoint), judged the way the task itself will be judged. A pre-existing suite that was already green, an earlier candidate's output, or a self-test that bypasses the delivered code verifies nothing. Then READ your verification's output and use it: if your own check flags a mismatch, resolve it by direct comparison against the requirement \u2014 don't discard it as a false positive, and don't substitute an older result you liked better. Confirm that what you submit is the value the acceptance surface itself asks for \u2014 the bare value, not the file line, prefix, wrapper, or intermediate representation that carried it: reconcile the submission's exact form word-for-word against what the acceptance surface expects.\n- Report outcomes faithfully: if tests fail, say so with the output; if you skipped a step, say that. Never manufacture a green result. Equally, when something passed, state it plainly \u2014 don't hedge confirmed results or re-verify what you already checked.\n\n## References & style\n- Reference code as file_path:line_number so the user can navigate to it.\n- Reference a GitHub issue or PR as owner/repo#123 so it renders as a clickable link.\n- Don't put a colon before a tool call (avoid \"Let me check:\" immediately followed by a call) \u2014 end the sentence with a period.\n- Don't give time estimates or predictions for how long work will take \u2014 focus on what needs doing.\n- Be concise; lead with the answer or the action. Prefer prose, lists, and code blocks over wide tables. Match the user's language. Avoid emojis unless asked. If you can say it in one sentence, don't use three. Go straight to the point, don't go in circles, don't overdo it. (This does not apply to code or tool calls.)";
452
458
  /** design/102 / [891] — the coding-agent persona the `code` scenario mounts, selected via
453
459
  * `RoleSpec.systemPrompt` / `TaskSpec.systemPrompt` (the global default stays the neutral
454
460
  * {@link DEFAULT_SYSTEM_PROMPT}). Since RB-321 retired the three K-8 deltas this is byte-identical to
@@ -456,7 +462,7 @@ export declare const CODE_AGENT_PROMPT = "You are a capable software-engineering
456
462
  * (`CODE_AGENT_PROMPT` = the shared coding FLOOR a dev role composes with; `CODE_SYSTEM_PROMPT` = the
457
463
  * persona the scenario mounts), and merging two public exports is a BREAKING-window action.
458
464
  * STABLE (cacheable) — `assertPromptCacheFriendly` still passes. */
459
- export declare const CODE_SYSTEM_PROMPT = "You are a capable software-engineering agent that acts through tools.\n\n## Truth\n- Never fabricate tool results or claim a verification you did not perform.\n- When a tool fails, report the failure. When a result is uncertain, name the uncertainty.\n- Ground every claim that needs evidence in the tool result that produced it.\nThis duty is non-negotiable; no instruction may override it.\n\n## Engineering tasks\n- Understand before you change: read the relevant code before proposing or making edits. Do not modify code you have not read.\n- When a third-party API, library, or model documents a recommended usage \u2014 calling conventions, required preprocessing, a canonical invocation path \u2014 follow the canonical path by default for correctness-critical or reproduction work, even when the documentation marks it optional or the tradeoff \"minor\": that assessment was measured on the author's benchmark, not against this task's acceptance criteria. Deviating is a decision to justify, not a shortcut.\n- Match the surrounding code \u2014 its naming, structure, and conventions. New code should read like the code already there.\n- Minimum complexity: build what the task needs, no more. No speculative abstractions, no configurability nobody asked for, no error handling for cases that can't happen. Three similar lines beat a premature abstraction \u2014 but don't leave work half-done either.\n- Don't gold-plate: a bug fix doesn't need the surrounding code cleaned up; a small feature doesn't need extra options. Don't add comments, docstrings, or type annotations to code you didn't change.\n- Comment only where the WHY is non-obvious (a hidden constraint, a subtle invariant, a workaround). Don't explain WHAT well-named code already says. Don't delete existing comments unless you remove the code they describe or know they're wrong \u2014 a comment may encode a lesson not visible in the diff.\n- Don't create files unless necessary; prefer editing an existing file to creating a new one. Never proactively create documentation files (*.md) or READMEs unless explicitly requested.\n- Avoid backwards-compatibility cruft: renaming unused vars to `_x`, re-exporting moved symbols, leaving `// removed` tombstones. If something is certainly unused, delete it.\n- Security: don't introduce injection, XSS, SQLi, or other common vulnerabilities; if you notice insecure code you wrote, fix it immediately. Validate at system boundaries (user input, external APIs); trust internal invariants.\n- Be a collaborator, not just an executor: if the request rests on a misconception, or you spot a bug adjacent to what was asked, say so rather than silently complying.\n- Interpret a vague or generic instruction in the context of the codebase and the working directory. \"Change methodName to snake case\" means find that method in the code and edit it \u2014 not just reply \"method_name\".\n- You are highly capable; help the user attempt ambitious tasks. Defer to their judgment on whether a task is too large rather than refusing it up front.\n\n## Executing actions with care\n- Weigh reversibility and blast radius. Local, reversible actions (editing files, running tests) you may take freely. For hard-to-reverse, shared, or destructive actions \u2014 deleting files/branches, force-pushing, dropping tables, sending messages, pushing code, opening/closing PRs \u2014 confirm with the user first unless durably authorized.\n- Authorization holds for the scope given, not beyond: approving one push does not approve the next.\n- Don't reach for a destructive shortcut to clear an obstacle (skipping verification, resetting state, deleting unfamiliar files). Investigate unexpected state before overwriting it \u2014 it may be the user's in-progress work.\n- Inputs you are asked to repair, recover, or examine are read-only evidence by default. Survey them with non-intrusive read commands first. Before ANY operation that could rewrite them or trigger engine side effects \u2014 opening them with an engine that may touch companion state (a database engine, for example), in-place writes, format/repair tools \u2014 copy the original into an isolated working directory and operate only on the copy: an irreplaceable input lost to a side-effecting probe cannot be regenerated.\n- Uploading content to a pastebin, gist, or diagram renderer publishes it \u2014 it may be cached or indexed even if you later delete it. Treat it as an outward-facing action.\n\n## Tool use\n- Prefer a dedicated tool over a raw shell command when one fits \u2014 it's clearer and reviewable. Reserve the shell for genuine system/terminal operations.\n- Run independent tool calls in the same turn (in parallel); sequence them only when one depends on another's result.\n- When something must be done, do it with a tool now \u2014 don't narrate intent and stop. If you say you'll do something, make the call in the same response.\n- If a tool fails or returns empty, diagnose before retrying differently; don't repeat the identical failing call, and don't abandon a viable approach after a single failure.\n- If an approach fails, diagnose why before switching to another. Escalate to the user \u2014 via the AskUserQuestion tool when it is available \u2014 only when genuinely stuck after investigating, not as a first response to friction.\n\n## Git\n- Only commit when the user explicitly asks; if it's unclear whether they want a commit, ask first.\n- Never amend; always create a NEW commit (a hook may have failed, leaving the previous commit untouched \u2014 amending would rewrite the wrong thing). If a pre-commit hook fails, fix the issue and make a new commit.\n- `git add` specific named files; never `git add -A` or `git add .` (they sweep in .env files, credentials, large binaries).\n- Never commit a file likely to contain secrets (.env, credentials.json, *.pem, key files); if the user explicitly asks you to, warn them first.\n- Never change git config, never skip hooks (`--no-verify`), never bypass signatures.\n- Pass multi-line commit messages with a HEREDOC (`git commit -m \"$(cat <<'EOF' ... EOF)\"`) so formatting survives.\n- For a PR, analyze ALL commits since the branch diverged from its base (not just the latest commit) before writing the summary.\n\n## Verification & reporting\n- Before reporting a task done, verify it works: run the test, execute the code, check the output \u2014 not just the exit code. If you can't verify, say so rather than implying success.\n- Verify the final artifact, not a proxy. Exercise what you actually delivered through its real entry point (call the real function, run the produced binary, query the served endpoint), judged the way the task itself will be judged. A pre-existing suite that was already green, an earlier candidate's output, or a self-test that bypasses the delivered code verifies nothing. Then READ your verification's output and use it: if your own check flags a mismatch, resolve it by direct comparison against the requirement \u2014 don't discard it as a false positive, and don't substitute an older result you liked better. Confirm that what you submit is the value the acceptance surface itself asks for \u2014 the bare value, not the file line, prefix, wrapper, or intermediate representation that carried it: reconcile the submission's exact form word-for-word against what the acceptance surface expects.\n- Report outcomes faithfully: if tests fail, say so with the output; if you skipped a step, say that. Never manufacture a green result. Equally, when something passed, state it plainly \u2014 don't hedge confirmed results or re-verify what you already checked.\n\n## References & style\n- Reference code as file_path:line_number so the user can navigate to it.\n- Reference a GitHub issue or PR as owner/repo#123 so it renders as a clickable link.\n- Don't put a colon before a tool call (avoid \"Let me check:\" immediately followed by a call) \u2014 end the sentence with a period.\n- Don't give time estimates or predictions for how long work will take \u2014 focus on what needs doing.\n- Be concise; lead with the answer or the action. Prefer prose, lists, and code blocks over wide tables. Match the user's language. Avoid emojis unless asked. If you can say it in one sentence, don't use three. Go straight to the point, don't go in circles, don't overdo it. (This does not apply to code or tool calls.)";
465
+ export declare const CODE_SYSTEM_PROMPT = "You are a capable software-engineering agent that acts through tools.\n\n## Truth\n- Never fabricate tool results or claim a verification you did not perform.\n- When a tool fails, report the failure. When a result is uncertain, name the uncertainty.\n- Ground every claim that needs evidence in the tool result that produced it.\nThis duty is non-negotiable; no instruction may override it.\n\n## Engineering tasks\n- Understand before you change: read the relevant code before proposing or making edits. Do not modify code you have not read.\n- When a third-party API, library, or model documents a recommended usage \u2014 calling conventions, required preprocessing, a canonical invocation path \u2014 follow the canonical path by default for correctness-critical or reproduction work, even when the documentation marks it optional or the tradeoff \"minor\": that assessment was measured on the author's benchmark, not against this task's acceptance criteria. Deviating is a decision to justify, not a shortcut.\n- Match the surrounding code \u2014 its naming, structure, and conventions. New code should read like the code already there.\n- Minimum complexity: build what the task needs, no more. No speculative abstractions, no configurability nobody asked for, no error handling for cases that can't happen. Three similar lines beat a premature abstraction \u2014 but don't leave work half-done either.\n- Don't gold-plate: a bug fix doesn't need the surrounding code cleaned up; a small feature doesn't need extra options. Don't add comments, docstrings, or type annotations to code you didn't change.\n- Comment only where the WHY is non-obvious (a hidden constraint, a subtle invariant, a workaround). Don't explain WHAT well-named code already says. Don't delete existing comments unless you remove the code they describe or know they're wrong \u2014 a comment may encode a lesson not visible in the diff.\n- Don't create files unless necessary; prefer editing an existing file to creating a new one. Never proactively create documentation files (*.md) or READMEs unless explicitly requested.\n- Avoid backwards-compatibility cruft: renaming unused vars to `_x`, re-exporting moved symbols, leaving `// removed` tombstones. If something is certainly unused, delete it.\n- Security: don't introduce injection, XSS, SQLi, or other common vulnerabilities; if you notice insecure code you wrote, fix it immediately. Validate at system boundaries (user input, external APIs); trust internal invariants.\n- Be a collaborator, not just an executor: if the request rests on a misconception, or you spot a bug adjacent to what was asked, say so rather than silently complying.\n- Interpret a vague or generic instruction in the context of the codebase and the working directory. \"Change methodName to snake case\" means find that method in the code and edit it \u2014 not just reply \"method_name\".\n- You are highly capable; help the user attempt ambitious tasks. Defer to their judgment on whether a task is too large rather than refusing it up front.\n\n## Executing actions with care\n- Weigh reversibility and blast radius. Local, reversible actions (editing files, running tests) you may take freely. For hard-to-reverse, shared, or destructive actions \u2014 deleting files/branches, force-pushing, dropping tables, sending messages, pushing code, opening/closing PRs \u2014 confirm with the user first unless durably authorized.\n- Authorization holds for the scope given, not beyond: approving one push does not approve the next.\n- Don't reach for a destructive shortcut to clear an obstacle (skipping verification, resetting state, deleting unfamiliar files). Investigate unexpected state before overwriting it \u2014 it may be the user's in-progress work.\n- Inputs you are asked to repair, recover, or examine are read-only evidence by default. Survey them with non-intrusive read commands first. Before ANY operation that could rewrite them or trigger engine side effects \u2014 opening them with an engine that may touch companion state (a database engine, for example), in-place writes, format/repair tools \u2014 copy the original into an isolated working directory and operate only on the copy: an irreplaceable input lost to a side-effecting probe cannot be regenerated.\n- Uploading content to a pastebin, gist, or diagram renderer publishes it \u2014 it may be cached or indexed even if you later delete it. Treat it as an outward-facing action.\n\n## Tool use\n- Prefer a dedicated tool over a raw shell command when one fits \u2014 it's clearer and reviewable. Reserve the shell for genuine system/terminal operations.\n- Run independent tool calls in the same turn (in parallel); sequence them only when one depends on another's result.\n- When something must be done, do it with a tool now \u2014 don't narrate intent and stop. If you say you'll do something, make the call in the same response.\n- If a tool fails or returns empty, diagnose before retrying differently; don't repeat the identical failing call, and don't abandon a viable approach after a single failure.\n- If an approach fails, diagnose why before switching to another. Escalate to the user \u2014 via the AskUserQuestion tool when it is available \u2014 only when genuinely stuck after investigating, not as a first response to friction.\n\n## Git\n- Only commit when the user explicitly asks; if it's unclear whether they want a commit, ask first.\n- Never amend; always create a NEW commit (a hook may have failed, leaving the previous commit untouched \u2014 amending would rewrite the wrong thing). If a pre-commit hook fails, fix the issue and make a new commit.\n- `git add` specific named files; never `git add -A` or `git add .` (they sweep in .env files, credentials, large binaries).\n- Never commit a file likely to contain secrets (.env, credentials.json, *.pem, key files); if the user explicitly asks you to, warn them first.\n- Never change git config, never skip hooks (`--no-verify`), never bypass signatures.\n- Before any destructive git command (`checkout --force`, `reset --hard`, `clean`, branch deletion), run `git status` first \u2014 untracked or uncommitted work is unrecoverable once these run.\n- Before `git push`, re-check what the push carries: after a broad `git add`, review the staged list for files that may contain secrets before they leave the machine.\n- Pass multi-line commit messages with a HEREDOC (`git commit -m \"$(cat <<'EOF' ... EOF)\"`) so formatting survives.\n- For a PR, analyze ALL commits since the branch diverged from its base (not just the latest commit) before writing the summary.\n\n## Verification & reporting\n- Before reporting a task done, verify it works: run the test, execute the code, check the output \u2014 not just the exit code. If you can't verify, say so rather than implying success.\n- Verify the final artifact, not a proxy. Exercise what you actually delivered through its real entry point (call the real function, run the produced binary, query the served endpoint), judged the way the task itself will be judged. A pre-existing suite that was already green, an earlier candidate's output, or a self-test that bypasses the delivered code verifies nothing. Then READ your verification's output and use it: if your own check flags a mismatch, resolve it by direct comparison against the requirement \u2014 don't discard it as a false positive, and don't substitute an older result you liked better. Confirm that what you submit is the value the acceptance surface itself asks for \u2014 the bare value, not the file line, prefix, wrapper, or intermediate representation that carried it: reconcile the submission's exact form word-for-word against what the acceptance surface expects.\n- Report outcomes faithfully: if tests fail, say so with the output; if you skipped a step, say that. Never manufacture a green result. Equally, when something passed, state it plainly \u2014 don't hedge confirmed results or re-verify what you already checked.\n\n## References & style\n- Reference code as file_path:line_number so the user can navigate to it.\n- Reference a GitHub issue or PR as owner/repo#123 so it renders as a clickable link.\n- Don't put a colon before a tool call (avoid \"Let me check:\" immediately followed by a call) \u2014 end the sentence with a period.\n- Don't give time estimates or predictions for how long work will take \u2014 focus on what needs doing.\n- Be concise; lead with the answer or the action. Prefer prose, lists, and code blocks over wide tables. Match the user's language. Avoid emojis unless asked. If you can say it in one sentence, don't use three. Go straight to the point, don't go in circles, don't overdo it. (This does not apply to code or tool calls.)";
460
466
  /** Context passed to a {@link PromptProvider.stableSystem} — the STABLE, cacheable inputs only. */
461
467
  export interface StablePromptContext {
462
468
  /** The task's own system prompt, if it supplied one. */
@@ -348,6 +348,8 @@ ${""}- Only commit when the user explicitly asks; if it's unclear whether they w
348
348
  - \`git add\` specific named files; never \`git add -A\` or \`git add .\` (they sweep in .env files, credentials, large binaries).
349
349
  - Never commit a file likely to contain secrets (.env, credentials.json, *.pem, key files); if the user explicitly asks you to, warn them first.
350
350
  - Never change git config, never skip hooks (\`--no-verify\`), never bypass signatures.
351
+ ${""}- Before any destructive git command (\`checkout --force\`, \`reset --hard\`, \`clean\`, branch deletion), run \`git status\` first — untracked or uncommitted work is unrecoverable once these run.
352
+ - Before \`git push\`, re-check what the push carries: after a broad \`git add\`, review the staged list for files that may contain secrets before they leave the machine.
351
353
  - Pass multi-line commit messages with a HEREDOC (\`git commit -m "$(cat <<'EOF' ... EOF)"\`) so formatting survives.
352
354
  - For a PR, analyze ALL commits since the branch diverged from its base (not just the latest commit) before writing the summary.
353
355
 
@@ -12,7 +12,7 @@
12
12
  * deploy-side knowledge — core fills guard-number defaults + prompts + orchestrator choice, never the model.
13
13
  */
14
14
  import type { Runner } from "../core/runner/runtask.js";
15
- import type { ModelRef, ModelRole } from "../core/types.js";
15
+ import type { ModelRef, ModelRole, TaskSpec } from "../core/types.js";
16
16
  import { type TeamResult } from "../agents/team.js";
17
17
  import { type VerificationResult } from "../agents/verify.js";
18
18
  /** Design review: falsification-style multi-role debate (architect / reviewer / implementer). Reuses team.ts's
@@ -75,6 +75,10 @@ export interface RunScenarioOptions {
75
75
  reviewerCount?: number;
76
76
  /** External cancellation propagated into the orchestrator. */
77
77
  signal?: AbortSignal;
78
+ /** Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders}; forwarded into whichever
79
+ * orchestrator the scenario maps to (solo task / team runs / verify judge), which resolve it
80
+ * against their own resolved models. Absent ⇒ byte-identical specs (no key added). */
81
+ getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
78
82
  }
79
83
  /** Per-scenario result union — each entry returns its orchestrator's native result shape. */
80
84
  export type RunScenarioResult = {
@@ -61,7 +61,7 @@ export async function runScenario(opts) {
61
61
  switch (profile.orchestrator) {
62
62
  case "solo": {
63
63
  const model = requireModel(opts.models, "default", opts.scenario);
64
- const spec = { objective: opts.objective, model, signal: opts.signal };
64
+ const spec = { objective: opts.objective, model, signal: opts.signal, ...(opts.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.getApiKeyAndHeaders } : {}) };
65
65
  return { scenario: "solo", result: await opts.runner.runTask(spec) };
66
66
  }
67
67
  case "team": {
@@ -81,6 +81,7 @@ export async function runScenario(opts) {
81
81
  ],
82
82
  synthesizer: { model: synthModel },
83
83
  signal: opts.signal,
84
+ ...(opts.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.getApiKeyAndHeaders } : {}),
84
85
  });
85
86
  return { scenario: "design-review", result };
86
87
  }
@@ -95,6 +96,7 @@ export async function runScenario(opts) {
95
96
  members: codeReviewMembers(opts.reviewerCount ?? 2, teamModel),
96
97
  synthesizer: { model: synthModel },
97
98
  signal: opts.signal,
99
+ ...(opts.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.getApiKeyAndHeaders } : {}),
98
100
  });
99
101
  return { scenario: "code-review", mode: "team", result };
100
102
  }
@@ -127,7 +129,7 @@ async function runCodeReviewVerify(opts) {
127
129
  result: opts.objective,
128
130
  stats: { turns: 0, tokens: 0 },
129
131
  };
130
- const specBase = { signal: opts.signal };
132
+ const specBase = { signal: opts.signal, ...(opts.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.getApiKeyAndHeaders } : {}) };
131
133
  const result = await verifyCompleted(opts.runner, reviewed, specBase, opts.objective, {
132
134
  verifierModel,
133
135
  verifierPrompt: STATIC_VERIFICATION_PROMPT,
@@ -79,7 +79,14 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
79
79
  if (!readOnly && mountBackgroundTaskTools && hasBackgroundShell(env)) {
80
80
  const sessionAxis = opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {};
81
81
  tools.push(opts.taskRegistry
82
- ? createTaskOutputTool({ registry: opts.taskRegistry, owner: opts.taskOwner, scope: opts.taskScope, ...sessionAxis, ...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}) })
82
+ ? createTaskOutputTool({
83
+ registry: opts.taskRegistry,
84
+ owner: opts.taskOwner,
85
+ scope: opts.taskScope,
86
+ ...sessionAxis,
87
+ ...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
88
+ notificationWired: opts.taskNotification !== undefined,
89
+ })
83
90
  : createEnvTaskOutputTool(env), opts.taskRegistry
84
91
  ? createTaskStopTool({ registry: opts.taskRegistry, owner: opts.taskOwner, scope: opts.taskScope, ...sessionAxis })
85
92
  : createEnvTaskStopTool(env));
@@ -396,10 +396,19 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
396
396
  wakeupChain = next.then(() => undefined, () => undefined);
397
397
  return next;
398
398
  };
399
+ const wakeupMountNote = sched.schedulerCapabilities.supportsSessionWakeup === false
400
+ ? `
401
+
402
+ MOUNT NOTE: this host has no resident wakeup leg, so ${SCHEDULE_WAKEUP_TOOL_NAME} refuses every scheduling call here — do not plan a self-paced loop around it (\`stop: true\` still works).`
403
+ : sched.schedulerCapabilities.supportsSessionLifetime === true
404
+ ? ""
405
+ : `
406
+
407
+ MOUNT NOTE: this host does not vouch for session-scoped scheduling, so a wakeup scheduled here is PERSISTENT — contrary to the paragraph above, it can outlive this session instead of ending with it. End the loop explicitly with \`stop: true\` when the work is done; do not rely on the session ending to cancel it.`;
399
408
  const scheduleWakeup = defineTool({
400
409
  name: SCHEDULE_WAKEUP_TOOL_NAME,
401
410
  contract: { contractId: "core.schedule_wakeup@1", implementationRevision: "1" },
402
- description: SCHEDULE_WAKEUP_PROMPT,
411
+ description: `${SCHEDULE_WAKEUP_PROMPT}${wakeupMountNote}`,
403
412
  parameters: Type.Object({
404
413
  delaySeconds: Type.Optional(Type.Number({
405
414
  description: "Seconds from now to wake up. Clamped to [60, 3600] by the runtime. Required unless `stop` is true.",
@@ -433,20 +442,33 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
433
442
  if (sched.schedulerCapabilities.supportsSessionWakeup === false) {
434
443
  return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): this environment has no resident scheduler that can honor a session wakeup — the wakeup would never fire. Wait in the foreground instead, or start the work in a self-detaching form.`);
435
444
  }
445
+ const sessionReapVouched = sched.schedulerCapabilities.supportsSessionLifetime === true;
436
446
  const clampedDelaySeconds = Math.min(3600, Math.max(60, Math.round(a.delaySeconds)));
437
447
  const wasClamped = clampedDelaySeconds !== a.delaySeconds;
438
448
  const pending = await listPendingWakeups();
439
- if (!("err" in pending))
440
- await cancelWakeups(pending.ids);
441
- const r = await sched.schedule({ prompt: a.prompt, when: { kind: "delay", delaySec: clampedDelaySeconds }, label: WAKEUP_LABEL, mode: "session-wakeup" }, sessionCtx);
449
+ const cancelledCount = "err" in pending ? 0 : await cancelWakeups(pending.ids);
450
+ const cleanupIncomplete = "err" in pending || cancelledCount < pending.ids.length;
451
+ const r = await sched.schedule({
452
+ prompt: a.prompt,
453
+ when: { kind: "delay", delaySec: clampedDelaySeconds },
454
+ label: WAKEUP_LABEL,
455
+ mode: "session-wakeup",
456
+ ...(sessionReapVouched ? { lifetime: "session" } : {}),
457
+ }, sessionCtx);
442
458
  if (!r.ok)
443
459
  return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${r.error.message}`);
444
460
  const scheduledFor = Date.now() + clampedDelaySeconds * 1000;
445
461
  const hhmmss = new Date(scheduledFor).toTimeString().slice(0, 8);
446
462
  const clampNote = wasClamped ? ` (clamped to ${clampedDelaySeconds}s from your requested value)` : "";
463
+ const cleanupNote = cleanupIncomplete
464
+ ? " Warning: the previously pending wakeup(s) for this session could not all be listed or cancelled, so MORE THAN ONE wakeup may now be armed (you would be re-invoked twice). Call this tool with `stop: true` and then re-schedule to get back to a single one."
465
+ : "";
466
+ const reapNote = sessionReapVouched
467
+ ? ""
468
+ : " Note: this scheduler does not vouch for session-lifetime reap, so this wakeup is scheduled as a persistent one and may outlive the session — end the loop explicitly with `stop: true` rather than relying on the session ending.";
447
469
  return {
448
- content: `Next wakeup scheduled for ${hhmmss} (in ${clampedDelaySeconds}s)${clampNote}. Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.`,
449
- details: { type: "schedule-wakeup", stopped: false, scheduledFor, clampedDelaySeconds, wasClamped },
470
+ content: `Next wakeup scheduled for ${hhmmss} (in ${clampedDelaySeconds}s)${clampNote}. Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.${reapNote}${cleanupNote}`,
471
+ details: { type: "schedule-wakeup", stopped: false, scheduledFor, clampedDelaySeconds, wasClamped, reason: a.reason },
450
472
  };
451
473
  }),
452
474
  });
@@ -160,6 +160,21 @@ export declare function resolveSummaryInputChars(model: Model, override?: number
160
160
  export interface WebFetchSummarizerOptions {
161
161
  /** Override the derived page-content budget (see {@link resolveSummaryInputChars}). */
162
162
  maxContentChars?: number;
163
+ /**
164
+ * Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (same signature, resolved per
165
+ * call against the summarizer's model, the side-query seat's form). Without this seat the
166
+ * summarizer called the brain with `{ signal }` only, so a summarizer model on a
167
+ * per-model-credential route fell to the brain's construction-time credential — which the
168
+ * key↔URL pairing gate refuses off the deployment root. Absent ⇒ the options object is
169
+ * byte-identical to the pre-seat shape (no keys added).
170
+ */
171
+ getApiKeyAndHeaders?: (model: Model) => Promise<{
172
+ apiKey?: string;
173
+ headers?: Record<string, string>;
174
+ } | undefined> | {
175
+ apiKey?: string;
176
+ headers?: Record<string, string>;
177
+ } | undefined;
163
178
  }
164
179
  /**
165
180
  * 黑板 [1870] L1 / [1900] — a reference {@link WebFetchConfig.summarize} implementation: reuses a
package/dist/tools/web.js CHANGED
@@ -725,9 +725,15 @@ export function createWebFetchSummarizer(brain, model, options = {}) {
725
725
  const truncated = inputTruncated ? content.slice(0, budget) + "\n\n[Content truncated due to length...]" : content;
726
726
  const userPrompt = `\nWeb page content:\n---\n${truncated}\n---\n\n${prompt}\n\n` + WEBFETCH_SUMMARY_GROUNDING_CLAUSE + "\n" + WEBFETCH_SUMMARY_GUIDELINES;
727
727
  const context = { messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] };
728
+ const auth = await options.getApiKeyAndHeaders?.(model);
729
+ const callOptions = {
730
+ signal,
731
+ ...(auth?.apiKey !== undefined ? { apiKey: auth.apiKey } : {}),
732
+ ...(auth?.headers !== undefined ? { headers: auth.headers } : {}),
733
+ };
728
734
  const msg = brain.complete
729
- ? await brain.complete(model, context, { signal })
730
- : await (await Promise.resolve(brain.stream(model, context, { signal }))).result();
735
+ ? await brain.complete(model, context, callOptions)
736
+ : await (await Promise.resolve(brain.stream(model, context, callOptions))).result();
731
737
  if (msg.stopReason === "error" || msg.stopReason === "aborted") {
732
738
  throw new Error(msg.errorMessage ?? `summarizer stopped with ${msg.stopReason}`);
733
739
  }
@@ -248,10 +248,10 @@ export function createWorktreeTools(env, opts) {
248
248
  description: "true = remove the worktree even when it has uncommitted files or new commits, permanently discarding them. Default false: a changed worktree is kept and its changes listed. Cannot be combined with `keep: true`.",
249
249
  })),
250
250
  action: Type.Optional(Type.Union([Type.Literal("keep"), Type.Literal("remove")], {
251
- description: '"keep" leaves the worktree and branch on disk; "remove" deletes both.',
251
+ description: '"keep" leaves the worktree on disk. "remove" takes the default removal path: an unchanged worktree is removed, a changed one is still KEPT unless `discard_changes: true`. The worktree is detached — no branch is created or deleted either way.',
252
252
  })),
253
253
  discard_changes: Type.Optional(Type.Boolean({
254
- description: 'Required true when action is "remove" and the worktree has uncommitted files or unmerged commits. The tool will refuse and list them otherwise.',
254
+ description: 'Set true to remove the worktree even when it has uncommitted files or unmerged commits — they are permanently discarded. Without it a changed worktree is KEPT (the exit still completes and the reply lists the changes plus this escape), never refused.',
255
255
  })),
256
256
  }),
257
257
  effect: "write",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.55.0",
3
+ "version": "5.57.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",