@sema-agent/server 1.313.0 → 1.315.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 (83) hide show
  1. package/dist/approval-hmac.d.ts +17 -0
  2. package/dist/approval-hmac.js +27 -0
  3. package/dist/auth-bridge.d.ts +3 -2
  4. package/dist/bench/s1/live-deps.js +2 -2
  5. package/dist/budget.d.ts +4 -76
  6. package/dist/budget.js +4 -83
  7. package/dist/capabilities/sandbox-file-send.d.ts +2 -1
  8. package/dist/config-center/apply-effective.d.ts +22 -0
  9. package/dist/config-center/apply-effective.js +283 -0
  10. package/dist/config-center/http-client.d.ts +23 -0
  11. package/dist/config-center/http-client.js +109 -0
  12. package/dist/config-center/restart-signal.d.ts +12 -0
  13. package/dist/config-center/restart-signal.js +70 -0
  14. package/dist/config-center/skills-mcp.d.ts +13 -0
  15. package/dist/config-center/skills-mcp.js +113 -0
  16. package/dist/config-center/types.d.ts +143 -0
  17. package/dist/config-center/types.js +2 -0
  18. package/dist/config-types.d.ts +1 -1
  19. package/dist/elicitation.d.ts +5 -3
  20. package/dist/elicitation.js +3 -2
  21. package/dist/fleet/fleet-bus.js +92 -83
  22. package/dist/fleet-lease.d.ts +1 -1
  23. package/dist/fleet-lease.js +1 -1
  24. package/dist/hooks/hook-llm.d.ts +5 -4
  25. package/dist/hooks/hook-runner.d.ts +1 -1
  26. package/dist/hooks/hook-runner.js +19 -10
  27. package/dist/http/server.d.ts +95 -69
  28. package/dist/http/server.js +75 -86
  29. package/dist/index.d.ts +1 -1
  30. package/dist/key-resolver.d.ts +1 -1
  31. package/dist/key-resolver.js +1 -1
  32. package/dist/leader/grader-env-factory.d.ts +1 -1
  33. package/dist/leader/grader-env-factory.js +2 -2
  34. package/dist/leader/leader.js +5 -2
  35. package/dist/leader/wire.d.ts +1 -1
  36. package/dist/leader/wire.js +170 -166
  37. package/dist/main.js +487 -476
  38. package/dist/memory-scope.d.ts +18 -0
  39. package/dist/memory-scope.js +38 -0
  40. package/dist/observability/cost-taxonomy.d.ts +34 -0
  41. package/dist/observability/cost-taxonomy.js +26 -0
  42. package/dist/observability/prompt-manifest.d.ts +75 -0
  43. package/dist/observability/prompt-manifest.js +82 -0
  44. package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
  45. package/dist/plugins/checkpoint-store-sql.js +224 -0
  46. package/dist/plugins/image-bake-store-sql.d.ts +53 -0
  47. package/dist/plugins/image-bake-store-sql.js +463 -0
  48. package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
  49. package/dist/plugins/k8s-bg-scripts.js +129 -0
  50. package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
  51. package/dist/plugins/k8s-exec-protocol.js +87 -0
  52. package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
  53. package/dist/plugins/pg-checkpoint-store.js +1 -189
  54. package/dist/plugins/pg-cost-quota.js +3 -143
  55. package/dist/plugins/pg-image-bake.d.ts +1 -40
  56. package/dist/plugins/pg-image-bake.js +1 -420
  57. package/dist/plugins/pg-rate-limiter.d.ts +2 -32
  58. package/dist/plugins/pg-rate-limiter.js +4 -147
  59. package/dist/plugins/remote-env-k8s.d.ts +5 -38
  60. package/dist/plugins/remote-env-k8s.js +7 -213
  61. package/dist/plugins/sql-driver.d.ts +25 -0
  62. package/dist/plugins/sql-driver.js +59 -0
  63. package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
  64. package/dist/plugins/tidb-checkpoint-store.js +1 -191
  65. package/dist/plugins/tidb-image-bake.d.ts +1 -38
  66. package/dist/plugins/tidb-image-bake.js +1 -348
  67. package/dist/plugins/web-search.d.ts +4 -3
  68. package/dist/plugins/write-behind-counter.d.ts +14 -3
  69. package/dist/plugins/write-behind-counter.js +39 -12
  70. package/dist/principal-jwt.d.ts +44 -0
  71. package/dist/principal-jwt.js +95 -0
  72. package/dist/question.d.ts +2 -1
  73. package/dist/question.js +3 -2
  74. package/dist/run-local.js +2 -2
  75. package/dist/runtime-caps-resolver.d.ts +7 -7
  76. package/dist/runtime-caps-resolver.js +3 -3
  77. package/dist/security.d.ts +4 -74
  78. package/dist/security.js +6 -155
  79. package/dist/sema-registry.d.ts +5 -200
  80. package/dist/sema-registry.js +4 -567
  81. package/dist/tool-approval.d.ts +2 -1
  82. package/dist/tool-approval.js +3 -2
  83. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -58,15 +58,15 @@ import { createRegistryJwtVerifier } from "./auth-bridge.js";
58
58
  import { createMetrics } from "./observability/metrics.js";
59
59
  import { setRedactionObserver, redactSecrets } from "./trace/redact.js";
60
60
  import { RateLimiter } from "./observability/rate-limit.js";
61
- import { createHttpServer, explicitOperator } from "./http/server.js";
61
+ import { createHttpServer, explicitOperator, } from "./http/server.js";
62
62
  import { exportMemoryScope } from "./memory-export.js";
63
63
  import { performMemorySync } from "./memory-sync.js";
64
64
  import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
65
65
  import { createLeaderEndpoint } from "./leader/endpoint.js";
66
- import { buildLeaderRunner } from "./leader/wire.js";
66
+ import { createLeaderRunner } from "./leader/wire.js";
67
67
  import { WorkflowNotifyGate, JournalingWorkflowRunStore, FileWorkflowNotifyJournalStore, } from "./orchestration/workflow-notify-journal.js";
68
68
  import { FileWorkflowCompletionInbox, InMemoryWorkflowCompletionInbox, resolveCompletionRoute, resolveServedSession, } from "./orchestration/workflow-completion-inbox.js";
69
- import { buildPricing, buildTracer, buildSideQueryAccountant, cappedCeiling, ModelUsageTracker, PromptManifestTracker } from "./budget.js";
69
+ import { buildPricing, createTracer, createSideQueryAccountant, cappedCeiling, ModelUsageTracker, PromptManifestTracker } from "./budget.js";
70
70
  import { CostQuota } from "./observability/cost-quota.js";
71
71
  import { startOtlpExporter } from "./observability/otel-exporter.js";
72
72
  import { HEARTBEAT_MS, backgroundAgentOutput, taskHandleOutput, taskHandleStop } from "./runs.js";
@@ -84,14 +84,14 @@ import { LocalTaskAttachmentStore } from "./plugins/local-task-attachment-store.
84
84
  import { MinioBlobBackend, SQL_BLOB_DEFAULT_MAX_BYTES } from "./plugins/blob-backend.js";
85
85
  import { TiDBBackgroundAgentStore, PgBackgroundAgentStore, ensureTiDBBackgroundAgentSchema, ensurePgBackgroundAgentSchema } from "./plugins/background-agent-store-sql.js";
86
86
  import { TiDBMailboxStore, PgMailboxStore, ensureTiDBMailboxSchema, ensurePgMailboxSchema } from "./plugins/mailbox-store-sql.js";
87
- import { createPrincipalCapsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
87
+ import { createPrincipalEntitlementsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
88
88
  import { applyRuntimeGovernance, stripDelegationTools } from "./runtime-governance.js";
89
89
  import { parseTaskSettings, applyTaskSettings, coercePermissionMode, withPermissionMode, effectiveThinking, acceptAppendSystemPrompt, hasConstitutionAnchors, providerDropsAppend, MAX_SETTINGS_OUTPUT_STYLE_CHARS } from "./task-settings.js";
90
- import { buildTaskHooks, composeHooks } from "./hooks/hook-runner.js";
90
+ import { createTaskHooks, composeHooks } from "./hooks/hook-runner.js";
91
91
  import { createHookLlm } from "./hooks/hook-llm.js";
92
92
  import { selfOrchestrationFromBody, enableForkFromBody, workflowModelAllowlistFor, normalizeRetainSubagentSessions } from "./task-workflow.js";
93
93
  import { FleetUsageAccumulator, startFleetClientFromEnv } from "./fleet-client.js";
94
- import { buildFleetLeaseFromEnv } from "./fleet-lease.js";
94
+ import { createFleetLeaseFromEnv } from "./fleet-lease.js";
95
95
  import { reclaimOrphansAtBoot } from "./boot-reclaim.js";
96
96
  import { evictLspOnDestroy } from "./lsp-evict.js";
97
97
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-memory.js";
@@ -104,7 +104,7 @@ import { createWorkerHardenedVmRunner } from "./orchestration/hardened-vm-worker
104
104
  import { WorkflowAgentRegistry } from "./orchestration/workflow-agent-steer.js";
105
105
  import { SubagentSteerRegistry } from "./orchestration/subagent-steer.js";
106
106
  import { FleetEventBus, fleetBackgroundChildPublisher } from "./fleet/fleet-bus.js";
107
- import { buildKeyResolver } from "./key-resolver.js";
107
+ import { createKeyResolver } from "./key-resolver.js";
108
108
  function serviceVersion() {
109
109
  try {
110
110
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -668,9 +668,9 @@ async function main() {
668
668
  const modelUsageTracker = new ModelUsageTracker();
669
669
  const promptManifestTracker = new PromptManifestTracker();
670
670
  const fleetUsage = config.configCenter && process.env.FLEET_ADVERTISE_ADDRESS ? new FleetUsageAccumulator() : undefined;
671
- const fleetLease = buildFleetLeaseFromEnv(config, { logger, metrics });
672
- const tracer = buildTracer(metrics, costQuota, modelUsageTracker, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1, promptManifestTracker);
673
- const sideQueryAccounting = buildSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
671
+ const fleetLease = createFleetLeaseFromEnv(config, { logger, metrics });
672
+ const tracer = createTracer(metrics, costQuota, modelUsageTracker, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1, promptManifestTracker);
673
+ const sideQueryAccounting = createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
674
674
  const toolResultStore = backend?.toolResult ? backend.toolResult() : undefined;
675
675
  const sessionPolicyStore = backend ? backend.sessionPolicy() : undefined;
676
676
  const fileSnapshotStore = backend ? backend.fileSnapshot() : undefined;
@@ -975,7 +975,7 @@ async function main() {
975
975
  });
976
976
  }
977
977
  const principalCaps = config.configCenter && !config.configCenter.dryRun
978
- ? createPrincipalCapsClient({
978
+ ? createPrincipalEntitlementsClient({
979
979
  baseUrl: config.configCenter.baseUrl,
980
980
  token: config.configCenter.token,
981
981
  ...(config.configCenter.worker ? { worker: config.configCenter.worker } : {}),
@@ -1190,19 +1190,19 @@ async function main() {
1190
1190
  tracer,
1191
1191
  promptSource,
1192
1192
  sessionStore: subRunnerSessions,
1193
- ...(executionEnvFactory ? { executionEnvFactory } : {}),
1194
- ...(lspManager ? { lspManager } : {}),
1195
- ...(backgroundAgentStore ? { backgroundAgentStore } : {}),
1196
- ...(mailboxStore ? { mailboxStore } : {}),
1197
- ...(rosterStore ? { rosterStore } : {}),
1198
- ...(runnerDeps.onBackgroundChildEvent ? { onBackgroundChildEvent: runnerDeps.onBackgroundChildEvent } : {}),
1199
- ...(runnerDeps.loadProjectMemory ? { loadProjectMemory: runnerDeps.loadProjectMemory } : {}),
1200
- ...(runnerDeps.probeInstructionSources ? { probeInstructionSources: runnerDeps.probeInstructionSources } : {}),
1193
+ executionEnvFactory: executionEnvFactory ? executionEnvFactory : undefined,
1194
+ lspManager: lspManager ? lspManager : undefined,
1195
+ backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
1196
+ mailboxStore: mailboxStore ? mailboxStore : undefined,
1197
+ rosterStore: rosterStore ? rosterStore : undefined,
1198
+ onBackgroundChildEvent: runnerDeps.onBackgroundChildEvent ? runnerDeps.onBackgroundChildEvent : undefined,
1199
+ loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
1200
+ probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
1201
1201
  hooks: deploymentHooks,
1202
- ...(runnerDeps.onError ? { onError: runnerDeps.onError } : {}),
1202
+ onError: runnerDeps.onError ? runnerDeps.onError : undefined,
1203
1203
  toolResultStore,
1204
- ...(sessionPolicyStore ? { sessionPolicyStore } : {}),
1205
- ...(checkpointStore ? { checkpointStore } : {}),
1204
+ sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
1205
+ checkpointStore: checkpointStore ? checkpointStore : undefined,
1206
1206
  });
1207
1207
  const authorize = createAuthorizer(config, sessionStore);
1208
1208
  const runStore = backend ? backend.run() : undefined;
@@ -1575,7 +1575,7 @@ async function main() {
1575
1575
  if (!planeDeferred)
1576
1576
  markRosterLanded(r.effective);
1577
1577
  mutateInPlace(pricing, buildPricing(config.models));
1578
- keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1578
+ keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1579
1579
  const lkgPersisted = await persistLkgDurable(r.effective, r.etag);
1580
1580
  const reasons = restartReasons(effective, r.effective);
1581
1581
  const skillsChangeDetected = reasons.includes("skills");
@@ -1675,7 +1675,7 @@ async function main() {
1675
1675
  if (!planeDeferredLate)
1676
1676
  markRosterLanded(r.effective);
1677
1677
  mutateInPlace(pricing, buildPricing(config.models));
1678
- keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1678
+ keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1679
1679
  effective = r.effective;
1680
1680
  latestEffective = r.effective;
1681
1681
  ccEtag = r.etag;
@@ -1705,7 +1705,7 @@ async function main() {
1705
1705
  if (bootConfigPending)
1706
1706
  void bootConfigPending.then((r) => (lkgBooted ? refreshTick(r) : deferredBootApply(r)), () => { });
1707
1707
  }
1708
- let keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1708
+ let keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1709
1709
  if (process.env.ANTHROPIC_AUTH_TOKEN && !process.env.ANTHROPIC_API_KEY && (Object.keys(config.modelApiKeys).length > 0 || Object.keys(config.modelApiKeyEnv).length > 0)) {
1710
1710
  logger.warn("per_model_key_bearer_gateway_combo", {
1711
1711
  perModelKeys: Object.keys(config.modelApiKeys).length + Object.keys(config.modelApiKeyEnv).length,
@@ -1789,7 +1789,7 @@ async function main() {
1789
1789
  (leaderProvider === "e2b" ||
1790
1790
  (leaderProvider === "k8s" && "s3" in leaderMinio && executionEnvFactory) ||
1791
1791
  (leaderProvider === "host" && executionEnvFactory))
1792
- ? createLeaderEndpoint(buildLeaderRunner({
1792
+ ? createLeaderEndpoint(createLeaderRunner({
1793
1793
  brain, models: config.models, roles: config.roles, pricing, logger,
1794
1794
  fanoutEnabled: config.leaderFanoutEnabled,
1795
1795
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
@@ -1834,73 +1834,77 @@ async function main() {
1834
1834
  if (registryJwtVerifier)
1835
1835
  logger.info("auth_bridge_enabled", { issuer: authBridgeIssuer });
1836
1836
  const hookWakeBus = {};
1837
- const server = createHttpServer({
1838
- runner,
1839
- config,
1840
- authorize,
1841
- sessionStoreLabel: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
1842
- taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
1843
- snapshotBlobSqlCapBytes: backend && backend.kind !== "local" && !config.snapshotBlobStore
1844
- ? (config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
1845
- : undefined,
1846
- modelReady: () => modelReadyState.ready,
1847
- scenarioDetails,
1848
- registryJwtVerifier: registryJwtVerifier ? registryJwtVerifier : undefined,
1849
- hookWakeBus,
1837
+ const stores = {
1850
1838
  runStore,
1851
- fleetBus,
1852
- workflowsCapable,
1853
1839
  resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined,
1840
+ approvalStore,
1854
1841
  approvalExemptionStore: approvalExemptionStore ? approvalExemptionStore : undefined,
1855
- sessionTitler: sessionTitler ? sessionTitler : undefined,
1842
+ checkpointStore,
1856
1843
  sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
1857
1844
  fileSnapshotStore: fileSnapshotStore ? fileSnapshotStore : undefined,
1858
- backend: backend ? backend : undefined,
1859
- sessionMirrorRuling: principalCaps
1860
- ? async (p) => (await principalCaps.executionRuling(p))?.sessionMirror
1861
- : undefined,
1862
- approvalStore,
1863
- checkpointStore,
1845
+ taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
1864
1846
  backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
1865
- parkedReviveTool: parkedReviveTool ? parkedReviveTool : undefined,
1866
- parkedReviveInheritedGate: parkedReviveInheritedGate ? parkedReviveInheritedGate : undefined,
1867
- imageIndex,
1868
- imageBakes,
1869
- leaderEndpoint,
1847
+ sessionStorage: ownerAware,
1870
1848
  workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
1871
1849
  workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
1850
+ imageIndex,
1851
+ imageBakes,
1852
+ outcomeSink: outcomeSink ? outcomeSink : undefined,
1853
+ sendFileLedger: sendFileLedger ? sendFileLedger : undefined,
1854
+ backend: backend ? backend : undefined,
1855
+ };
1856
+ const coordinators = {
1857
+ elicitation: elicitation ? elicitation : undefined,
1858
+ question: question ? question : undefined,
1859
+ toolApproval: toolApproval ? toolApproval : undefined,
1872
1860
  workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined,
1873
1861
  subagentSteerRegistry,
1874
- subagentTaskOutput: (handle, access) => backgroundAgentOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1875
- taskHandleOutput: (handle, access) => taskHandleOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1876
- taskHandleStop: (handle, access) => taskHandleStop(defaultTaskRegistry, handle, access, backgroundAgentStore),
1877
1862
  workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined,
1878
- sessionAudit,
1879
- sessionStorage: ownerAware,
1863
+ fleetBus,
1880
1864
  sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined,
1881
- sessionEventsMaxConnections: sessionWatchRegistry ? posIntEnv(process.env.SESSION_EVENTS_MAX_CONNS, 256, 100_000) : undefined,
1865
+ hookWakeBus,
1866
+ sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined,
1867
+ leaderEndpoint,
1868
+ sessionTitler: sessionTitler ? sessionTitler : undefined,
1869
+ };
1870
+ const seams = {
1871
+ sessionAudit,
1882
1872
  purgeSession: purgeSession ? purgeSession : undefined,
1873
+ subagentTaskOutput: (handle, access) => backgroundAgentOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1874
+ taskHandleOutput: (handle, access) => taskHandleOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1875
+ taskHandleStop: (handle, access) => taskHandleStop(defaultTaskRegistry, handle, access, backgroundAgentStore),
1876
+ memoryExport: memoryExportBackend ? (scope) => exportMemoryScope(memoryExportBackend, scope) : undefined,
1877
+ memorySync: memoryExportBackend && memorySyncCursors
1878
+ ? (scope, syncReq) => performMemorySync(memoryExportBackend, memorySyncCursors, scope, syncReq)
1879
+ : undefined,
1880
+ sessionMirrorRuling: principalCaps
1881
+ ? async (p) => (await principalCaps.executionRuling(p))?.sessionMirror
1882
+ : undefined,
1883
1883
  instrumentDegenerate,
1884
- planCacheProbe,
1885
- modelUsage: modelUsageTracker,
1886
- promptManifests: promptManifestTracker,
1887
- elicitation: elicitation ? elicitation : undefined,
1888
- question: question ? question : undefined,
1889
- toolApproval: toolApproval ? toolApproval : undefined,
1890
- sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined,
1891
- sendFileLedger: sendFileLedger ? sendFileLedger : undefined,
1892
- instanceId,
1884
+ sideQueryAccounting,
1885
+ parkedReviveTool: parkedReviveTool ? parkedReviveTool : undefined,
1886
+ parkedReviveInheritedGate: parkedReviveInheritedGate ? parkedReviveInheritedGate : undefined,
1887
+ };
1888
+ const observability = {
1893
1889
  logger,
1894
1890
  metrics,
1891
+ modelUsage: modelUsageTracker,
1892
+ promptManifests: promptManifestTracker,
1893
+ planCacheProbe,
1894
+ };
1895
+ const governance = {
1896
+ authorize,
1897
+ registryJwtVerifier: registryJwtVerifier ? registryJwtVerifier : undefined,
1895
1898
  rateLimiter,
1896
1899
  costQuota,
1897
1900
  fleetLease: fleetLease ? fleetLease : undefined,
1898
- sideQueryAccounting,
1899
- outcomeSink: outcomeSink ? outcomeSink : undefined,
1900
- memoryExport: memoryExportBackend ? (scope) => exportMemoryScope(memoryExportBackend, scope) : undefined,
1901
- memorySync: memoryExportBackend && memorySyncCursors
1902
- ? (scope, syncReq) => performMemorySync(memoryExportBackend, memorySyncCursors, scope, syncReq)
1903
- : undefined,
1901
+ };
1902
+ const deployment = {
1903
+ sessionStoreLabel: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
1904
+ modelReady: () => modelReadyState.ready,
1905
+ scenarioDetails,
1906
+ workflowsCapable,
1907
+ instanceId,
1904
1908
  capabilities: {
1905
1909
  version: serviceVersion(),
1906
1910
  scenarios: Object.keys(scenarios),
@@ -1910,431 +1914,438 @@ async function main() {
1910
1914
  planeDeferredState: () => planeDeferredNoHandoff,
1911
1915
  drainState,
1912
1916
  storeDegraded: storeBackendDegraded,
1913
- resolveSpec: async (body, _req, auth, opts) => {
1914
- const requested = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario);
1915
- gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), config.remoteExec?.provider ?? "in-process");
1916
- const scenarioName = scenarios[requested] ? requested : "default";
1917
- if (scenarioName === "autonomous")
1918
- logger.warn("scenario_autonomous_deprecated", { alias: "code", note: "renamed by [891]; alias keeps finalVerification pinned — pass scenario:'code' (+ explicit finalVerification if wanted)" });
1919
- const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal);
1920
- const centerDecls = centerPrompts?.declarations;
1921
- const appendLessPack = centerDecls
1922
- ? centerIdentityAssembled(centerDecls, scenarioName, hasConstitutionAnchors)
1923
- : providerDropsAppend(cap.promptProvider, typeof body.systemPrompt === "string" ? body.systemPrompt : undefined);
1924
- const acceptedAppend = acceptAppendSystemPrompt(body.appendSystemPrompt, (detail) => logger.warn("append_system_prompt_dropped", { detail, sessionId: auth?.sessionId ?? null }), appendLessPack);
1925
- const objective = typeof body.objective === "string" ? body.objective : "";
1926
- const parsedSettings = parseTaskSettings(body.settings);
1927
- if (opts?.leg === "fresh" && appendLessPack) {
1928
- const riderPresent = typeof body.appendSystemPrompt === "string" && body.appendSystemPrompt.length > 0;
1929
- if (riderPresent || parsedSettings.settings?.outputStyle) {
1930
- throw new HttpError(400, `${riderPresent ? "appendSystemPrompt" : "settings.outputStyle"} is not supported when the effective prompt is already assembled (constitution anchors in systemPrompt, or a center assembled-identity pack for this scenario): the assembler's pass-through path cannot mount the append slot — fold it into the assembled prompt instead`);
1931
- }
1932
- }
1933
- if (parsedSettings.deferred.length > 0) {
1934
- logger.warn("task_settings_deferred", { fields: parsedSettings.deferred, sessionId: auth?.sessionId ?? null });
1917
+ };
1918
+ const knobs = {
1919
+ snapshotBlobSqlCapBytes: backend && backend.kind !== "local" && !config.snapshotBlobStore
1920
+ ? (config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
1921
+ : undefined,
1922
+ sessionEventsMaxConnections: sessionWatchRegistry ? posIntEnv(process.env.SESSION_EVENTS_MAX_CONNS, 256, 100_000) : undefined,
1923
+ };
1924
+ const resolveSpec = async (body, _req, auth, opts) => {
1925
+ const requested = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario);
1926
+ gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), config.remoteExec?.provider ?? "in-process");
1927
+ const scenarioName = scenarios[requested] ? requested : "default";
1928
+ if (scenarioName === "autonomous")
1929
+ logger.warn("scenario_autonomous_deprecated", { alias: "code", note: "renamed by [891]; alias keeps finalVerification pinned — pass scenario:'code' (+ explicit finalVerification if wanted)" });
1930
+ const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal);
1931
+ const centerDecls = centerPrompts?.declarations;
1932
+ const appendLessPack = centerDecls
1933
+ ? centerIdentityAssembled(centerDecls, scenarioName, hasConstitutionAnchors)
1934
+ : providerDropsAppend(cap.promptProvider, typeof body.systemPrompt === "string" ? body.systemPrompt : undefined);
1935
+ const acceptedAppend = acceptAppendSystemPrompt(body.appendSystemPrompt, (detail) => logger.warn("append_system_prompt_dropped", { detail, sessionId: auth?.sessionId ?? null }), appendLessPack);
1936
+ const objective = typeof body.objective === "string" ? body.objective : "";
1937
+ const parsedSettings = parseTaskSettings(body.settings);
1938
+ if (opts?.leg === "fresh" && appendLessPack) {
1939
+ const riderPresent = typeof body.appendSystemPrompt === "string" && body.appendSystemPrompt.length > 0;
1940
+ if (riderPresent || parsedSettings.settings?.outputStyle) {
1941
+ throw new HttpError(400, `${riderPresent ? "appendSystemPrompt" : "settings.outputStyle"} is not supported when the effective prompt is already assembled (constitution anchors in systemPrompt, or a center assembled-identity pack for this scenario): the assembler's pass-through path cannot mount the append slot — fold it into the assembled prompt instead`);
1935
1942
  }
1936
- let attachmentNotice;
1937
- {
1938
- const reqAtt = body.attachmentIds;
1939
- if (reqAtt !== undefined) {
1940
- if (!Array.isArray(reqAtt) || !reqAtt.every((x) => typeof x === "string" && x.length > 0 && x.length <= 64)) {
1941
- throw new HttpError(400, "`attachmentIds` must be an array of attachment ids (strings)");
1942
- }
1943
- if (reqAtt.length > 16)
1944
- throw new HttpError(400, "`attachmentIds` exceeds the per-task limit (16)");
1945
- if (reqAtt.length > 0) {
1946
- if (!taskAttachmentStore)
1947
- throw new HttpError(501, "attachmentIds require a store backend (DB_BACKEND=tidb|pg|local)");
1948
- if (!auth?.sessionId)
1949
- throw new HttpError(400, "`attachmentIds` requires a session-resolving deployment (no authorizer session)");
1950
- const sid = auth.sessionId;
1951
- const r = await bindAttachmentsForTask({
1952
- store: taskAttachmentStore,
1953
- owner: auth?.principal ?? "default",
1954
- ids: reqAtt,
1955
- sessionId: sid,
1956
- leg: opts?.leg === "fresh" ? "fresh" : "resume",
1957
- nowMs: Date.now(),
1958
- onMissing: (id) => {
1959
- if (opts?.leg === "fresh")
1960
- throw new HttpError(400, `attachment not found: ${id}`);
1961
- logger.warn("attachment_missing_on_resume", { sessionId: sid, id });
1962
- },
1963
- });
1964
- attachmentNotice = r.notice;
1965
- }
1943
+ }
1944
+ if (parsedSettings.deferred.length > 0) {
1945
+ logger.warn("task_settings_deferred", { fields: parsedSettings.deferred, sessionId: auth?.sessionId ?? null });
1946
+ }
1947
+ let attachmentNotice;
1948
+ {
1949
+ const reqAtt = body.attachmentIds;
1950
+ if (reqAtt !== undefined) {
1951
+ if (!Array.isArray(reqAtt) || !reqAtt.every((x) => typeof x === "string" && x.length > 0 && x.length <= 64)) {
1952
+ throw new HttpError(400, "`attachmentIds` must be an array of attachment ids (strings)");
1953
+ }
1954
+ if (reqAtt.length > 16)
1955
+ throw new HttpError(400, "`attachmentIds` exceeds the per-task limit (16)");
1956
+ if (reqAtt.length > 0) {
1957
+ if (!taskAttachmentStore)
1958
+ throw new HttpError(501, "attachmentIds require a store backend (DB_BACKEND=tidb|pg|local)");
1959
+ if (!auth?.sessionId)
1960
+ throw new HttpError(400, "`attachmentIds` requires a session-resolving deployment (no authorizer session)");
1961
+ const sid = auth.sessionId;
1962
+ const r = await bindAttachmentsForTask({
1963
+ store: taskAttachmentStore,
1964
+ owner: auth?.principal ?? "default",
1965
+ ids: reqAtt,
1966
+ sessionId: sid,
1967
+ leg: opts?.leg === "fresh" ? "fresh" : "resume",
1968
+ nowMs: Date.now(),
1969
+ onMissing: (id) => {
1970
+ if (opts?.leg === "fresh")
1971
+ throw new HttpError(400, `attachment not found: ${id}`);
1972
+ logger.warn("attachment_missing_on_resume", { sessionId: sid, id });
1973
+ },
1974
+ });
1975
+ attachmentNotice = r.notice;
1966
1976
  }
1967
1977
  }
1968
- const execLane = config.remoteExec?.provider ?? "in-process";
1969
- if (typeof body.cwd === "string" && body.cwd.length > 0 && auth?.sessionId) {
1970
- if (cwdHonored(config) && isValidCwd(body.cwd))
1971
- setSessionCwd(auth.sessionId, body.cwd);
1972
- else if (inProcessSingleUserLane(config) && isValidCwd(body.cwd) && satisfiedByProcessCwd(body.cwd, realpathSync))
1973
- logger.debug("task_cwd_inherited", { lane: execLane, sessionId: auth.sessionId });
1978
+ }
1979
+ const execLane = config.remoteExec?.provider ?? "in-process";
1980
+ if (typeof body.cwd === "string" && body.cwd.length > 0 && auth?.sessionId) {
1981
+ if (cwdHonored(config) && isValidCwd(body.cwd))
1982
+ setSessionCwd(auth.sessionId, body.cwd);
1983
+ else if (inProcessSingleUserLane(config) && isValidCwd(body.cwd) && satisfiedByProcessCwd(body.cwd, realpathSync))
1984
+ logger.debug("task_cwd_inherited", { lane: execLane, sessionId: auth.sessionId });
1985
+ else
1986
+ logger.warn("task_cwd_ignored", { honored: cwdHonored(config), lane: execLane, sessionId: auth.sessionId });
1987
+ }
1988
+ if (parsedSettings.settings?.shellEnv && auth?.sessionId) {
1989
+ if (cwdHonored(config))
1990
+ setSessionShellEnv(auth.sessionId, parsedSettings.settings.shellEnv);
1991
+ else if (inProcessSingleUserLane(config)) {
1992
+ const requested = parsedSettings.settings.shellEnv;
1993
+ const mismatched = shellEnvMismatchCount(requested, process.env);
1994
+ if (mismatched === 0)
1995
+ logger.debug("task_shell_env_inherited", { lane: execLane, keys: Object.keys(requested).length, sessionId: auth.sessionId });
1974
1996
  else
1975
- logger.warn("task_cwd_ignored", { honored: cwdHonored(config), lane: execLane, sessionId: auth.sessionId });
1997
+ logger.warn("task_shell_env_ignored", { honored: false, lane: execLane, mismatchedKeys: mismatched, sessionId: auth.sessionId });
1976
1998
  }
1977
- if (parsedSettings.settings?.shellEnv && auth?.sessionId) {
1978
- if (cwdHonored(config))
1979
- setSessionShellEnv(auth.sessionId, parsedSettings.settings.shellEnv);
1980
- else if (inProcessSingleUserLane(config)) {
1981
- const requested = parsedSettings.settings.shellEnv;
1982
- const mismatched = shellEnvMismatchCount(requested, process.env);
1983
- if (mismatched === 0)
1984
- logger.debug("task_shell_env_inherited", { lane: execLane, keys: Object.keys(requested).length, sessionId: auth.sessionId });
1985
- else
1986
- logger.warn("task_shell_env_ignored", { honored: false, lane: execLane, mismatchedKeys: mismatched, sessionId: auth.sessionId });
1987
- }
1988
- else
1989
- logger.warn("task_shell_env_ignored", { honored: false, lane: execLane, sessionId: auth.sessionId });
1999
+ else
2000
+ logger.warn("task_shell_env_ignored", { honored: false, lane: execLane, sessionId: auth.sessionId });
2001
+ }
2002
+ const taskHooks = (() => {
2003
+ const hc = parsedSettings.settings?.hooks;
2004
+ if (!hc)
2005
+ return undefined;
2006
+ if (config.requirePrincipal === true) {
2007
+ logger.warn("task_hooks_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
2008
+ return undefined;
1990
2009
  }
1991
- const taskHooks = (() => {
1992
- const hc = parsedSettings.settings?.hooks;
1993
- if (!hc)
2010
+ const effMode = coercePermissionMode(body.permissionMode) ?? parsedSettings.settings?.permissions?.defaultMode;
2011
+ return createTaskHooks(hc, {
2012
+ logger,
2013
+ sessionId: auth?.sessionId ?? "",
2014
+ cwd: (auth?.sessionId ? perSessionCwd.get(auth.sessionId) : undefined) ?? process.cwd(),
2015
+ ...(effMode ? { permissionMode: effMode } : {}),
2016
+ ...(parsedSettings.settings?.shellEnv ? { shellEnv: parsedSettings.settings.shellEnv } : {}),
2017
+ hookLlm,
2018
+ hookAgent,
2019
+ onHookNotice: (n) => fleetBus.publishHookNotice({
2020
+ ...n,
2021
+ ...(auth?.principal ? { ownerScope: auth.principal } : {}),
2022
+ ...(auth?.sessionId ? { ownerSessionId: auth.sessionId } : {}),
2023
+ }),
2024
+ ...(auth?.sessionId
2025
+ ? {
2026
+ wake: async (text) => hookWakeBus.deliver
2027
+ ?
2028
+ hookWakeBus.deliver(auth.sessionId, `[hook asyncRewake] ${redactSecrets(text)}`)
2029
+ : false,
2030
+ }
2031
+ : {}),
2032
+ });
2033
+ })();
2034
+ const rawAddDirs = body.additionalDirectories;
2035
+ const additionalDirectories = cwdHonored(config) ? parseAdditionalDirectories(rawAddDirs) : undefined;
2036
+ if (rawAddDirs !== undefined && !cwdHonored(config)) {
2037
+ logger.warn("task_additional_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
2038
+ }
2039
+ const wireCatalog = expandTiers(config.models, config.tiers) ?? config.models;
2040
+ const picked = resolveTaskModel(body.model ?? parsedSettings.settings?.model, objective, wireCatalog);
2041
+ if (picked.unknownExplicit !== undefined) {
2042
+ metrics.inc("task_model_unknown_fallback_total");
2043
+ logger.warn("task_model_unknown_fallback", { requested: picked.unknownExplicit.slice(0, 120), fallback: picked.model, sessionId: auth?.sessionId ?? null });
2044
+ }
2045
+ if (Array.isArray(body.images) && body.images.length > 0 && !modelSupportsImages(wireCatalog[picked.model])) {
2046
+ metrics.inc("images_omitted_total", { model: picked.model });
2047
+ logger.warn("images_omitted_no_vision_model", { model: picked.model, count: body.images.length });
2048
+ }
2049
+ let resumeAtEntryId;
2050
+ if (typeof body.resumeAt === "string" && body.resumeAt.length > 0) {
2051
+ if (!auth?.sessionId)
2052
+ throw new HttpError(422, "resumeAt requires a session to branch (resume_at.no_session)");
2053
+ if (!resumeAnchorStore || !ownerAware.getLeafId)
2054
+ throw new HttpError(501, "resume-at is not available on this worker (no session-store backend for the anchor map)");
2055
+ resumeAtEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.resumeAt, auth.principal ?? null);
2056
+ if (resumeAtEntryId === undefined)
2057
+ throw new HttpError(404, "resumeAt: no such message in this session (resume_at.unknown_event)");
2058
+ }
2059
+ let rewindFilesToEntryId;
2060
+ if (resumeAtEntryId === undefined && typeof body.rewindFilesTo === "string" && body.rewindFilesTo.length > 0) {
2061
+ if (!auth?.sessionId)
2062
+ throw new HttpError(422, "rewindFilesTo requires a session (rewind_files_to.no_session)");
2063
+ if (!resumeAnchorStore || !ownerAware.getLeafId)
2064
+ throw new HttpError(501, "rewind-files-to is not available on this worker (no session-store backend for the anchor map)");
2065
+ rewindFilesToEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.rewindFilesTo, auth.principal ?? null);
2066
+ if (rewindFilesToEntryId === undefined)
2067
+ throw new HttpError(404, "rewindFilesTo: no such message in this session (rewind_files_to.unknown_event)");
2068
+ }
2069
+ const s4ProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId : undefined);
2070
+ const s4DefaultScopes = s4ProjectId ? config.projects[s4ProjectId]?.defaultScopes : undefined;
2071
+ const spec = {
2072
+ objective: attachmentNotice ? `${picked.cleanedObjective}\n\n${attachmentNotice}` : picked.cleanedObjective,
2073
+ systemPrompt: typeof body.systemPrompt === "string" ? body.systemPrompt : undefined,
2074
+ appendSystemPrompt: acceptedAppend,
2075
+ sessionId: auth?.sessionId,
2076
+ principal: auth?.principal,
2077
+ images: body.images,
2078
+ ...(body.clientContext ? { clientContext: { timeZone: body.clientContext.timeZone, userEmail: body.clientContext.userEmail } } : {}),
2079
+ ...(() => {
2080
+ const ct = body.compaction?.clampTolerance;
2081
+ return typeof ct === "number" && Number.isFinite(ct) && ct >= 0 && ct <= 1 ? { compaction: { clampTolerance: ct } } : {};
2082
+ })(),
2083
+ outputSchema: body.outputSchema,
2084
+ ...(typeof body.outputRetries === "number" && Number.isFinite(body.outputRetries) && body.outputRetries >= 1
2085
+ ? { outputRetries: Math.min(10, Math.floor(body.outputRetries)) }
2086
+ : {}),
2087
+ resumeAt: resumeAtEntryId,
2088
+ resumeAtMode: normalizeResumeAtMode(body.resumeAtMode, resumeAtEntryId !== undefined),
2089
+ requireExistingSession: body.requireExistingSession === true ? true : undefined,
2090
+ enableFork: enableForkFromBody(body, config, Boolean(centerRuntimeCapsResolver)),
2091
+ suggestNextPrompts: normalizeSuggestNextPrompts(body.suggestNextPrompts),
2092
+ rewindFiles: body.rewindFiles === true ? true : undefined,
2093
+ rewindFilesTo: rewindFilesToEntryId,
2094
+ additionalDirectories,
2095
+ enablePlanMode: config.planModeEnabled ? true : undefined,
2096
+ selfOrchestration: selfOrchestrationFromBody({ selfOrchestration: body.selfOrchestration === true || parsedSettings.settings?.ultracode === true }, config, Boolean(centerRuntimeCapsResolver)),
2097
+ forwardSubagentEvents: body.forwardSubagentEvents === true ? true : undefined,
2098
+ retainSubagentSessions: normalizeRetainSubagentSessions(body.retainSubagentSessions),
2099
+ ...taskAgentsSpecFragment(body.agents, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })),
2100
+ ...(retainBackgroundProcessesFromBody(body.retainBackgroundProcesses, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })) === true
2101
+ ? { retainBackgroundProcesses: true }
2102
+ : {}),
2103
+ ...(() => { const v = toolNameListFromBody(body.excludeTools); return v ? { excludeTools: v } : {}; })(),
2104
+ ...(() => { const v = toolNameListFromBody(body.deferTools); return v ? { deferTools: v } : {}; })(),
2105
+ ...(() => { const v = promptProfileFromBody(body.promptProfile); return v ? { promptProfile: v } : {}; })(),
2106
+ ...(typeof body.interactiveTools === "boolean"
2107
+ ? { interactiveTools: body.interactiveTools }
2108
+ : {}),
2109
+ resilience: normalizeResilience(body.resilience, explicitOperator(auth?.principal, config.operatorPrincipals)),
2110
+ finalVerification: cap.finalVerification === true || body.finalVerification === true ? true : undefined,
2111
+ attachments: normalizeAttachments(body.attachments),
2112
+ ...(taskHooks ? { hooks: composeHooks(deploymentHooks, taskHooks) } : {}),
2113
+ model: picked.model,
2114
+ ...((() => {
2115
+ if (typeof body.compactionModel !== "string" || body.compactionModel.length === 0)
2116
+ return {};
2117
+ const cm = matchCatalogModel(body.compactionModel, wireCatalog);
2118
+ if (cm === undefined) {
2119
+ if (opts?.leg === "fresh") {
2120
+ throw new HttpError(400, `unknown compactionModel "${body.compactionModel.slice(0, 120)}" — not in the configured catalog (name, tier word, or id; a catalog refresh may have removed it mid-request)`);
2121
+ }
2122
+ metrics.inc("task_model_unknown_fallback_total");
2123
+ logger.warn("compaction_model_unknown_fallback", { requested: body.compactionModel.slice(0, 120), sessionId: auth?.sessionId ?? null });
2124
+ return {};
2125
+ }
2126
+ return { compactionModel: cm };
2127
+ })()),
2128
+ thinking: effectiveThinking(body.reasoningEffort, parsedSettings.settings?.ultracode === true),
2129
+ getApiKeyAndHeaders: keyResolver,
2130
+ ...(() => {
2131
+ const limits = resolveTaskLimits(body.limits, taskLimitCaps, taskTimeoutSec, config.requirePrincipal, body.council === true || body.debate === true || scenarioName === "team");
2132
+ const declarations = [];
2133
+ if (taskLimitCaps.timeoutSec !== undefined)
2134
+ declarations.push({ key: "server.limits.timeoutSecCap", value: String(taskLimitCaps.timeoutSec), reason: "env TASK_TIMEOUT_MAX_SEC (operator ceiling on caller limits.timeoutSec)" });
2135
+ if (taskLimitCaps.maxOutputTokens !== undefined)
2136
+ declarations.push({ key: "server.limits.maxOutputTokensCap", value: String(taskLimitCaps.maxOutputTokens), reason: "env TASK_MAX_OUTPUT_TOKENS_MAX (operator ceiling)" });
2137
+ if (taskLimitCaps.maxTurns !== undefined)
2138
+ declarations.push({ key: "server.limits.maxTurnsCap", value: String(taskLimitCaps.maxTurns), reason: "env TASK_MAX_TURNS_MAX (operator ceiling)" });
2139
+ if (config.maxTaskCostUsd > 0)
2140
+ declarations.push({ key: "server.budget.maxCostUsdCap", value: String(config.maxTaskCostUsd), reason: "env MAX_TASK_COST_USD (operator ceiling; becomes the effective value when the caller sends none)" });
2141
+ if (config.maxTaskTokens > 0)
2142
+ declarations.push({ key: "server.budget.maxTokensCap", value: String(config.maxTaskTokens), reason: "env MAX_TASK_TOKENS_MAX (operator ceiling; becomes the effective value when the caller sends none)" });
2143
+ if (config.requirePrincipal === true)
2144
+ declarations.push({ key: "server.limits.wallClockBaseSec", value: "2400/3600", reason: "tenancy wall-clock base (multi-tenant; big tasks 3600) — TASK_TIMEOUT_SEC raises, never shrinks" });
2145
+ return { ...(limits !== undefined ? { limits } : {}), ...(declarations.length > 0 ? { configOverrides: declarations } : {}) };
2146
+ })(),
2147
+ ...(config.requirePrincipal !== true ? { backgroundScope: "session" } : {}),
2148
+ maxCostUsd: cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd),
2149
+ maxTokens: cappedCeiling(body.maxTokens, config.maxTaskTokens),
2150
+ degrade: (() => {
2151
+ if (!config.degrade || cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd) === undefined)
1994
2152
  return undefined;
1995
- if (config.requirePrincipal === true) {
1996
- logger.warn("task_hooks_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
2153
+ if (Array.isArray(body.images) && body.images.length > 0 && !config.degrade.toSupportsImages) {
2154
+ metrics.inc("degrade_dropped_total", { reason: "vision_target" });
2155
+ logger.warn("degrade_dropped", { reason: "vision_target", model: picked.model, images: body.images.length });
1997
2156
  return undefined;
1998
2157
  }
1999
- const effMode = coercePermissionMode(body.permissionMode) ?? parsedSettings.settings?.permissions?.defaultMode;
2000
- return buildTaskHooks(hc, {
2001
- logger,
2002
- sessionId: auth?.sessionId ?? "",
2003
- cwd: (auth?.sessionId ? perSessionCwd.get(auth.sessionId) : undefined) ?? process.cwd(),
2004
- ...(effMode ? { permissionMode: effMode } : {}),
2005
- ...(parsedSettings.settings?.shellEnv ? { shellEnv: parsedSettings.settings.shellEnv } : {}),
2006
- hookLlm,
2007
- hookAgent,
2008
- onHookNotice: (n) => fleetBus.publishHookNotice({
2009
- ...n,
2010
- ...(auth?.principal ? { ownerScope: auth.principal } : {}),
2011
- ...(auth?.sessionId ? { ownerSessionId: auth.sessionId } : {}),
2012
- }),
2013
- ...(auth?.sessionId
2158
+ return { to: config.degrade.to, atCostFraction: config.degrade.atCostFraction };
2159
+ })(),
2160
+ memory: memoryEngine ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes) : undefined,
2161
+ tools: ((base) => {
2162
+ const extra = [...(selectEnvTool ? [selectEnvTool] : []), ...(sendUserFileToolSpec ? [sendUserFileToolSpec] : [])];
2163
+ const merged = extra.length > 0 ? [...(base ?? []), ...extra] : base;
2164
+ return merged && config.toolDeferLongtail ? applyLongtailDefer(merged, true) : merged;
2165
+ })(cap.tools),
2166
+ skills: mergeUserSkills(cap.skills, body.skills, logger),
2167
+ mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger),
2168
+ promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
2169
+ toolPolicy: durableEnabled
2170
+ ? combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({
2171
+ requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
2172
+ ...(approvalExemptionStore && auth?.sessionId
2014
2173
  ? {
2015
- wake: async (text) => hookWakeBus.deliver
2016
- ?
2017
- hookWakeBus.deliver(auth.sessionId, `[hook asyncRewake] ${redactSecrets(text)}`)
2018
- : false,
2174
+ exempt: (toolName) => approvalExemptionStore.has(auth.sessionId, toolName),
2175
+ onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
2019
2176
  }
2020
2177
  : {}),
2021
- });
2022
- })();
2023
- const rawAddDirs = body.additionalDirectories;
2024
- const additionalDirectories = cwdHonored(config) ? parseAdditionalDirectories(rawAddDirs) : undefined;
2025
- if (rawAddDirs !== undefined && !cwdHonored(config)) {
2026
- logger.warn("task_additional_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
2027
- }
2028
- const wireCatalog = expandTiers(config.models, config.tiers) ?? config.models;
2029
- const picked = resolveTaskModel(body.model ?? parsedSettings.settings?.model, objective, wireCatalog);
2030
- if (picked.unknownExplicit !== undefined) {
2031
- metrics.inc("task_model_unknown_fallback_total");
2032
- logger.warn("task_model_unknown_fallback", { requested: picked.unknownExplicit.slice(0, 120), fallback: picked.model, sessionId: auth?.sessionId ?? null });
2033
- }
2034
- if (Array.isArray(body.images) && body.images.length > 0 && !modelSupportsImages(wireCatalog[picked.model])) {
2035
- metrics.inc("images_omitted_total", { model: picked.model });
2036
- logger.warn("images_omitted_no_vision_model", { model: picked.model, count: body.images.length });
2037
- }
2038
- let resumeAtEntryId;
2039
- if (typeof body.resumeAt === "string" && body.resumeAt.length > 0) {
2040
- if (!auth?.sessionId)
2041
- throw new HttpError(422, "resumeAt requires a session to branch (resume_at.no_session)");
2042
- if (!resumeAnchorStore || !ownerAware.getLeafId)
2043
- throw new HttpError(501, "resume-at is not available on this worker (no session-store backend for the anchor map)");
2044
- resumeAtEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.resumeAt, auth.principal ?? null);
2045
- if (resumeAtEntryId === undefined)
2046
- throw new HttpError(404, "resumeAt: no such message in this session (resume_at.unknown_event)");
2047
- }
2048
- let rewindFilesToEntryId;
2049
- if (resumeAtEntryId === undefined && typeof body.rewindFilesTo === "string" && body.rewindFilesTo.length > 0) {
2050
- if (!auth?.sessionId)
2051
- throw new HttpError(422, "rewindFilesTo requires a session (rewind_files_to.no_session)");
2052
- if (!resumeAnchorStore || !ownerAware.getLeafId)
2053
- throw new HttpError(501, "rewind-files-to is not available on this worker (no session-store backend for the anchor map)");
2054
- rewindFilesToEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.rewindFilesTo, auth.principal ?? null);
2055
- if (rewindFilesToEntryId === undefined)
2056
- throw new HttpError(404, "rewindFilesTo: no such message in this session (rewind_files_to.unknown_event)");
2057
- }
2058
- const s4ProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId : undefined);
2059
- const s4DefaultScopes = s4ProjectId ? config.projects[s4ProjectId]?.defaultScopes : undefined;
2060
- const spec = {
2061
- objective: attachmentNotice ? `${picked.cleanedObjective}\n\n${attachmentNotice}` : picked.cleanedObjective,
2062
- systemPrompt: typeof body.systemPrompt === "string" ? body.systemPrompt : undefined,
2063
- appendSystemPrompt: acceptedAppend,
2064
- sessionId: auth?.sessionId,
2065
- principal: auth?.principal,
2066
- images: body.images,
2067
- ...(body.clientContext ? { clientContext: { timeZone: body.clientContext.timeZone, userEmail: body.clientContext.userEmail } } : {}),
2068
- ...(() => {
2069
- const ct = body.compaction?.clampTolerance;
2070
- return typeof ct === "number" && Number.isFinite(ct) && ct >= 0 && ct <= 1 ? { compaction: { clampTolerance: ct } } : {};
2071
- })(),
2072
- outputSchema: body.outputSchema,
2073
- ...(typeof body.outputRetries === "number" && Number.isFinite(body.outputRetries) && body.outputRetries >= 1
2074
- ? { outputRetries: Math.min(10, Math.floor(body.outputRetries)) }
2075
- : {}),
2076
- resumeAt: resumeAtEntryId,
2077
- resumeAtMode: normalizeResumeAtMode(body.resumeAtMode, resumeAtEntryId !== undefined),
2078
- requireExistingSession: body.requireExistingSession === true ? true : undefined,
2079
- enableFork: enableForkFromBody(body, config, Boolean(centerRuntimeCapsResolver)),
2080
- suggestNextPrompts: normalizeSuggestNextPrompts(body.suggestNextPrompts),
2081
- rewindFiles: body.rewindFiles === true ? true : undefined,
2082
- rewindFilesTo: rewindFilesToEntryId,
2083
- additionalDirectories,
2084
- enablePlanMode: config.planModeEnabled ? true : undefined,
2085
- selfOrchestration: selfOrchestrationFromBody({ selfOrchestration: body.selfOrchestration === true || parsedSettings.settings?.ultracode === true }, config, Boolean(centerRuntimeCapsResolver)),
2086
- forwardSubagentEvents: body.forwardSubagentEvents === true ? true : undefined,
2087
- retainSubagentSessions: normalizeRetainSubagentSessions(body.retainSubagentSessions),
2088
- ...taskAgentsSpecFragment(body.agents, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })),
2089
- ...(retainBackgroundProcessesFromBody(body.retainBackgroundProcesses, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })) === true
2090
- ? { retainBackgroundProcesses: true }
2091
- : {}),
2092
- ...(() => { const v = toolNameListFromBody(body.excludeTools); return v ? { excludeTools: v } : {}; })(),
2093
- ...(() => { const v = toolNameListFromBody(body.deferTools); return v ? { deferTools: v } : {}; })(),
2094
- ...(() => { const v = promptProfileFromBody(body.promptProfile); return v ? { promptProfile: v } : {}; })(),
2095
- ...(typeof body.interactiveTools === "boolean"
2096
- ? { interactiveTools: body.interactiveTools }
2097
- : {}),
2098
- resilience: normalizeResilience(body.resilience, explicitOperator(auth?.principal, config.operatorPrincipals)),
2099
- finalVerification: cap.finalVerification === true || body.finalVerification === true ? true : undefined,
2100
- attachments: normalizeAttachments(body.attachments),
2101
- ...(taskHooks ? { hooks: composeHooks(deploymentHooks, taskHooks) } : {}),
2102
- model: picked.model,
2103
- ...((() => {
2104
- if (typeof body.compactionModel !== "string" || body.compactionModel.length === 0)
2105
- return {};
2106
- const cm = matchCatalogModel(body.compactionModel, wireCatalog);
2107
- if (cm === undefined) {
2108
- if (opts?.leg === "fresh") {
2109
- throw new HttpError(400, `unknown compactionModel "${body.compactionModel.slice(0, 120)}" — not in the configured catalog (name, tier word, or id; a catalog refresh may have removed it mid-request)`);
2110
- }
2111
- metrics.inc("task_model_unknown_fallback_total");
2112
- logger.warn("compaction_model_unknown_fallback", { requested: body.compactionModel.slice(0, 120), sessionId: auth?.sessionId ?? null });
2113
- return {};
2114
- }
2115
- return { compactionModel: cm };
2116
- })()),
2117
- thinking: effectiveThinking(body.reasoningEffort, parsedSettings.settings?.ultracode === true),
2118
- getApiKeyAndHeaders: keyResolver,
2119
- ...(() => {
2120
- const limits = resolveTaskLimits(body.limits, taskLimitCaps, taskTimeoutSec, config.requirePrincipal, body.council === true || body.debate === true || scenarioName === "team");
2121
- const declarations = [];
2122
- if (taskLimitCaps.timeoutSec !== undefined)
2123
- declarations.push({ key: "server.limits.timeoutSecCap", value: String(taskLimitCaps.timeoutSec), reason: "env TASK_TIMEOUT_MAX_SEC (operator ceiling on caller limits.timeoutSec)" });
2124
- if (taskLimitCaps.maxOutputTokens !== undefined)
2125
- declarations.push({ key: "server.limits.maxOutputTokensCap", value: String(taskLimitCaps.maxOutputTokens), reason: "env TASK_MAX_OUTPUT_TOKENS_MAX (operator ceiling)" });
2126
- if (taskLimitCaps.maxTurns !== undefined)
2127
- declarations.push({ key: "server.limits.maxTurnsCap", value: String(taskLimitCaps.maxTurns), reason: "env TASK_MAX_TURNS_MAX (operator ceiling)" });
2128
- if (config.maxTaskCostUsd > 0)
2129
- declarations.push({ key: "server.budget.maxCostUsdCap", value: String(config.maxTaskCostUsd), reason: "env MAX_TASK_COST_USD (operator ceiling; becomes the effective value when the caller sends none)" });
2130
- if (config.maxTaskTokens > 0)
2131
- declarations.push({ key: "server.budget.maxTokensCap", value: String(config.maxTaskTokens), reason: "env MAX_TASK_TOKENS_MAX (operator ceiling; becomes the effective value when the caller sends none)" });
2132
- if (config.requirePrincipal === true)
2133
- declarations.push({ key: "server.limits.wallClockBaseSec", value: "2400/3600", reason: "tenancy wall-clock base (multi-tenant; big tasks 3600) — TASK_TIMEOUT_SEC raises, never shrinks" });
2134
- return { ...(limits !== undefined ? { limits } : {}), ...(declarations.length > 0 ? { configOverrides: declarations } : {}) };
2135
- })(),
2136
- ...(config.requirePrincipal !== true ? { backgroundScope: "session" } : {}),
2137
- maxCostUsd: cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd),
2138
- maxTokens: cappedCeiling(body.maxTokens, config.maxTaskTokens),
2139
- degrade: (() => {
2140
- if (!config.degrade || cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd) === undefined)
2141
- return undefined;
2142
- if (Array.isArray(body.images) && body.images.length > 0 && !config.degrade.toSupportsImages) {
2143
- metrics.inc("degrade_dropped_total", { reason: "vision_target" });
2144
- logger.warn("degrade_dropped", { reason: "vision_target", model: picked.model, images: body.images.length });
2145
- return undefined;
2146
- }
2147
- return { to: config.degrade.to, atCostFraction: config.degrade.atCostFraction };
2148
- })(),
2149
- memory: memoryEngine ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes) : undefined,
2150
- tools: ((base) => {
2151
- const extra = [...(selectEnvTool ? [selectEnvTool] : []), ...(sendUserFileToolSpec ? [sendUserFileToolSpec] : [])];
2152
- const merged = extra.length > 0 ? [...(base ?? []), ...extra] : base;
2153
- return merged && config.toolDeferLongtail ? applyLongtailDefer(merged, true) : merged;
2154
- })(cap.tools),
2155
- skills: mergeUserSkills(cap.skills, body.skills, logger),
2156
- mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger),
2157
- promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
2158
- toolPolicy: durableEnabled
2159
- ? combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({
2160
- requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
2161
- ...(approvalExemptionStore && auth?.sessionId
2178
+ }))
2179
+ : approvalEnabled && approvalStore
2180
+ ? createOaApprovalPolicy({
2181
+ store: approvalStore,
2182
+ requireApproval: config.approvalRequire,
2183
+ deny: config.approvalDeny,
2184
+ pollMs: config.approvalPollMs,
2185
+ context: () => ({ sessionId: auth?.sessionId ?? null, owner: auth?.principal ?? null }),
2186
+ neverAuto: config.approvalNeverAuto,
2187
+ ...(approvalExemptionStore
2162
2188
  ? {
2163
- exempt: (toolName) => approvalExemptionStore.has(auth.sessionId, toolName),
2164
- onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
2189
+ exempt: (sessionId, toolName) => approvalExemptionStore.has(sessionId, toolName),
2190
+ onExempted: (sessionId, toolName) => logger.info("approval_exempted", { sessionId, toolName }),
2165
2191
  }
2166
2192
  : {}),
2167
- }))
2168
- : approvalEnabled && approvalStore
2169
- ? createOaApprovalPolicy({
2170
- store: approvalStore,
2171
- requireApproval: config.approvalRequire,
2172
- deny: config.approvalDeny,
2173
- pollMs: config.approvalPollMs,
2174
- context: () => ({ sessionId: auth?.sessionId ?? null, owner: auth?.principal ?? null }),
2175
- neverAuto: config.approvalNeverAuto,
2176
- ...(approvalExemptionStore
2177
- ? {
2178
- exempt: (sessionId, toolName) => approvalExemptionStore.has(sessionId, toolName),
2179
- onExempted: (sessionId, toolName) => logger.info("approval_exempted", { sessionId, toolName }),
2180
- }
2181
- : {}),
2182
- })
2183
- : singleUserAutoAcceptBaseline
2184
- ? createAllowDenyPolicy({})
2185
- : undefined,
2186
- ...(durableEnabled
2187
- ? {
2188
- checkpointStore,
2189
- durableApproval: {
2190
- scope: auth?.principal ?? "_",
2191
- ...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
2192
- },
2193
- ...((rs) => (rs ? { resourceSuspend: rs } : {}))(resourceSuspendOptIn({
2194
- enabled: config.resourceSuspend,
2195
- ttlSec: config.resourceSuspendTtlSec,
2196
- isVerify: body.verify === true,
2197
- isCascade: body.cascade === true,
2198
- scope: auth?.principal ?? "_",
2199
- })),
2200
- onQuestion: QUESTION_AWAITS_RESUME,
2201
- }
2202
- : {}),
2203
- };
2204
- const governedBase = applyRuntimeGovernance(spec, { autonomy: config.autonomy, commandPolicy: config.commandPolicy });
2205
- let governed = governedBase;
2206
- const bodyMode = coercePermissionMode(body.permissionMode);
2207
- let effectiveSettings = bodyMode ? withPermissionMode(parsedSettings.settings, bodyMode) : parsedSettings.settings;
2208
- if (appendLessPack && effectiveSettings?.outputStyle) {
2209
- logger.warn("output_style_dropped", { detail: "the effective prompt pack cannot mount the append slot (already-assembled systemPrompt or center assembled-identity declaration)", sessionId: auth?.sessionId ?? null });
2210
- const { outputStyle: _dropped, ...rest } = effectiveSettings;
2211
- effectiveSettings = Object.keys(rest).length > 0 ? rest : undefined;
2212
- }
2213
- if (effectiveSettings?.outputStyle && acceptedAppend && acceptedAppend.length + 2 + effectiveSettings.outputStyle.length > MAX_SETTINGS_OUTPUT_STYLE_CHARS) {
2214
- logger.warn("output_style_dropped", { detail: `combined append carriers exceed the ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} cap (rider ${acceptedAppend.length} + style ${effectiveSettings.outputStyle.length}) — style dropped (pre-cap stored body on a resume leg?)`, sessionId: auth?.sessionId ?? null });
2215
- const { outputStyle: _dropped2, ...rest2 } = effectiveSettings;
2216
- effectiveSettings = Object.keys(rest2).length > 0 ? rest2 : undefined;
2217
- }
2218
- const hostSemanticsLane = config.remoteExec === undefined || config.remoteExec.provider === "host";
2219
- const shellScratchpad = await acceptShellScratchpadDir(body.scratchpadDir, {
2220
- requirePrincipal: config.requirePrincipal,
2221
- hostSemanticsLane,
2222
- warn: (msg, meta) => logger.warn(msg, { ...(meta ?? {}), sessionId: auth?.sessionId ?? null }),
2223
- });
2224
- const scratchpadDir = shellScratchpad ??
2225
- (hostSemanticsLane && auth?.sessionId ? await ensureScratchpadDir(config.localDataRoot ?? localRoot, auth.sessionId) : undefined);
2226
- if (effectiveSettings) {
2227
- const fsWriteGate = hostSemanticsLane
2228
- ? (() => {
2229
- const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
2230
- const gateCwd = sessionCwd ??
2231
- (config.remoteExec === undefined
2232
- ? process.cwd()
2233
- : join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted"));
2234
- return {
2235
- env: new NodeExecutionEnv({ cwd: gateCwd }),
2236
- cwd: gateCwd,
2237
- ...(scratchpadDir ? { scratchpadDir } : {}),
2238
- ...(config.sensitiveWritePatterns.length > 0 ? { sensitivePatterns: config.sensitiveWritePatterns } : {}),
2239
- ...(config.manualModeShellGate ? { shellGate: config.manualModeShellGate } : {}),
2240
- ...(approvalExemptionStore && auth?.sessionId
2241
- ? {
2242
- isExempt: async (toolName) => {
2243
- const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
2244
- if (hit)
2245
- logger.info("fs_write_gate_exempted", { toolName, sessionId: auth.sessionId });
2246
- return hit;
2247
- },
2248
- }
2249
- : {}),
2250
- };
2251
- })()
2252
- : undefined;
2253
- const workflowGate = approvalExemptionStore && auth?.sessionId
2254
- ? {
2255
- isExempt: async (toolName) => {
2256
- const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
2257
- if (hit)
2258
- logger.info("workflow_gate_exempted", { toolName, sessionId: auth.sessionId });
2259
- return hit;
2260
- },
2261
- }
2262
- : undefined;
2263
- try {
2264
- governed = applyTaskSettings(governedBase, effectiveSettings, fsWriteGate, workflowGate);
2193
+ })
2194
+ : singleUserAutoAcceptBaseline
2195
+ ? createAllowDenyPolicy({})
2196
+ : undefined,
2197
+ ...(durableEnabled
2198
+ ? {
2199
+ checkpointStore,
2200
+ durableApproval: {
2201
+ scope: auth?.principal ?? "_",
2202
+ ...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
2203
+ },
2204
+ ...((rs) => (rs ? { resourceSuspend: rs } : {}))(resourceSuspendOptIn({
2205
+ enabled: config.resourceSuspend,
2206
+ ttlSec: config.resourceSuspendTtlSec,
2207
+ isVerify: body.verify === true,
2208
+ isCascade: body.cascade === true,
2209
+ scope: auth?.principal ?? "_",
2210
+ })),
2211
+ onQuestion: QUESTION_AWAITS_RESUME,
2265
2212
  }
2266
- catch (e) {
2267
- throw new HttpError(422, `settings are tighten-only and cannot loosen the deployment policy: ${e.message}`);
2213
+ : {}),
2214
+ };
2215
+ const governedBase = applyRuntimeGovernance(spec, { autonomy: config.autonomy, commandPolicy: config.commandPolicy });
2216
+ let governed = governedBase;
2217
+ const bodyMode = coercePermissionMode(body.permissionMode);
2218
+ let effectiveSettings = bodyMode ? withPermissionMode(parsedSettings.settings, bodyMode) : parsedSettings.settings;
2219
+ if (appendLessPack && effectiveSettings?.outputStyle) {
2220
+ logger.warn("output_style_dropped", { detail: "the effective prompt pack cannot mount the append slot (already-assembled systemPrompt or center assembled-identity declaration)", sessionId: auth?.sessionId ?? null });
2221
+ const { outputStyle: _dropped, ...rest } = effectiveSettings;
2222
+ effectiveSettings = Object.keys(rest).length > 0 ? rest : undefined;
2223
+ }
2224
+ if (effectiveSettings?.outputStyle && acceptedAppend && acceptedAppend.length + 2 + effectiveSettings.outputStyle.length > MAX_SETTINGS_OUTPUT_STYLE_CHARS) {
2225
+ logger.warn("output_style_dropped", { detail: `combined append carriers exceed the ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} cap (rider ${acceptedAppend.length} + style ${effectiveSettings.outputStyle.length}) — style dropped (pre-cap stored body on a resume leg?)`, sessionId: auth?.sessionId ?? null });
2226
+ const { outputStyle: _dropped2, ...rest2 } = effectiveSettings;
2227
+ effectiveSettings = Object.keys(rest2).length > 0 ? rest2 : undefined;
2228
+ }
2229
+ const hostSemanticsLane = config.remoteExec === undefined || config.remoteExec.provider === "host";
2230
+ const shellScratchpad = await acceptShellScratchpadDir(body.scratchpadDir, {
2231
+ requirePrincipal: config.requirePrincipal,
2232
+ hostSemanticsLane,
2233
+ warn: (msg, meta) => logger.warn(msg, { ...(meta ?? {}), sessionId: auth?.sessionId ?? null }),
2234
+ });
2235
+ const scratchpadDir = shellScratchpad ??
2236
+ (hostSemanticsLane && auth?.sessionId ? await ensureScratchpadDir(config.localDataRoot ?? localRoot, auth.sessionId) : undefined);
2237
+ if (effectiveSettings) {
2238
+ const fsWriteGate = hostSemanticsLane
2239
+ ? (() => {
2240
+ const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
2241
+ const gateCwd = sessionCwd ??
2242
+ (config.remoteExec === undefined
2243
+ ? process.cwd()
2244
+ : join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted"));
2245
+ return {
2246
+ env: new NodeExecutionEnv({ cwd: gateCwd }),
2247
+ cwd: gateCwd,
2248
+ ...(scratchpadDir ? { scratchpadDir } : {}),
2249
+ ...(config.sensitiveWritePatterns.length > 0 ? { sensitivePatterns: config.sensitiveWritePatterns } : {}),
2250
+ ...(config.manualModeShellGate ? { shellGate: config.manualModeShellGate } : {}),
2251
+ ...(approvalExemptionStore && auth?.sessionId
2252
+ ? {
2253
+ isExempt: async (toolName) => {
2254
+ const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
2255
+ if (hit)
2256
+ logger.info("fs_write_gate_exempted", { toolName, sessionId: auth.sessionId });
2257
+ return hit;
2258
+ },
2259
+ }
2260
+ : {}),
2261
+ };
2262
+ })()
2263
+ : undefined;
2264
+ const workflowGate = approvalExemptionStore && auth?.sessionId
2265
+ ? {
2266
+ isExempt: async (toolName) => {
2267
+ const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
2268
+ if (hit)
2269
+ logger.info("workflow_gate_exempted", { toolName, sessionId: auth.sessionId });
2270
+ return hit;
2271
+ },
2268
2272
  }
2273
+ : undefined;
2274
+ try {
2275
+ governed = applyTaskSettings(governedBase, effectiveSettings, fsWriteGate, workflowGate);
2269
2276
  }
2270
- if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
2271
- governed = { ...governed, tools: stripDelegationTools(governed.tools) };
2277
+ catch (e) {
2278
+ throw new HttpError(422, `settings are tighten-only and cannot loosen the deployment policy: ${e.message}`);
2272
2279
  }
2273
- const bodyProfile = typeof body.sandboxImageProfile === "string" && body.sandboxImageProfile.length > 0 ? body.sandboxImageProfile : undefined;
2274
- const taskImageProfile = bodyProfile ?? (body.cascade === true || body.verify === true ? undefined : sessionEnvSelection.get(auth?.sessionId));
2275
- let resolvedImageCaps;
2276
- if (taskImageProfile !== undefined) {
2277
- if (config.remoteExec?.provider !== "k8s") {
2278
- throw new HttpError(400, "sandboxImageProfile is only supported on the k8s sandbox backend");
2279
- }
2280
- if (!imageIndex) {
2281
- throw new HttpError(400, "sandboxImageProfile requires the image index (sema-registry backend not configured)");
2282
- }
2283
- if (!auth?.sessionId) {
2284
- throw new HttpError(400, "sandboxImageProfile requires a resolved session");
2285
- }
2286
- const capsNeeded = Array.isArray(body.capabilitiesNeeded)
2287
- ? body.capabilitiesNeeded.filter((c) => typeof c === "string")
2288
- : undefined;
2289
- const resolved = await resolveSandboxImageRef({
2290
- profile: taskImageProfile,
2291
- ...(capsNeeded && capsNeeded.length > 0 ? { capabilitiesNeeded: capsNeeded } : {}),
2292
- viewer: { operator: explicitOperator(auth.principal, config.operatorPrincipals), tenantId: auth.principal ?? null },
2293
- index: imageIndex,
2294
- });
2295
- if (!resolved.ok)
2296
- throw new HttpError(resolved.status, resolved.message);
2297
- perTaskImage.set(auth.sessionId, resolved.ref);
2298
- resolvedImageCaps = resolved.capabilities;
2280
+ }
2281
+ if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
2282
+ governed = { ...governed, tools: stripDelegationTools(governed.tools) };
2283
+ }
2284
+ const bodyProfile = typeof body.sandboxImageProfile === "string" && body.sandboxImageProfile.length > 0 ? body.sandboxImageProfile : undefined;
2285
+ const taskImageProfile = bodyProfile ?? (body.cascade === true || body.verify === true ? undefined : sessionEnvSelection.get(auth?.sessionId));
2286
+ let resolvedImageCaps;
2287
+ if (taskImageProfile !== undefined) {
2288
+ if (config.remoteExec?.provider !== "k8s") {
2289
+ throw new HttpError(400, "sandboxImageProfile is only supported on the k8s sandbox backend");
2299
2290
  }
2300
- if (config.envFactsEnabled) {
2301
- const pkgSourceLane = config.remoteExec?.provider === "e2b" || config.remoteExec?.provider === "k8s";
2302
- const remoteScratchpadDir = !hostSemanticsLane && isRemoteScratchpadLane(config.remoteExec?.provider)
2303
- ? remoteScratchpadDirFor(auth?.sessionId)
2304
- : undefined;
2305
- const factScratchpadDir = scratchpadDir ?? remoteScratchpadDir;
2306
- const facts = buildEnvFacts({
2307
- profile: taskImageProfile,
2308
- capabilities: resolvedImageCaps,
2309
- ...(pkgSourceLane ? { pkgSource: config.sandboxPkgSource } : {}),
2310
- egress: egressForRemoteExec(config.remoteExec),
2311
- ...(factScratchpadDir ? { scratchpadDir: factScratchpadDir } : {}),
2312
- resumeFacts: resumeFactsForLane(config.remoteExec?.provider, config.remoteExec?.provider === "k8s" ? { k8sSnapshot: Boolean(config.remoteExec.s3Snapshot) } : undefined),
2313
- });
2314
- if (facts)
2315
- governed = { ...governed, envFacts: facts };
2291
+ if (!imageIndex) {
2292
+ throw new HttpError(400, "sandboxImageProfile requires the image index (sema-registry backend not configured)");
2316
2293
  }
2317
- if (scratchpadDir !== undefined) {
2318
- const dirs = governed.additionalDirectories ?? [];
2319
- if (!dirs.includes(scratchpadDir))
2320
- governed = { ...governed, additionalDirectories: [...dirs, scratchpadDir] };
2294
+ if (!auth?.sessionId) {
2295
+ throw new HttpError(400, "sandboxImageProfile requires a resolved session");
2321
2296
  }
2322
- if (config.routerEnabled) {
2323
- const isolatedExecEnv = isIsolatedExecEnv(config.remoteExec?.provider, config.remoteExec?.provider === "k8s" ? config.remoteExec.runtimeClass : undefined);
2324
- const decision = routeServiceTask({
2325
- isolatedExecEnv,
2326
- hasExecutionEnv: config.remoteExec != null,
2327
- ...(config.autonomy ? { autonomy: config.autonomy } : {}),
2328
- explicitTeam: body.council === true || body.debate === true || scenarioName === "team",
2329
- });
2330
- logger.info("orchestration_routed", { mode: decision.mode, reason: decision.reason, sessionId: auth?.sessionId ?? null });
2331
- if (decision.mode === "supervisor") {
2332
- return tightenTaskSpec(governed, supPostureOverrides());
2333
- }
2297
+ const capsNeeded = Array.isArray(body.capabilitiesNeeded)
2298
+ ? body.capabilitiesNeeded.filter((c) => typeof c === "string")
2299
+ : undefined;
2300
+ const resolved = await resolveSandboxImageRef({
2301
+ profile: taskImageProfile,
2302
+ ...(capsNeeded && capsNeeded.length > 0 ? { capabilitiesNeeded: capsNeeded } : {}),
2303
+ viewer: { operator: explicitOperator(auth.principal, config.operatorPrincipals), tenantId: auth.principal ?? null },
2304
+ index: imageIndex,
2305
+ });
2306
+ if (!resolved.ok)
2307
+ throw new HttpError(resolved.status, resolved.message);
2308
+ perTaskImage.set(auth.sessionId, resolved.ref);
2309
+ resolvedImageCaps = resolved.capabilities;
2310
+ }
2311
+ if (config.envFactsEnabled) {
2312
+ const pkgSourceLane = config.remoteExec?.provider === "e2b" || config.remoteExec?.provider === "k8s";
2313
+ const remoteScratchpadDir = !hostSemanticsLane && isRemoteScratchpadLane(config.remoteExec?.provider)
2314
+ ? remoteScratchpadDirFor(auth?.sessionId)
2315
+ : undefined;
2316
+ const factScratchpadDir = scratchpadDir ?? remoteScratchpadDir;
2317
+ const facts = buildEnvFacts({
2318
+ profile: taskImageProfile,
2319
+ capabilities: resolvedImageCaps,
2320
+ ...(pkgSourceLane ? { pkgSource: config.sandboxPkgSource } : {}),
2321
+ egress: egressForRemoteExec(config.remoteExec),
2322
+ ...(factScratchpadDir ? { scratchpadDir: factScratchpadDir } : {}),
2323
+ resumeFacts: resumeFactsForLane(config.remoteExec?.provider, config.remoteExec?.provider === "k8s" ? { k8sSnapshot: Boolean(config.remoteExec.s3Snapshot) } : undefined),
2324
+ });
2325
+ if (facts)
2326
+ governed = { ...governed, envFacts: facts };
2327
+ }
2328
+ if (scratchpadDir !== undefined) {
2329
+ const dirs = governed.additionalDirectories ?? [];
2330
+ if (!dirs.includes(scratchpadDir))
2331
+ governed = { ...governed, additionalDirectories: [...dirs, scratchpadDir] };
2332
+ }
2333
+ if (config.routerEnabled) {
2334
+ const isolatedExecEnv = isIsolatedExecEnv(config.remoteExec?.provider, config.remoteExec?.provider === "k8s" ? config.remoteExec.runtimeClass : undefined);
2335
+ const decision = routeServiceTask({
2336
+ isolatedExecEnv,
2337
+ hasExecutionEnv: config.remoteExec != null,
2338
+ ...(config.autonomy ? { autonomy: config.autonomy } : {}),
2339
+ explicitTeam: body.council === true || body.debate === true || scenarioName === "team",
2340
+ });
2341
+ logger.info("orchestration_routed", { mode: decision.mode, reason: decision.reason, sessionId: auth?.sessionId ?? null });
2342
+ if (decision.mode === "supervisor") {
2343
+ return tightenTaskSpec(governed, supPostureOverrides());
2334
2344
  }
2335
- return governed;
2336
- },
2337
- });
2345
+ }
2346
+ return governed;
2347
+ };
2348
+ const server = createHttpServer({ runner, config, resolveSpec, stores, coordinators, seams, observability, governance, deployment, knobs });
2338
2349
  runDenySweep = server.denyExpiredApprovals;
2339
2350
  const bindHost = resolveBindHost(config);
2340
2351
  await new Promise((resolve) => (bindHost ? server.listen(config.port, bindHost, resolve) : server.listen(config.port, resolve)));