@caupulican/pi-adaptative 0.80.97 → 0.80.99

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 (82) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/core/agent-session.d.ts +46 -5
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +385 -17
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/autonomy/envelope-enforcement.d.ts +17 -0
  7. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -0
  8. package/dist/core/autonomy/envelope-enforcement.js +80 -0
  9. package/dist/core/autonomy/envelope-enforcement.js.map +1 -0
  10. package/dist/core/autonomy/foreground-envelope.d.ts +22 -0
  11. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -0
  12. package/dist/core/autonomy/foreground-envelope.js +65 -0
  13. package/dist/core/autonomy/foreground-envelope.js.map +1 -0
  14. package/dist/core/autonomy/status.d.ts +11 -0
  15. package/dist/core/autonomy/status.d.ts.map +1 -1
  16. package/dist/core/autonomy/status.js.map +1 -1
  17. package/dist/core/context/brain-curator.d.ts +7 -0
  18. package/dist/core/context/brain-curator.d.ts.map +1 -1
  19. package/dist/core/context/brain-curator.js +6 -0
  20. package/dist/core/context/brain-curator.js.map +1 -1
  21. package/dist/core/context/context-composition.d.ts.map +1 -1
  22. package/dist/core/context/context-composition.js +1 -1
  23. package/dist/core/context/context-composition.js.map +1 -1
  24. package/dist/core/delegation/session-worker-result.d.ts +8 -2
  25. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  26. package/dist/core/delegation/session-worker-result.js +18 -1
  27. package/dist/core/delegation/session-worker-result.js.map +1 -1
  28. package/dist/core/delegation/worker-actions.d.ts +50 -0
  29. package/dist/core/delegation/worker-actions.d.ts.map +1 -0
  30. package/dist/core/delegation/worker-actions.js +70 -0
  31. package/dist/core/delegation/worker-actions.js.map +1 -0
  32. package/dist/core/delegation/worker-runner.d.ts +9 -0
  33. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  34. package/dist/core/delegation/worker-runner.js +38 -4
  35. package/dist/core/delegation/worker-runner.js.map +1 -1
  36. package/dist/core/learning/observation-store.d.ts +20 -0
  37. package/dist/core/learning/observation-store.d.ts.map +1 -0
  38. package/dist/core/learning/observation-store.js +101 -0
  39. package/dist/core/learning/observation-store.js.map +1 -0
  40. package/dist/core/model-capability.d.ts +19 -0
  41. package/dist/core/model-capability.d.ts.map +1 -1
  42. package/dist/core/model-capability.js +19 -0
  43. package/dist/core/model-capability.js.map +1 -1
  44. package/dist/core/model-router/executor-route.d.ts +8 -0
  45. package/dist/core/model-router/executor-route.d.ts.map +1 -0
  46. package/dist/core/model-router/executor-route.js +33 -0
  47. package/dist/core/model-router/executor-route.js.map +1 -0
  48. package/dist/core/model-router/tool-escalation.d.ts +2 -0
  49. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  50. package/dist/core/model-router/tool-escalation.js +6 -0
  51. package/dist/core/model-router/tool-escalation.js.map +1 -1
  52. package/dist/core/research/research-runner.d.ts +8 -1
  53. package/dist/core/research/research-runner.d.ts.map +1 -1
  54. package/dist/core/research/research-runner.js +13 -1
  55. package/dist/core/research/research-runner.js.map +1 -1
  56. package/dist/core/research/workspace-collector.d.ts +25 -0
  57. package/dist/core/research/workspace-collector.d.ts.map +1 -0
  58. package/dist/core/research/workspace-collector.js +286 -0
  59. package/dist/core/research/workspace-collector.js.map +1 -0
  60. package/dist/core/settings-manager.d.ts +5 -0
  61. package/dist/core/settings-manager.d.ts.map +1 -1
  62. package/dist/core/settings-manager.js +8 -0
  63. package/dist/core/settings-manager.js.map +1 -1
  64. package/dist/modes/interactive/components/fitness-role-selector.d.ts +1 -1
  65. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/fitness-role-selector.js +5 -0
  67. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
  68. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/settings-selector.js +20 -0
  70. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  71. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-mode.js +9 -0
  73. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  74. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  75. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  76. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  77. package/examples/extensions/sandbox/package-lock.json +2 -2
  78. package/examples/extensions/sandbox/package.json +1 -1
  79. package/examples/extensions/with-deps/package-lock.json +2 -2
  80. package/examples/extensions/with-deps/package.json +1 -1
  81. package/npm-shrinkwrap.json +12 -12
  82. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"context-composition.d.ts","sourceRoot":"","sources":["../../../src/core/context/context-composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { CurationTelemetrySnapshot } from \"./brain-curator.ts\";\n\n/**\n * Context composition dashboard (user-facing): decomposes EVERYTHING that rides along on every\n * request — system prompt, active tool schemas, extension contributions, injected blocks\n * (memory recall pages, evidence blocks), and the session messages themselves (raw vs. GC-packed\n * vs. policy-stubbed) — so a user integrating their own tools/extensions can see exactly what\n * each addition costs per request and where cleaning is (or is not) working.\n *\n * Honesty contract: everything here is an ESTIMATE (chars/4) EXCEPT `providerReportedTokens`,\n * which is what the provider actually billed. The dashboard always shows both and the delta —\n * the delta is the measure of how much the estimates can be trusted, never hidden.\n *\n * Known exclusions (named, not hidden): extension `context` handlers may rewrite messages at\n * send time in ways this view cannot see. The memory evidence block and enforcement stubbing\n * are ALSO send-time-only, but those are modeled explicitly via `adjustments`.\n */\n\nexport interface ToolCompositionRow {\n\tname: string;\n\t/** Estimated tokens for the tool's name+description+schema as sent to the provider. */\n\tschemaTokens: number;\n\tsource: \"built-in\" | \"extension\";\n}\n\nexport interface ExtensionCompositionRow {\n\tname: string;\n\tpath: string;\n\ttoolCount: number;\n\tcommandCount: number;\n\t/** Estimated schema tokens of this extension's ACTIVE tools (its per-request cost). */\n\tactiveToolSchemaTokens: number;\n}\n\nexport interface MessageClassRow {\n\tlabel: string;\n\tcount: number;\n\ttokens: number;\n}\n\nexport interface ContextCompositionReport {\n\t/** Estimated tokens of the system prompt sent on every request. */\n\tsystemPromptTokens: number;\n\tsystemPromptChars: number;\n\t/** Estimated tokens of ALL active tool schemas sent on every request. */\n\ttoolSchemaTokens: number;\n\ttools: ToolCompositionRow[];\n\textensions: ExtensionCompositionRow[];\n\t/** Session message classes (raw/user/assistant/stubs/recall pages), heaviest first. */\n\tmessageClasses: MessageClassRow[];\n\tmessageTokens: number;\n\tmessageCount: number;\n\t/** Estimated total sent per request: system prompt + tool schemas + messages. */\n\testimatedRequestTokens: number;\n\t/** What the provider actually reported for the current context, when known. */\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc: { packedCount: number; savedTokens: number } | null;\n\tenforcement: { enforcedCount: number; advisoryEvictions: number } | null;\n\tcuration: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string } | null;\n\t/** Background/side-channel spend that does NOT ride in this context but bills the account. */\n\tspawned: { cost: number; reports: number } | null;\n\t/** Send-time-only deltas folded into estimatedRequestTokens: +evidence block, -policy stubs. */\n\tadjustments: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Actionable, bounded observations derived from the numbers above. */\n\tobservations: string[];\n}\n\nexport interface BuildContextCompositionInput {\n\tsystemPrompt: string;\n\ttools: Array<{ name: string; description?: string; parameters?: unknown; source?: \"built-in\" | \"extension\" }>;\n\textensions: Array<{\n\t\tname: string;\n\t\tpath: string;\n\t\ttoolNames: string[];\n\t\tcommandCount: number;\n\t}>;\n\tmessages: AgentMessage[];\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc?: { packedCount: number; savedTokens: number };\n\tenforcement?: { enforcedCount: number; advisoryEvictions: number };\n\tcuration?: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string };\n\tspawned?: { cost: number; reports: number };\n\tadjustments?: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Pre-formed warnings from other subsystems (e.g. profile-withheld context files). */\n\textraObservations?: string[];\n}\n\nfunction estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst content = (message as { content?: unknown }).content;\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => (part as { type?: string }).type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction classifyMessage(message: AgentMessage): string {\n\tconst details = (\n\t\tmessage as { details?: { contextGc?: { packed?: unknown }; promptPolicy?: { enforced?: unknown } } }\n\t).details;\n\tif (details?.contextGc?.packed === true) return \"gc-packed stub\";\n\tif (details?.promptPolicy?.enforced === true) return \"policy stub\";\n\tif (message.role === \"custom\") {\n\t\tconst customType = (message as { customType?: string }).customType ?? \"\";\n\t\tif (customType === \"memory_context\" || messageText(message).includes(\"<memory_context\")) {\n\t\t\treturn \"memory recall page\";\n\t\t}\n\t\treturn `custom (${customType || \"unknown\"})`;\n\t}\n\tif (message.role === \"toolResult\") return `toolResult (${(message as { toolName?: string }).toolName ?? \"?\"})`;\n\treturn message.role;\n}\n\nexport function buildContextCompositionReport(input: BuildContextCompositionInput): ContextCompositionReport {\n\tconst systemPromptTokens = estimateTextTokens(input.systemPrompt);\n\n\tconst tools: ToolCompositionRow[] = input.tools\n\t\t.map((tool) => ({\n\t\t\tname: tool.name,\n\t\t\tschemaTokens: estimateTextTokens(\n\t\t\t\tJSON.stringify({ name: tool.name, description: tool.description ?? \"\", parameters: tool.parameters ?? {} }),\n\t\t\t),\n\t\t\tsource: tool.source ?? (\"built-in\" as const),\n\t\t}))\n\t\t.sort((a, b) => b.schemaTokens - a.schemaTokens);\n\tconst toolSchemaTokens = tools.reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\tconst toolTokensByName = new Map(tools.map((tool) => [tool.name, tool.schemaTokens]));\n\n\tconst extensions: ExtensionCompositionRow[] = input.extensions\n\t\t.map((extension) => ({\n\t\t\tname: extension.name,\n\t\t\tpath: extension.path,\n\t\t\ttoolCount: extension.toolNames.length,\n\t\t\tcommandCount: extension.commandCount,\n\t\t\tactiveToolSchemaTokens: extension.toolNames.reduce(\n\t\t\t\t(sum, toolName) => sum + (toolTokensByName.get(toolName) ?? 0),\n\t\t\t\t0,\n\t\t\t),\n\t\t}))\n\t\t.sort((a, b) => b.activeToolSchemaTokens - a.activeToolSchemaTokens);\n\n\tconst classes = new Map<string, MessageClassRow>();\n\tlet messageTokens = 0;\n\tfor (const message of input.messages) {\n\t\tconst label = classifyMessage(message);\n\t\tconst tokens = estimateTokens(message);\n\t\tmessageTokens += tokens;\n\t\tconst row = classes.get(label) ?? { label, count: 0, tokens: 0 };\n\t\trow.count++;\n\t\trow.tokens += tokens;\n\t\tclasses.set(label, row);\n\t}\n\tconst messageClasses = [...classes.values()].sort((a, b) => b.tokens - a.tokens);\n\n\tconst adjustments = input.adjustments ?? { memoryEvidenceTokens: 0, enforcementSavedTokens: 0 };\n\tconst estimatedRequestTokens = Math.max(\n\t\t0,\n\t\tsystemPromptTokens +\n\t\t\ttoolSchemaTokens +\n\t\t\tmessageTokens +\n\t\t\tadjustments.memoryEvidenceTokens -\n\t\t\tadjustments.enforcementSavedTokens,\n\t);\n\n\tconst observations: string[] = [...(input.extraObservations ?? [])];\n\tconst heaviestTool = tools[0];\n\tif (heaviestTool && toolSchemaTokens > 0 && heaviestTool.schemaTokens > Math.max(500, toolSchemaTokens * 0.3)) {\n\t\tobservations.push(\n\t\t\t`tool \"${heaviestTool.name}\" alone is ~${heaviestTool.schemaTokens} tokens of schema on EVERY request — trim its description/schema if you own it`,\n\t\t);\n\t}\n\tconst recall = messageClasses.find((row) => row.label === \"memory recall page\");\n\tif (recall && recall.tokens > 1500) {\n\t\tobservations.push(\n\t\t\t`${recall.count} memory recall page(s) hold ~${recall.tokens} tokens — verify context GC is packing stale ones (gc packed: ${input.gc?.packedCount ?? 0})`,\n\t\t);\n\t}\n\tif (input.contextWindow && systemPromptTokens + toolSchemaTokens > input.contextWindow * 0.35) {\n\t\tobservations.push(\n\t\t\t`fixed per-request overhead (system+tools) is ~${Math.round(((systemPromptTokens + toolSchemaTokens) / input.contextWindow) * 100)}% of the context window before any conversation`,\n\t\t);\n\t}\n\tif (input.providerReportedTokens !== null) {\n\t\tconst delta = input.providerReportedTokens - estimatedRequestTokens;\n\t\tif (Math.abs(delta) > Math.max(2000, estimatedRequestTokens * 0.25)) {\n\t\t\tobservations.push(\n\t\t\t\t`provider-reported context (${input.providerReportedTokens}) differs from the estimate by ${delta > 0 ? \"+\" : \"\"}${delta} tokens — treat estimates as directional`,\n\t\t\t);\n\t\t}\n\t}\n\tif (input.curation?.enabled && input.curation.lastSkipReason) {\n\t\tobservations.push(`curation is enabled but idle: ${input.curation.lastSkipReason}`);\n\t}\n\n\treturn {\n\t\tsystemPromptTokens,\n\t\tsystemPromptChars: input.systemPrompt.length,\n\t\ttoolSchemaTokens,\n\t\ttools,\n\t\textensions,\n\t\tmessageClasses,\n\t\tmessageTokens,\n\t\tmessageCount: input.messages.length,\n\t\testimatedRequestTokens,\n\t\tproviderReportedTokens: input.providerReportedTokens,\n\t\tcontextWindow: input.contextWindow,\n\t\tgc: input.gc ?? null,\n\t\tenforcement: input.enforcement ?? null,\n\t\tcuration: input.curation ?? null,\n\t\tspawned: input.spawned ?? null,\n\t\tadjustments,\n\t\tobservations,\n\t};\n}\n\n/** Bounded plain-text dashboard (interactive `/context` command and tests). */\nexport function formatContextCompositionDashboard(report: ContextCompositionReport, maxToolRows = 10): string {\n\tconst pct = (tokens: number) =>\n\t\treport.contextWindow ? ` (${((tokens / report.contextWindow) * 100).toFixed(1)}% of window)` : \"\";\n\tconst lines: string[] = [\n\t\t\"Context composition — what rides on EVERY request\",\n\t\t`estimated request total: ~${report.estimatedRequestTokens} tokens${pct(report.estimatedRequestTokens)}${\n\t\t\treport.providerReportedTokens !== null ? ` · provider-reported: ${report.providerReportedTokens}` : \"\"\n\t\t}`,\n\t\t\"\",\n\t\t`system prompt: ~${report.systemPromptTokens} tokens (${report.systemPromptChars} chars)`,\n\t\t`tool schemas: ~${report.toolSchemaTokens} tokens across ${report.tools.length} active tool(s)`,\n\t];\n\tfor (const tool of report.tools.slice(0, maxToolRows)) {\n\t\tlines.push(` - ${tool.name}: ~${tool.schemaTokens} tok [${tool.source}]`);\n\t}\n\tif (report.tools.length > maxToolRows) {\n\t\tconst rest = report.tools.slice(maxToolRows).reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\t\tlines.push(` - (+${report.tools.length - maxToolRows} more: ~${rest} tok)`);\n\t}\n\tif (report.extensions.length > 0) {\n\t\tlines.push(\"\", \"extensions:\");\n\t\tfor (const extension of report.extensions.slice(0, 8)) {\n\t\t\tlines.push(\n\t\t\t\t` - ${extension.name}: ${extension.toolCount} tool(s), ${extension.commandCount} command(s), ~${extension.activeToolSchemaTokens} tok of active schemas`,\n\t\t\t);\n\t\t}\n\t}\n\tlines.push(\"\", `session messages: ${report.messageCount} row(s), ~${report.messageTokens} tokens`);\n\tif (report.adjustments.memoryEvidenceTokens > 0 || report.adjustments.enforcementSavedTokens > 0) {\n\t\tlines.push(\n\t\t\t`send-time adjustments: +${report.adjustments.memoryEvidenceTokens} memory evidence, -${report.adjustments.enforcementSavedTokens} policy stubs (applied when the request is built)`,\n\t\t);\n\t}\n\tfor (const row of report.messageClasses.slice(0, 10)) {\n\t\tlines.push(` - ${row.label}: ${row.count} row(s), ~${row.tokens} tok`);\n\t}\n\tif (report.gc) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`context GC: ${report.gc.packedCount} row(s) packed, ~${report.gc.savedTokens} tokens saved this pass`,\n\t\t);\n\t}\n\tif (report.enforcement) {\n\t\tlines.push(\n\t\t\t`prompt policy: ${report.enforcement.enforcedCount} stub(s) this turn (${report.enforcement.advisoryEvictions} via brain advisory)`,\n\t\t);\n\t}\n\tif (report.curation) {\n\t\tconst t = report.curation.telemetry;\n\t\tlines.push(\n\t\t\t`brain curation: ${report.curation.enabled ? \"enabled\" : \"disabled\"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${\n\t\t\t\treport.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : \"\"\n\t\t\t}`,\n\t\t);\n\t}\n\tif (report.spawned && report.spawned.reports > 0) {\n\t\tlines.push(\n\t\t\t`spawned/background spend (NOT in this context): ${report.spawned.reports} report(s), $${report.spawned.cost.toFixed(4)}`,\n\t\t);\n\t}\n\tif (report.observations.length > 0) {\n\t\tlines.push(\"\", \"observations:\");\n\t\tfor (const observation of report.observations.slice(0, 5)) {\n\t\t\tlines.push(` ! ${observation}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"context-composition.d.ts","sourceRoot":"","sources":["../../../src/core/context/context-composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { CurationTelemetrySnapshot } from \"./brain-curator.ts\";\n\n/**\n * Context composition dashboard (user-facing): decomposes EVERYTHING that rides along on every\n * request — system prompt, active tool schemas, extension contributions, injected blocks\n * (memory recall pages, evidence blocks), and the session messages themselves (raw vs. GC-packed\n * vs. policy-stubbed) — so a user integrating their own tools/extensions can see exactly what\n * each addition costs per request and where cleaning is (or is not) working.\n *\n * Honesty contract: everything here is an ESTIMATE (chars/4) EXCEPT `providerReportedTokens`,\n * which is what the provider actually billed. The dashboard always shows both and the delta —\n * the delta is the measure of how much the estimates can be trusted, never hidden.\n *\n * Known exclusions (named, not hidden): extension `context` handlers may rewrite messages at\n * send time in ways this view cannot see. The memory evidence block and enforcement stubbing\n * are ALSO send-time-only, but those are modeled explicitly via `adjustments`.\n */\n\nexport interface ToolCompositionRow {\n\tname: string;\n\t/** Estimated tokens for the tool's name+description+schema as sent to the provider. */\n\tschemaTokens: number;\n\tsource: \"built-in\" | \"extension\";\n}\n\nexport interface ExtensionCompositionRow {\n\tname: string;\n\tpath: string;\n\ttoolCount: number;\n\tcommandCount: number;\n\t/** Estimated schema tokens of this extension's ACTIVE tools (its per-request cost). */\n\tactiveToolSchemaTokens: number;\n}\n\nexport interface MessageClassRow {\n\tlabel: string;\n\tcount: number;\n\ttokens: number;\n}\n\nexport interface ContextCompositionReport {\n\t/** Estimated tokens of the system prompt sent on every request. */\n\tsystemPromptTokens: number;\n\tsystemPromptChars: number;\n\t/** Estimated tokens of ALL active tool schemas sent on every request. */\n\ttoolSchemaTokens: number;\n\ttools: ToolCompositionRow[];\n\textensions: ExtensionCompositionRow[];\n\t/** Session message classes (raw/user/assistant/stubs/recall pages), heaviest first. */\n\tmessageClasses: MessageClassRow[];\n\tmessageTokens: number;\n\tmessageCount: number;\n\t/** Estimated total sent per request: system prompt + tool schemas + messages. */\n\testimatedRequestTokens: number;\n\t/** What the provider actually reported for the current context, when known. */\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc: { packedCount: number; savedTokens: number } | null;\n\tenforcement: { enforcedCount: number; advisoryEvictions: number } | null;\n\tcuration: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string } | null;\n\t/** Background/side-channel spend that does NOT ride in this context but bills the account. */\n\tspawned: { cost: number; reports: number } | null;\n\t/** Send-time-only deltas folded into estimatedRequestTokens: +evidence block, -policy stubs. */\n\tadjustments: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Actionable, bounded observations derived from the numbers above. */\n\tobservations: string[];\n}\n\nexport interface BuildContextCompositionInput {\n\tsystemPrompt: string;\n\ttools: Array<{ name: string; description?: string; parameters?: unknown; source?: \"built-in\" | \"extension\" }>;\n\textensions: Array<{\n\t\tname: string;\n\t\tpath: string;\n\t\ttoolNames: string[];\n\t\tcommandCount: number;\n\t}>;\n\tmessages: AgentMessage[];\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc?: { packedCount: number; savedTokens: number };\n\tenforcement?: { enforcedCount: number; advisoryEvictions: number };\n\tcuration?: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string };\n\tspawned?: { cost: number; reports: number };\n\tadjustments?: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Pre-formed warnings from other subsystems (e.g. profile-withheld context files). */\n\textraObservations?: string[];\n}\n\nfunction estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst content = (message as { content?: unknown }).content;\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => (part as { type?: string }).type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction classifyMessage(message: AgentMessage): string {\n\tconst details = (\n\t\tmessage as { details?: { contextGc?: { packed?: unknown }; promptPolicy?: { enforced?: unknown } } }\n\t).details;\n\tif (details?.contextGc?.packed === true) return \"gc-packed stub\";\n\tif (details?.promptPolicy?.enforced === true) return \"policy stub\";\n\tif (message.role === \"custom\") {\n\t\tconst customType = (message as { customType?: string }).customType ?? \"\";\n\t\tif (customType === \"memory_context\" || messageText(message).includes(\"<memory_context\")) {\n\t\t\treturn \"memory recall page\";\n\t\t}\n\t\treturn `custom (${customType || \"unknown\"})`;\n\t}\n\tif (message.role === \"toolResult\") return `toolResult (${(message as { toolName?: string }).toolName ?? \"?\"})`;\n\treturn message.role;\n}\n\nexport function buildContextCompositionReport(input: BuildContextCompositionInput): ContextCompositionReport {\n\tconst systemPromptTokens = estimateTextTokens(input.systemPrompt);\n\n\tconst tools: ToolCompositionRow[] = input.tools\n\t\t.map((tool) => ({\n\t\t\tname: tool.name,\n\t\t\tschemaTokens: estimateTextTokens(\n\t\t\t\tJSON.stringify({ name: tool.name, description: tool.description ?? \"\", parameters: tool.parameters ?? {} }),\n\t\t\t),\n\t\t\tsource: tool.source ?? (\"built-in\" as const),\n\t\t}))\n\t\t.sort((a, b) => b.schemaTokens - a.schemaTokens);\n\tconst toolSchemaTokens = tools.reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\tconst toolTokensByName = new Map(tools.map((tool) => [tool.name, tool.schemaTokens]));\n\n\tconst extensions: ExtensionCompositionRow[] = input.extensions\n\t\t.map((extension) => ({\n\t\t\tname: extension.name,\n\t\t\tpath: extension.path,\n\t\t\ttoolCount: extension.toolNames.length,\n\t\t\tcommandCount: extension.commandCount,\n\t\t\tactiveToolSchemaTokens: extension.toolNames.reduce(\n\t\t\t\t(sum, toolName) => sum + (toolTokensByName.get(toolName) ?? 0),\n\t\t\t\t0,\n\t\t\t),\n\t\t}))\n\t\t.sort((a, b) => b.activeToolSchemaTokens - a.activeToolSchemaTokens);\n\n\tconst classes = new Map<string, MessageClassRow>();\n\tlet messageTokens = 0;\n\tfor (const message of input.messages) {\n\t\tconst label = classifyMessage(message);\n\t\tconst tokens = estimateTokens(message);\n\t\tmessageTokens += tokens;\n\t\tconst row = classes.get(label) ?? { label, count: 0, tokens: 0 };\n\t\trow.count++;\n\t\trow.tokens += tokens;\n\t\tclasses.set(label, row);\n\t}\n\tconst messageClasses = [...classes.values()].sort((a, b) => b.tokens - a.tokens);\n\n\tconst adjustments = input.adjustments ?? { memoryEvidenceTokens: 0, enforcementSavedTokens: 0 };\n\tconst estimatedRequestTokens = Math.max(\n\t\t0,\n\t\tsystemPromptTokens +\n\t\t\ttoolSchemaTokens +\n\t\t\tmessageTokens +\n\t\t\tadjustments.memoryEvidenceTokens -\n\t\t\tadjustments.enforcementSavedTokens,\n\t);\n\n\tconst observations: string[] = [...(input.extraObservations ?? [])];\n\tconst heaviestTool = tools[0];\n\tif (heaviestTool && toolSchemaTokens > 0 && heaviestTool.schemaTokens > Math.max(500, toolSchemaTokens * 0.3)) {\n\t\tobservations.push(\n\t\t\t`tool \"${heaviestTool.name}\" alone is ~${heaviestTool.schemaTokens} tokens of schema on EVERY request — trim its description/schema if you own it`,\n\t\t);\n\t}\n\tconst recall = messageClasses.find((row) => row.label === \"memory recall page\");\n\tif (recall && recall.tokens > 1500) {\n\t\tobservations.push(\n\t\t\t`${recall.count} memory recall page(s) hold ~${recall.tokens} tokens — verify context GC is packing stale ones (gc packed: ${input.gc?.packedCount ?? 0})`,\n\t\t);\n\t}\n\tif (input.contextWindow && systemPromptTokens + toolSchemaTokens > input.contextWindow * 0.35) {\n\t\tobservations.push(\n\t\t\t`fixed per-request overhead (system+tools) is ~${Math.round(((systemPromptTokens + toolSchemaTokens) / input.contextWindow) * 100)}% of the context window before any conversation`,\n\t\t);\n\t}\n\tif (input.providerReportedTokens !== null) {\n\t\tconst delta = input.providerReportedTokens - estimatedRequestTokens;\n\t\tif (Math.abs(delta) > Math.max(2000, estimatedRequestTokens * 0.25)) {\n\t\t\tobservations.push(\n\t\t\t\t`provider-reported context (${input.providerReportedTokens}) differs from the estimate by ${delta > 0 ? \"+\" : \"\"}${delta} tokens — treat estimates as directional`,\n\t\t\t);\n\t\t}\n\t}\n\tif (input.curation?.enabled && input.curation.lastSkipReason) {\n\t\tobservations.push(`curation is enabled but idle: ${input.curation.lastSkipReason}`);\n\t}\n\n\treturn {\n\t\tsystemPromptTokens,\n\t\tsystemPromptChars: input.systemPrompt.length,\n\t\ttoolSchemaTokens,\n\t\ttools,\n\t\textensions,\n\t\tmessageClasses,\n\t\tmessageTokens,\n\t\tmessageCount: input.messages.length,\n\t\testimatedRequestTokens,\n\t\tproviderReportedTokens: input.providerReportedTokens,\n\t\tcontextWindow: input.contextWindow,\n\t\tgc: input.gc ?? null,\n\t\tenforcement: input.enforcement ?? null,\n\t\tcuration: input.curation ?? null,\n\t\tspawned: input.spawned ?? null,\n\t\tadjustments,\n\t\tobservations,\n\t};\n}\n\n/** Bounded plain-text dashboard (interactive `/context` command and tests). */\nexport function formatContextCompositionDashboard(report: ContextCompositionReport, maxToolRows = 10): string {\n\tconst pct = (tokens: number) =>\n\t\treport.contextWindow ? ` (${((tokens / report.contextWindow) * 100).toFixed(1)}% of window)` : \"\";\n\tconst lines: string[] = [\n\t\t\"Context composition — what rides on EVERY request\",\n\t\t`estimated request total: ~${report.estimatedRequestTokens} tokens${pct(report.estimatedRequestTokens)}${\n\t\t\treport.providerReportedTokens !== null ? ` · provider-reported: ${report.providerReportedTokens}` : \"\"\n\t\t}`,\n\t\t\"\",\n\t\t`system prompt: ~${report.systemPromptTokens} tokens (${report.systemPromptChars} chars)`,\n\t\t`tool schemas: ~${report.toolSchemaTokens} tokens across ${report.tools.length} active tool(s)`,\n\t];\n\tfor (const tool of report.tools.slice(0, maxToolRows)) {\n\t\tlines.push(` - ${tool.name}: ~${tool.schemaTokens} tok [${tool.source}]`);\n\t}\n\tif (report.tools.length > maxToolRows) {\n\t\tconst rest = report.tools.slice(maxToolRows).reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\t\tlines.push(` - (+${report.tools.length - maxToolRows} more: ~${rest} tok)`);\n\t}\n\tif (report.extensions.length > 0) {\n\t\tlines.push(\"\", \"extensions:\");\n\t\tfor (const extension of report.extensions.slice(0, 8)) {\n\t\t\tlines.push(\n\t\t\t\t` - ${extension.name}: ${extension.toolCount} tool(s), ${extension.commandCount} command(s), ~${extension.activeToolSchemaTokens} tok of active schemas`,\n\t\t\t);\n\t\t}\n\t}\n\tlines.push(\"\", `session messages: ${report.messageCount} row(s), ~${report.messageTokens} tokens`);\n\tif (report.adjustments.memoryEvidenceTokens > 0 || report.adjustments.enforcementSavedTokens > 0) {\n\t\tlines.push(\n\t\t\t`send-time adjustments: +${report.adjustments.memoryEvidenceTokens} memory evidence, -${report.adjustments.enforcementSavedTokens} policy stubs (applied when the request is built)`,\n\t\t);\n\t}\n\tfor (const row of report.messageClasses.slice(0, 10)) {\n\t\tlines.push(` - ${row.label}: ${row.count} row(s), ~${row.tokens} tok`);\n\t}\n\tif (report.gc) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`context GC: ${report.gc.packedCount} row(s) packed, ~${report.gc.savedTokens} tokens saved this pass`,\n\t\t);\n\t}\n\tif (report.enforcement) {\n\t\tlines.push(\n\t\t\t`prompt policy: ${report.enforcement.enforcedCount} stub(s) this turn (${report.enforcement.advisoryEvictions} via brain advisory)`,\n\t\t);\n\t}\n\tif (report.curation) {\n\t\tconst t = report.curation.telemetry;\n\t\tlines.push(\n\t\t\t`brain curation: ${report.curation.enabled ? \"enabled\" : \"disabled\"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.digestsServed} digest(s) served into stubs, ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${\n\t\t\t\treport.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : \"\"\n\t\t\t}`,\n\t\t);\n\t}\n\tif (report.spawned && report.spawned.reports > 0) {\n\t\tlines.push(\n\t\t\t`spawned/background spend (NOT in this context): ${report.spawned.reports} report(s), $${report.spawned.cost.toFixed(4)}`,\n\t\t);\n\t}\n\tif (report.observations.length > 0) {\n\t\tlines.push(\"\", \"observations:\");\n\t\tfor (const observation of report.observations.slice(0, 5)) {\n\t\t\tlines.push(` ! ${observation}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -147,7 +147,7 @@ export function formatContextCompositionDashboard(report, maxToolRows = 10) {
147
147
  }
148
148
  if (report.curation) {
149
149
  const t = report.curation.telemetry;
150
- lines.push(`brain curation: ${report.curation.enabled ? "enabled" : "disabled"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${report.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : ""}`);
150
+ lines.push(`brain curation: ${report.curation.enabled ? "enabled" : "disabled"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.digestsServed} digest(s) served into stubs, ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${report.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : ""}`);
151
151
  }
152
152
  if (report.spawned && report.spawned.reports > 0) {
153
153
  lines.push(`spawned/background spend (NOT in this context): ${report.spawned.reports} report(s), $${report.spawned.cost.toFixed(4)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"context-composition.js","sourceRoot":"","sources":["../../../src/core/context/context-composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { CurationTelemetrySnapshot } from \"./brain-curator.ts\";\n\n/**\n * Context composition dashboard (user-facing): decomposes EVERYTHING that rides along on every\n * request — system prompt, active tool schemas, extension contributions, injected blocks\n * (memory recall pages, evidence blocks), and the session messages themselves (raw vs. GC-packed\n * vs. policy-stubbed) — so a user integrating their own tools/extensions can see exactly what\n * each addition costs per request and where cleaning is (or is not) working.\n *\n * Honesty contract: everything here is an ESTIMATE (chars/4) EXCEPT `providerReportedTokens`,\n * which is what the provider actually billed. The dashboard always shows both and the delta —\n * the delta is the measure of how much the estimates can be trusted, never hidden.\n *\n * Known exclusions (named, not hidden): extension `context` handlers may rewrite messages at\n * send time in ways this view cannot see. The memory evidence block and enforcement stubbing\n * are ALSO send-time-only, but those are modeled explicitly via `adjustments`.\n */\n\nexport interface ToolCompositionRow {\n\tname: string;\n\t/** Estimated tokens for the tool's name+description+schema as sent to the provider. */\n\tschemaTokens: number;\n\tsource: \"built-in\" | \"extension\";\n}\n\nexport interface ExtensionCompositionRow {\n\tname: string;\n\tpath: string;\n\ttoolCount: number;\n\tcommandCount: number;\n\t/** Estimated schema tokens of this extension's ACTIVE tools (its per-request cost). */\n\tactiveToolSchemaTokens: number;\n}\n\nexport interface MessageClassRow {\n\tlabel: string;\n\tcount: number;\n\ttokens: number;\n}\n\nexport interface ContextCompositionReport {\n\t/** Estimated tokens of the system prompt sent on every request. */\n\tsystemPromptTokens: number;\n\tsystemPromptChars: number;\n\t/** Estimated tokens of ALL active tool schemas sent on every request. */\n\ttoolSchemaTokens: number;\n\ttools: ToolCompositionRow[];\n\textensions: ExtensionCompositionRow[];\n\t/** Session message classes (raw/user/assistant/stubs/recall pages), heaviest first. */\n\tmessageClasses: MessageClassRow[];\n\tmessageTokens: number;\n\tmessageCount: number;\n\t/** Estimated total sent per request: system prompt + tool schemas + messages. */\n\testimatedRequestTokens: number;\n\t/** What the provider actually reported for the current context, when known. */\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc: { packedCount: number; savedTokens: number } | null;\n\tenforcement: { enforcedCount: number; advisoryEvictions: number } | null;\n\tcuration: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string } | null;\n\t/** Background/side-channel spend that does NOT ride in this context but bills the account. */\n\tspawned: { cost: number; reports: number } | null;\n\t/** Send-time-only deltas folded into estimatedRequestTokens: +evidence block, -policy stubs. */\n\tadjustments: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Actionable, bounded observations derived from the numbers above. */\n\tobservations: string[];\n}\n\nexport interface BuildContextCompositionInput {\n\tsystemPrompt: string;\n\ttools: Array<{ name: string; description?: string; parameters?: unknown; source?: \"built-in\" | \"extension\" }>;\n\textensions: Array<{\n\t\tname: string;\n\t\tpath: string;\n\t\ttoolNames: string[];\n\t\tcommandCount: number;\n\t}>;\n\tmessages: AgentMessage[];\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc?: { packedCount: number; savedTokens: number };\n\tenforcement?: { enforcedCount: number; advisoryEvictions: number };\n\tcuration?: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string };\n\tspawned?: { cost: number; reports: number };\n\tadjustments?: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Pre-formed warnings from other subsystems (e.g. profile-withheld context files). */\n\textraObservations?: string[];\n}\n\nfunction estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst content = (message as { content?: unknown }).content;\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => (part as { type?: string }).type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction classifyMessage(message: AgentMessage): string {\n\tconst details = (\n\t\tmessage as { details?: { contextGc?: { packed?: unknown }; promptPolicy?: { enforced?: unknown } } }\n\t).details;\n\tif (details?.contextGc?.packed === true) return \"gc-packed stub\";\n\tif (details?.promptPolicy?.enforced === true) return \"policy stub\";\n\tif (message.role === \"custom\") {\n\t\tconst customType = (message as { customType?: string }).customType ?? \"\";\n\t\tif (customType === \"memory_context\" || messageText(message).includes(\"<memory_context\")) {\n\t\t\treturn \"memory recall page\";\n\t\t}\n\t\treturn `custom (${customType || \"unknown\"})`;\n\t}\n\tif (message.role === \"toolResult\") return `toolResult (${(message as { toolName?: string }).toolName ?? \"?\"})`;\n\treturn message.role;\n}\n\nexport function buildContextCompositionReport(input: BuildContextCompositionInput): ContextCompositionReport {\n\tconst systemPromptTokens = estimateTextTokens(input.systemPrompt);\n\n\tconst tools: ToolCompositionRow[] = input.tools\n\t\t.map((tool) => ({\n\t\t\tname: tool.name,\n\t\t\tschemaTokens: estimateTextTokens(\n\t\t\t\tJSON.stringify({ name: tool.name, description: tool.description ?? \"\", parameters: tool.parameters ?? {} }),\n\t\t\t),\n\t\t\tsource: tool.source ?? (\"built-in\" as const),\n\t\t}))\n\t\t.sort((a, b) => b.schemaTokens - a.schemaTokens);\n\tconst toolSchemaTokens = tools.reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\tconst toolTokensByName = new Map(tools.map((tool) => [tool.name, tool.schemaTokens]));\n\n\tconst extensions: ExtensionCompositionRow[] = input.extensions\n\t\t.map((extension) => ({\n\t\t\tname: extension.name,\n\t\t\tpath: extension.path,\n\t\t\ttoolCount: extension.toolNames.length,\n\t\t\tcommandCount: extension.commandCount,\n\t\t\tactiveToolSchemaTokens: extension.toolNames.reduce(\n\t\t\t\t(sum, toolName) => sum + (toolTokensByName.get(toolName) ?? 0),\n\t\t\t\t0,\n\t\t\t),\n\t\t}))\n\t\t.sort((a, b) => b.activeToolSchemaTokens - a.activeToolSchemaTokens);\n\n\tconst classes = new Map<string, MessageClassRow>();\n\tlet messageTokens = 0;\n\tfor (const message of input.messages) {\n\t\tconst label = classifyMessage(message);\n\t\tconst tokens = estimateTokens(message);\n\t\tmessageTokens += tokens;\n\t\tconst row = classes.get(label) ?? { label, count: 0, tokens: 0 };\n\t\trow.count++;\n\t\trow.tokens += tokens;\n\t\tclasses.set(label, row);\n\t}\n\tconst messageClasses = [...classes.values()].sort((a, b) => b.tokens - a.tokens);\n\n\tconst adjustments = input.adjustments ?? { memoryEvidenceTokens: 0, enforcementSavedTokens: 0 };\n\tconst estimatedRequestTokens = Math.max(\n\t\t0,\n\t\tsystemPromptTokens +\n\t\t\ttoolSchemaTokens +\n\t\t\tmessageTokens +\n\t\t\tadjustments.memoryEvidenceTokens -\n\t\t\tadjustments.enforcementSavedTokens,\n\t);\n\n\tconst observations: string[] = [...(input.extraObservations ?? [])];\n\tconst heaviestTool = tools[0];\n\tif (heaviestTool && toolSchemaTokens > 0 && heaviestTool.schemaTokens > Math.max(500, toolSchemaTokens * 0.3)) {\n\t\tobservations.push(\n\t\t\t`tool \"${heaviestTool.name}\" alone is ~${heaviestTool.schemaTokens} tokens of schema on EVERY request — trim its description/schema if you own it`,\n\t\t);\n\t}\n\tconst recall = messageClasses.find((row) => row.label === \"memory recall page\");\n\tif (recall && recall.tokens > 1500) {\n\t\tobservations.push(\n\t\t\t`${recall.count} memory recall page(s) hold ~${recall.tokens} tokens — verify context GC is packing stale ones (gc packed: ${input.gc?.packedCount ?? 0})`,\n\t\t);\n\t}\n\tif (input.contextWindow && systemPromptTokens + toolSchemaTokens > input.contextWindow * 0.35) {\n\t\tobservations.push(\n\t\t\t`fixed per-request overhead (system+tools) is ~${Math.round(((systemPromptTokens + toolSchemaTokens) / input.contextWindow) * 100)}% of the context window before any conversation`,\n\t\t);\n\t}\n\tif (input.providerReportedTokens !== null) {\n\t\tconst delta = input.providerReportedTokens - estimatedRequestTokens;\n\t\tif (Math.abs(delta) > Math.max(2000, estimatedRequestTokens * 0.25)) {\n\t\t\tobservations.push(\n\t\t\t\t`provider-reported context (${input.providerReportedTokens}) differs from the estimate by ${delta > 0 ? \"+\" : \"\"}${delta} tokens — treat estimates as directional`,\n\t\t\t);\n\t\t}\n\t}\n\tif (input.curation?.enabled && input.curation.lastSkipReason) {\n\t\tobservations.push(`curation is enabled but idle: ${input.curation.lastSkipReason}`);\n\t}\n\n\treturn {\n\t\tsystemPromptTokens,\n\t\tsystemPromptChars: input.systemPrompt.length,\n\t\ttoolSchemaTokens,\n\t\ttools,\n\t\textensions,\n\t\tmessageClasses,\n\t\tmessageTokens,\n\t\tmessageCount: input.messages.length,\n\t\testimatedRequestTokens,\n\t\tproviderReportedTokens: input.providerReportedTokens,\n\t\tcontextWindow: input.contextWindow,\n\t\tgc: input.gc ?? null,\n\t\tenforcement: input.enforcement ?? null,\n\t\tcuration: input.curation ?? null,\n\t\tspawned: input.spawned ?? null,\n\t\tadjustments,\n\t\tobservations,\n\t};\n}\n\n/** Bounded plain-text dashboard (interactive `/context` command and tests). */\nexport function formatContextCompositionDashboard(report: ContextCompositionReport, maxToolRows = 10): string {\n\tconst pct = (tokens: number) =>\n\t\treport.contextWindow ? ` (${((tokens / report.contextWindow) * 100).toFixed(1)}% of window)` : \"\";\n\tconst lines: string[] = [\n\t\t\"Context composition — what rides on EVERY request\",\n\t\t`estimated request total: ~${report.estimatedRequestTokens} tokens${pct(report.estimatedRequestTokens)}${\n\t\t\treport.providerReportedTokens !== null ? ` · provider-reported: ${report.providerReportedTokens}` : \"\"\n\t\t}`,\n\t\t\"\",\n\t\t`system prompt: ~${report.systemPromptTokens} tokens (${report.systemPromptChars} chars)`,\n\t\t`tool schemas: ~${report.toolSchemaTokens} tokens across ${report.tools.length} active tool(s)`,\n\t];\n\tfor (const tool of report.tools.slice(0, maxToolRows)) {\n\t\tlines.push(` - ${tool.name}: ~${tool.schemaTokens} tok [${tool.source}]`);\n\t}\n\tif (report.tools.length > maxToolRows) {\n\t\tconst rest = report.tools.slice(maxToolRows).reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\t\tlines.push(` - (+${report.tools.length - maxToolRows} more: ~${rest} tok)`);\n\t}\n\tif (report.extensions.length > 0) {\n\t\tlines.push(\"\", \"extensions:\");\n\t\tfor (const extension of report.extensions.slice(0, 8)) {\n\t\t\tlines.push(\n\t\t\t\t` - ${extension.name}: ${extension.toolCount} tool(s), ${extension.commandCount} command(s), ~${extension.activeToolSchemaTokens} tok of active schemas`,\n\t\t\t);\n\t\t}\n\t}\n\tlines.push(\"\", `session messages: ${report.messageCount} row(s), ~${report.messageTokens} tokens`);\n\tif (report.adjustments.memoryEvidenceTokens > 0 || report.adjustments.enforcementSavedTokens > 0) {\n\t\tlines.push(\n\t\t\t`send-time adjustments: +${report.adjustments.memoryEvidenceTokens} memory evidence, -${report.adjustments.enforcementSavedTokens} policy stubs (applied when the request is built)`,\n\t\t);\n\t}\n\tfor (const row of report.messageClasses.slice(0, 10)) {\n\t\tlines.push(` - ${row.label}: ${row.count} row(s), ~${row.tokens} tok`);\n\t}\n\tif (report.gc) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`context GC: ${report.gc.packedCount} row(s) packed, ~${report.gc.savedTokens} tokens saved this pass`,\n\t\t);\n\t}\n\tif (report.enforcement) {\n\t\tlines.push(\n\t\t\t`prompt policy: ${report.enforcement.enforcedCount} stub(s) this turn (${report.enforcement.advisoryEvictions} via brain advisory)`,\n\t\t);\n\t}\n\tif (report.curation) {\n\t\tconst t = report.curation.telemetry;\n\t\tlines.push(\n\t\t\t`brain curation: ${report.curation.enabled ? \"enabled\" : \"disabled\"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${\n\t\t\t\treport.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : \"\"\n\t\t\t}`,\n\t\t);\n\t}\n\tif (report.spawned && report.spawned.reports > 0) {\n\t\tlines.push(\n\t\t\t`spawned/background spend (NOT in this context): ${report.spawned.reports} report(s), $${report.spawned.cost.toFixed(4)}`,\n\t\t);\n\t}\n\tif (report.observations.length > 0) {\n\t\tlines.push(\"\", \"observations:\");\n\t\tfor (const observation of report.observations.slice(0, 5)) {\n\t\t\tlines.push(` ! ${observation}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"context-composition.js","sourceRoot":"","sources":["../../../src/core/context/context-composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { CurationTelemetrySnapshot } from \"./brain-curator.ts\";\n\n/**\n * Context composition dashboard (user-facing): decomposes EVERYTHING that rides along on every\n * request — system prompt, active tool schemas, extension contributions, injected blocks\n * (memory recall pages, evidence blocks), and the session messages themselves (raw vs. GC-packed\n * vs. policy-stubbed) — so a user integrating their own tools/extensions can see exactly what\n * each addition costs per request and where cleaning is (or is not) working.\n *\n * Honesty contract: everything here is an ESTIMATE (chars/4) EXCEPT `providerReportedTokens`,\n * which is what the provider actually billed. The dashboard always shows both and the delta —\n * the delta is the measure of how much the estimates can be trusted, never hidden.\n *\n * Known exclusions (named, not hidden): extension `context` handlers may rewrite messages at\n * send time in ways this view cannot see. The memory evidence block and enforcement stubbing\n * are ALSO send-time-only, but those are modeled explicitly via `adjustments`.\n */\n\nexport interface ToolCompositionRow {\n\tname: string;\n\t/** Estimated tokens for the tool's name+description+schema as sent to the provider. */\n\tschemaTokens: number;\n\tsource: \"built-in\" | \"extension\";\n}\n\nexport interface ExtensionCompositionRow {\n\tname: string;\n\tpath: string;\n\ttoolCount: number;\n\tcommandCount: number;\n\t/** Estimated schema tokens of this extension's ACTIVE tools (its per-request cost). */\n\tactiveToolSchemaTokens: number;\n}\n\nexport interface MessageClassRow {\n\tlabel: string;\n\tcount: number;\n\ttokens: number;\n}\n\nexport interface ContextCompositionReport {\n\t/** Estimated tokens of the system prompt sent on every request. */\n\tsystemPromptTokens: number;\n\tsystemPromptChars: number;\n\t/** Estimated tokens of ALL active tool schemas sent on every request. */\n\ttoolSchemaTokens: number;\n\ttools: ToolCompositionRow[];\n\textensions: ExtensionCompositionRow[];\n\t/** Session message classes (raw/user/assistant/stubs/recall pages), heaviest first. */\n\tmessageClasses: MessageClassRow[];\n\tmessageTokens: number;\n\tmessageCount: number;\n\t/** Estimated total sent per request: system prompt + tool schemas + messages. */\n\testimatedRequestTokens: number;\n\t/** What the provider actually reported for the current context, when known. */\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc: { packedCount: number; savedTokens: number } | null;\n\tenforcement: { enforcedCount: number; advisoryEvictions: number } | null;\n\tcuration: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string } | null;\n\t/** Background/side-channel spend that does NOT ride in this context but bills the account. */\n\tspawned: { cost: number; reports: number } | null;\n\t/** Send-time-only deltas folded into estimatedRequestTokens: +evidence block, -policy stubs. */\n\tadjustments: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Actionable, bounded observations derived from the numbers above. */\n\tobservations: string[];\n}\n\nexport interface BuildContextCompositionInput {\n\tsystemPrompt: string;\n\ttools: Array<{ name: string; description?: string; parameters?: unknown; source?: \"built-in\" | \"extension\" }>;\n\textensions: Array<{\n\t\tname: string;\n\t\tpath: string;\n\t\ttoolNames: string[];\n\t\tcommandCount: number;\n\t}>;\n\tmessages: AgentMessage[];\n\tproviderReportedTokens: number | null;\n\tcontextWindow: number | null;\n\tgc?: { packedCount: number; savedTokens: number };\n\tenforcement?: { enforcedCount: number; advisoryEvictions: number };\n\tcuration?: { enabled: boolean; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string };\n\tspawned?: { cost: number; reports: number };\n\tadjustments?: { memoryEvidenceTokens: number; enforcementSavedTokens: number };\n\t/** Pre-formed warnings from other subsystems (e.g. profile-withheld context files). */\n\textraObservations?: string[];\n}\n\nfunction estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst content = (message as { content?: unknown }).content;\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => (part as { type?: string }).type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction classifyMessage(message: AgentMessage): string {\n\tconst details = (\n\t\tmessage as { details?: { contextGc?: { packed?: unknown }; promptPolicy?: { enforced?: unknown } } }\n\t).details;\n\tif (details?.contextGc?.packed === true) return \"gc-packed stub\";\n\tif (details?.promptPolicy?.enforced === true) return \"policy stub\";\n\tif (message.role === \"custom\") {\n\t\tconst customType = (message as { customType?: string }).customType ?? \"\";\n\t\tif (customType === \"memory_context\" || messageText(message).includes(\"<memory_context\")) {\n\t\t\treturn \"memory recall page\";\n\t\t}\n\t\treturn `custom (${customType || \"unknown\"})`;\n\t}\n\tif (message.role === \"toolResult\") return `toolResult (${(message as { toolName?: string }).toolName ?? \"?\"})`;\n\treturn message.role;\n}\n\nexport function buildContextCompositionReport(input: BuildContextCompositionInput): ContextCompositionReport {\n\tconst systemPromptTokens = estimateTextTokens(input.systemPrompt);\n\n\tconst tools: ToolCompositionRow[] = input.tools\n\t\t.map((tool) => ({\n\t\t\tname: tool.name,\n\t\t\tschemaTokens: estimateTextTokens(\n\t\t\t\tJSON.stringify({ name: tool.name, description: tool.description ?? \"\", parameters: tool.parameters ?? {} }),\n\t\t\t),\n\t\t\tsource: tool.source ?? (\"built-in\" as const),\n\t\t}))\n\t\t.sort((a, b) => b.schemaTokens - a.schemaTokens);\n\tconst toolSchemaTokens = tools.reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\tconst toolTokensByName = new Map(tools.map((tool) => [tool.name, tool.schemaTokens]));\n\n\tconst extensions: ExtensionCompositionRow[] = input.extensions\n\t\t.map((extension) => ({\n\t\t\tname: extension.name,\n\t\t\tpath: extension.path,\n\t\t\ttoolCount: extension.toolNames.length,\n\t\t\tcommandCount: extension.commandCount,\n\t\t\tactiveToolSchemaTokens: extension.toolNames.reduce(\n\t\t\t\t(sum, toolName) => sum + (toolTokensByName.get(toolName) ?? 0),\n\t\t\t\t0,\n\t\t\t),\n\t\t}))\n\t\t.sort((a, b) => b.activeToolSchemaTokens - a.activeToolSchemaTokens);\n\n\tconst classes = new Map<string, MessageClassRow>();\n\tlet messageTokens = 0;\n\tfor (const message of input.messages) {\n\t\tconst label = classifyMessage(message);\n\t\tconst tokens = estimateTokens(message);\n\t\tmessageTokens += tokens;\n\t\tconst row = classes.get(label) ?? { label, count: 0, tokens: 0 };\n\t\trow.count++;\n\t\trow.tokens += tokens;\n\t\tclasses.set(label, row);\n\t}\n\tconst messageClasses = [...classes.values()].sort((a, b) => b.tokens - a.tokens);\n\n\tconst adjustments = input.adjustments ?? { memoryEvidenceTokens: 0, enforcementSavedTokens: 0 };\n\tconst estimatedRequestTokens = Math.max(\n\t\t0,\n\t\tsystemPromptTokens +\n\t\t\ttoolSchemaTokens +\n\t\t\tmessageTokens +\n\t\t\tadjustments.memoryEvidenceTokens -\n\t\t\tadjustments.enforcementSavedTokens,\n\t);\n\n\tconst observations: string[] = [...(input.extraObservations ?? [])];\n\tconst heaviestTool = tools[0];\n\tif (heaviestTool && toolSchemaTokens > 0 && heaviestTool.schemaTokens > Math.max(500, toolSchemaTokens * 0.3)) {\n\t\tobservations.push(\n\t\t\t`tool \"${heaviestTool.name}\" alone is ~${heaviestTool.schemaTokens} tokens of schema on EVERY request — trim its description/schema if you own it`,\n\t\t);\n\t}\n\tconst recall = messageClasses.find((row) => row.label === \"memory recall page\");\n\tif (recall && recall.tokens > 1500) {\n\t\tobservations.push(\n\t\t\t`${recall.count} memory recall page(s) hold ~${recall.tokens} tokens — verify context GC is packing stale ones (gc packed: ${input.gc?.packedCount ?? 0})`,\n\t\t);\n\t}\n\tif (input.contextWindow && systemPromptTokens + toolSchemaTokens > input.contextWindow * 0.35) {\n\t\tobservations.push(\n\t\t\t`fixed per-request overhead (system+tools) is ~${Math.round(((systemPromptTokens + toolSchemaTokens) / input.contextWindow) * 100)}% of the context window before any conversation`,\n\t\t);\n\t}\n\tif (input.providerReportedTokens !== null) {\n\t\tconst delta = input.providerReportedTokens - estimatedRequestTokens;\n\t\tif (Math.abs(delta) > Math.max(2000, estimatedRequestTokens * 0.25)) {\n\t\t\tobservations.push(\n\t\t\t\t`provider-reported context (${input.providerReportedTokens}) differs from the estimate by ${delta > 0 ? \"+\" : \"\"}${delta} tokens — treat estimates as directional`,\n\t\t\t);\n\t\t}\n\t}\n\tif (input.curation?.enabled && input.curation.lastSkipReason) {\n\t\tobservations.push(`curation is enabled but idle: ${input.curation.lastSkipReason}`);\n\t}\n\n\treturn {\n\t\tsystemPromptTokens,\n\t\tsystemPromptChars: input.systemPrompt.length,\n\t\ttoolSchemaTokens,\n\t\ttools,\n\t\textensions,\n\t\tmessageClasses,\n\t\tmessageTokens,\n\t\tmessageCount: input.messages.length,\n\t\testimatedRequestTokens,\n\t\tproviderReportedTokens: input.providerReportedTokens,\n\t\tcontextWindow: input.contextWindow,\n\t\tgc: input.gc ?? null,\n\t\tenforcement: input.enforcement ?? null,\n\t\tcuration: input.curation ?? null,\n\t\tspawned: input.spawned ?? null,\n\t\tadjustments,\n\t\tobservations,\n\t};\n}\n\n/** Bounded plain-text dashboard (interactive `/context` command and tests). */\nexport function formatContextCompositionDashboard(report: ContextCompositionReport, maxToolRows = 10): string {\n\tconst pct = (tokens: number) =>\n\t\treport.contextWindow ? ` (${((tokens / report.contextWindow) * 100).toFixed(1)}% of window)` : \"\";\n\tconst lines: string[] = [\n\t\t\"Context composition — what rides on EVERY request\",\n\t\t`estimated request total: ~${report.estimatedRequestTokens} tokens${pct(report.estimatedRequestTokens)}${\n\t\t\treport.providerReportedTokens !== null ? ` · provider-reported: ${report.providerReportedTokens}` : \"\"\n\t\t}`,\n\t\t\"\",\n\t\t`system prompt: ~${report.systemPromptTokens} tokens (${report.systemPromptChars} chars)`,\n\t\t`tool schemas: ~${report.toolSchemaTokens} tokens across ${report.tools.length} active tool(s)`,\n\t];\n\tfor (const tool of report.tools.slice(0, maxToolRows)) {\n\t\tlines.push(` - ${tool.name}: ~${tool.schemaTokens} tok [${tool.source}]`);\n\t}\n\tif (report.tools.length > maxToolRows) {\n\t\tconst rest = report.tools.slice(maxToolRows).reduce((sum, tool) => sum + tool.schemaTokens, 0);\n\t\tlines.push(` - (+${report.tools.length - maxToolRows} more: ~${rest} tok)`);\n\t}\n\tif (report.extensions.length > 0) {\n\t\tlines.push(\"\", \"extensions:\");\n\t\tfor (const extension of report.extensions.slice(0, 8)) {\n\t\t\tlines.push(\n\t\t\t\t` - ${extension.name}: ${extension.toolCount} tool(s), ${extension.commandCount} command(s), ~${extension.activeToolSchemaTokens} tok of active schemas`,\n\t\t\t);\n\t\t}\n\t}\n\tlines.push(\"\", `session messages: ${report.messageCount} row(s), ~${report.messageTokens} tokens`);\n\tif (report.adjustments.memoryEvidenceTokens > 0 || report.adjustments.enforcementSavedTokens > 0) {\n\t\tlines.push(\n\t\t\t`send-time adjustments: +${report.adjustments.memoryEvidenceTokens} memory evidence, -${report.adjustments.enforcementSavedTokens} policy stubs (applied when the request is built)`,\n\t\t);\n\t}\n\tfor (const row of report.messageClasses.slice(0, 10)) {\n\t\tlines.push(` - ${row.label}: ${row.count} row(s), ~${row.tokens} tok`);\n\t}\n\tif (report.gc) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`context GC: ${report.gc.packedCount} row(s) packed, ~${report.gc.savedTokens} tokens saved this pass`,\n\t\t);\n\t}\n\tif (report.enforcement) {\n\t\tlines.push(\n\t\t\t`prompt policy: ${report.enforcement.enforcedCount} stub(s) this turn (${report.enforcement.advisoryEvictions} via brain advisory)`,\n\t\t);\n\t}\n\tif (report.curation) {\n\t\tconst t = report.curation.telemetry;\n\t\tlines.push(\n\t\t\t`brain curation: ${report.curation.enabled ? \"enabled\" : \"disabled\"} — ${t.jobsRun} job(s) run, ${t.parseFailures} parse failure(s), ${t.digestsServed} digest(s) served into stubs, ${t.queued} queued, ~${Math.ceil(t.localChars / 4)} tokens processed locally${\n\t\t\t\treport.curation.lastSkipReason ? ` · last skip: ${report.curation.lastSkipReason}` : \"\"\n\t\t\t}`,\n\t\t);\n\t}\n\tif (report.spawned && report.spawned.reports > 0) {\n\t\tlines.push(\n\t\t\t`spawned/background spend (NOT in this context): ${report.spawned.reports} report(s), $${report.spawned.cost.toFixed(4)}`,\n\t\t);\n\t}\n\tif (report.observations.length > 0) {\n\t\tlines.push(\"\", \"observations:\");\n\t\tfor (const observation of report.observations.slice(0, 5)) {\n\t\t\tlines.push(` ! ${observation}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -1,10 +1,16 @@
1
- import type { WorkerResult } from "../autonomy/contracts.ts";
1
+ import type { WorkerRequest, WorkerResult } from "../autonomy/contracts.ts";
2
2
  import type { SessionEntry, SessionManager } from "../session-manager.ts";
3
3
  export declare const WORKER_RESULT_CUSTOM_TYPE = "worker_result";
4
4
  export interface WorkerResultSnapshotPayload {
5
5
  version: 1;
6
6
  result: WorkerResult;
7
+ /** The originating request (G2): persisted so a result is auditable against exactly what was
8
+ * asked — instructions, route, and the capability envelope that bounded it. Optional for
9
+ * backward compatibility with pre-G2 entries. */
10
+ request?: WorkerRequest;
7
11
  }
8
- export declare function appendWorkerResultSnapshot(sessionManager: Pick<SessionManager, "appendCustomEntry">, result: WorkerResult): string;
12
+ export declare function appendWorkerResultSnapshot(sessionManager: Pick<SessionManager, "appendCustomEntry">, result: WorkerResult, request?: WorkerRequest): string;
13
+ /** Requests persisted alongside results (absent for pre-G2 entries). */
14
+ export declare function getWorkerRequestSnapshots(entries: readonly SessionEntry[]): WorkerRequest[];
9
15
  export declare function getWorkerResultSnapshots(entries: readonly SessionEntry[]): WorkerResult[];
10
16
  //# sourceMappingURL=session-worker-result.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-worker-result.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/session-worker-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG1E,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC3C,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACrB;AAED,wBAAgB,0BAA0B,CACzC,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,MAAM,EAAE,YAAY,GAClB,MAAM,CAMR;AAQD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,EAAE,CAmBzF","sourcesContent":["import type { WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneWorkerResultForStorage, isWorkerResult } from \"./worker-result.ts\";\n\nexport const WORKER_RESULT_CUSTOM_TYPE = \"worker_result\";\n\nexport interface WorkerResultSnapshotPayload {\n\tversion: 1;\n\tresult: WorkerResult;\n}\n\nexport function appendWorkerResultSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tresult: WorkerResult,\n): string {\n\tconst payload: WorkerResultSnapshotPayload = {\n\t\tversion: 1,\n\t\tresult: cloneWorkerResultForStorage(result),\n\t};\n\treturn sessionManager.appendCustomEntry(WORKER_RESULT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getWorkerResultSnapshots(entries: readonly SessionEntry[]): WorkerResult[] {\n\tconst results: WorkerResult[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"result\" in payload)) continue;\n\t\tconst result = payload.result;\n\t\tif (isWorkerResult(result)) {\n\t\t\tresults.push(cloneWorkerResultForStorage(result));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
1
+ {"version":3,"file":"session-worker-result.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/session-worker-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG1E,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC3C,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;IACrB;;qDAEiD;IACjD,OAAO,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,0BAA0B,CACzC,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,aAAa,GACrB,MAAM,CAOR;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,aAAa,EAAE,CAY3F;AAQD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,EAAE,CAmBzF","sourcesContent":["import type { WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneWorkerResultForStorage, isWorkerResult } from \"./worker-result.ts\";\n\nexport const WORKER_RESULT_CUSTOM_TYPE = \"worker_result\";\n\nexport interface WorkerResultSnapshotPayload {\n\tversion: 1;\n\tresult: WorkerResult;\n\t/** The originating request (G2): persisted so a result is auditable against exactly what was\n\t * asked — instructions, route, and the capability envelope that bounded it. Optional for\n\t * backward compatibility with pre-G2 entries. */\n\trequest?: WorkerRequest;\n}\n\nexport function appendWorkerResultSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tresult: WorkerResult,\n\trequest?: WorkerRequest,\n): string {\n\tconst payload: WorkerResultSnapshotPayload = {\n\t\tversion: 1,\n\t\tresult: cloneWorkerResultForStorage(result),\n\t\t...(request ? { request: structuredClone(request) } : {}),\n\t};\n\treturn sessionManager.appendCustomEntry(WORKER_RESULT_CUSTOM_TYPE, payload);\n}\n\n/** Requests persisted alongside results (absent for pre-G2 entries). */\nexport function getWorkerRequestSnapshots(entries: readonly SessionEntry[]): WorkerRequest[] {\n\tconst requests: WorkerRequest[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) continue;\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload) || payload.version !== 1) continue;\n\t\tconst request = payload.request;\n\t\tif (isPlainRecord(request) && typeof request.id === \"string\") {\n\t\t\trequests.push(structuredClone(request) as unknown as WorkerRequest);\n\t\t}\n\t}\n\treturn requests;\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getWorkerResultSnapshots(entries: readonly SessionEntry[]): WorkerResult[] {\n\tconst results: WorkerResult[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"result\" in payload)) continue;\n\t\tconst result = payload.result;\n\t\tif (isWorkerResult(result)) {\n\t\t\tresults.push(cloneWorkerResultForStorage(result));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
@@ -1,12 +1,29 @@
1
1
  import { cloneWorkerResultForStorage, isWorkerResult } from "./worker-result.js";
2
2
  export const WORKER_RESULT_CUSTOM_TYPE = "worker_result";
3
- export function appendWorkerResultSnapshot(sessionManager, result) {
3
+ export function appendWorkerResultSnapshot(sessionManager, result, request) {
4
4
  const payload = {
5
5
  version: 1,
6
6
  result: cloneWorkerResultForStorage(result),
7
+ ...(request ? { request: structuredClone(request) } : {}),
7
8
  };
8
9
  return sessionManager.appendCustomEntry(WORKER_RESULT_CUSTOM_TYPE, payload);
9
10
  }
11
+ /** Requests persisted alongside results (absent for pre-G2 entries). */
12
+ export function getWorkerRequestSnapshots(entries) {
13
+ const requests = [];
14
+ for (const entry of entries) {
15
+ if (entry.type !== "custom" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE)
16
+ continue;
17
+ const payload = entry.data;
18
+ if (!isPlainRecord(payload) || payload.version !== 1)
19
+ continue;
20
+ const request = payload.request;
21
+ if (isPlainRecord(request) && typeof request.id === "string") {
22
+ requests.push(structuredClone(request));
23
+ }
24
+ }
25
+ return requests;
26
+ }
10
27
  function isPlainRecord(value) {
11
28
  if (!value || typeof value !== "object" || Array.isArray(value))
12
29
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"session-worker-result.js","sourceRoot":"","sources":["../../../src/core/delegation/session-worker-result.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEjF,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC;AAOzD,MAAM,UAAU,0BAA0B,CACzC,cAAyD,EACzD,MAAoB,EACX;IACT,MAAM,OAAO,GAAgC;QAC5C,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,2BAA2B,CAAC,MAAM,CAAC;KAC3C,CAAC;IACF,OAAO,cAAc,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,OAAO,CAAC,CAAC;AAAA,CAC5E;AAED,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAgC,EAAkB;IAC1F,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,yBAAyB,EAAE,CAAC;YAC/E,SAAS;QACV,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC;YAAE,SAAS;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf","sourcesContent":["import type { WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneWorkerResultForStorage, isWorkerResult } from \"./worker-result.ts\";\n\nexport const WORKER_RESULT_CUSTOM_TYPE = \"worker_result\";\n\nexport interface WorkerResultSnapshotPayload {\n\tversion: 1;\n\tresult: WorkerResult;\n}\n\nexport function appendWorkerResultSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tresult: WorkerResult,\n): string {\n\tconst payload: WorkerResultSnapshotPayload = {\n\t\tversion: 1,\n\t\tresult: cloneWorkerResultForStorage(result),\n\t};\n\treturn sessionManager.appendCustomEntry(WORKER_RESULT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getWorkerResultSnapshots(entries: readonly SessionEntry[]): WorkerResult[] {\n\tconst results: WorkerResult[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"result\" in payload)) continue;\n\t\tconst result = payload.result;\n\t\tif (isWorkerResult(result)) {\n\t\t\tresults.push(cloneWorkerResultForStorage(result));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
1
+ {"version":3,"file":"session-worker-result.js","sourceRoot":"","sources":["../../../src/core/delegation/session-worker-result.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEjF,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC;AAWzD,MAAM,UAAU,0BAA0B,CACzC,cAAyD,EACzD,MAAoB,EACpB,OAAuB,EACd;IACT,MAAM,OAAO,GAAgC;QAC5C,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,2BAA2B,CAAC,MAAM,CAAC;QAC3C,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzD,CAAC;IACF,OAAO,cAAc,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,OAAO,CAAC,CAAC;AAAA,CAC5E;AAED,wEAAwE;AACxE,MAAM,UAAU,yBAAyB,CAAC,OAAgC,EAAmB;IAC5F,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,yBAAyB;YAAE,SAAS;QACxF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS;QAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC9D,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAA6B,CAAC,CAAC;QACrE,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAgC,EAAkB;IAC1F,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,yBAAyB,EAAE,CAAC;YAC/E,SAAS;QACV,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC;YAAE,SAAS;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf","sourcesContent":["import type { WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneWorkerResultForStorage, isWorkerResult } from \"./worker-result.ts\";\n\nexport const WORKER_RESULT_CUSTOM_TYPE = \"worker_result\";\n\nexport interface WorkerResultSnapshotPayload {\n\tversion: 1;\n\tresult: WorkerResult;\n\t/** The originating request (G2): persisted so a result is auditable against exactly what was\n\t * asked — instructions, route, and the capability envelope that bounded it. Optional for\n\t * backward compatibility with pre-G2 entries. */\n\trequest?: WorkerRequest;\n}\n\nexport function appendWorkerResultSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tresult: WorkerResult,\n\trequest?: WorkerRequest,\n): string {\n\tconst payload: WorkerResultSnapshotPayload = {\n\t\tversion: 1,\n\t\tresult: cloneWorkerResultForStorage(result),\n\t\t...(request ? { request: structuredClone(request) } : {}),\n\t};\n\treturn sessionManager.appendCustomEntry(WORKER_RESULT_CUSTOM_TYPE, payload);\n}\n\n/** Requests persisted alongside results (absent for pre-G2 entries). */\nexport function getWorkerRequestSnapshots(entries: readonly SessionEntry[]): WorkerRequest[] {\n\tconst requests: WorkerRequest[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) continue;\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload) || payload.version !== 1) continue;\n\t\tconst request = payload.request;\n\t\tif (isPlainRecord(request) && typeof request.id === \"string\") {\n\t\t\trequests.push(structuredClone(request) as unknown as WorkerRequest);\n\t\t}\n\t}\n\treturn requests;\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getWorkerResultSnapshots(entries: readonly SessionEntry[]): WorkerResult[] {\n\tconst results: WorkerResult[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== WORKER_RESULT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"result\" in payload)) continue;\n\t\tconst result = payload.result;\n\t\tif (isWorkerResult(result)) {\n\t\t\tresults.push(cloneWorkerResultForStorage(result));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
@@ -0,0 +1,50 @@
1
+ import { existsSync } from "node:fs";
2
+ import type { CapabilityEnvelope } from "../autonomy/contracts.ts";
3
+ /**
4
+ * Code-writing workers (G2): the worker MODEL never touches the filesystem — it emits strict-JSON
5
+ * actions, and this RUNNER-side module applies them deterministically through the capability
6
+ * envelope's path scope. That keeps the structural-contract philosophy (a local model without
7
+ * tool-calling templates can still write code) and makes enforcement execution-time, not
8
+ * validation-only: an out-of-scope action is REFUSED with a reason, never silently dropped, and
9
+ * refusals surface as blockers on the result.
10
+ */
11
+ export interface WorkerAction {
12
+ op: "write" | "edit";
13
+ path: string;
14
+ /** write: full file content. */
15
+ content?: string;
16
+ /** edit: exact string to replace (must occur in the file). */
17
+ old?: string;
18
+ /** edit: replacement text. */
19
+ new?: string;
20
+ }
21
+ export declare function parseWorkerActions(raw: unknown): WorkerAction[];
22
+ export interface AppliedActionsReport {
23
+ /** Repo-relative paths actually changed. */
24
+ changedFiles: string[];
25
+ /** Envelope-scope refusals (execution-time enforcement) — surfaced, never silent. */
26
+ refused: Array<{
27
+ path: string;
28
+ reason: string;
29
+ }>;
30
+ /** Actions that were in scope but could not be applied (missing file, old-text not found). */
31
+ failed: Array<{
32
+ path: string;
33
+ reason: string;
34
+ }>;
35
+ }
36
+ export interface WorkerActionFs {
37
+ existsSync: typeof existsSync;
38
+ readFileSync: (path: string, encoding: "utf-8") => string;
39
+ writeFileSync: (path: string, content: string, encoding: "utf-8") => void;
40
+ mkdirSync: (path: string, options: {
41
+ recursive: true;
42
+ }) => unknown;
43
+ }
44
+ export declare function applyWorkerActions(args: {
45
+ actions: readonly WorkerAction[];
46
+ envelope: CapabilityEnvelope;
47
+ cwd: string;
48
+ fs?: WorkerActionFs;
49
+ }): AppliedActionsReport;
50
+ //# sourceMappingURL=worker-actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-actions.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA0C,MAAM,SAAS,CAAC;AAE7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,OAAO,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,EAAE,CAkB/D;AAED,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uFAAqF;IACrF,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,8FAA8F;IAC9F,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;IAC1D,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1E,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC;CACnE;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACxC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,cAAc,CAAC;CACpB,GAAG,oBAAoB,CAgCvB","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, relative, resolve } from \"node:path\";\nimport type { CapabilityEnvelope } from \"../autonomy/contracts.ts\";\nimport { isPathWithinEnvelope } from \"../autonomy/envelope-enforcement.ts\";\n\n/**\n * Code-writing workers (G2): the worker MODEL never touches the filesystem — it emits strict-JSON\n * actions, and this RUNNER-side module applies them deterministically through the capability\n * envelope's path scope. That keeps the structural-contract philosophy (a local model without\n * tool-calling templates can still write code) and makes enforcement execution-time, not\n * validation-only: an out-of-scope action is REFUSED with a reason, never silently dropped, and\n * refusals surface as blockers on the result.\n */\n\nexport interface WorkerAction {\n\top: \"write\" | \"edit\";\n\tpath: string;\n\t/** write: full file content. */\n\tcontent?: string;\n\t/** edit: exact string to replace (must occur in the file). */\n\told?: string;\n\t/** edit: replacement text. */\n\tnew?: string;\n}\n\nconst MAX_ACTIONS = 20;\nconst MAX_CONTENT_CHARS = 512 * 1024;\n\nexport function parseWorkerActions(raw: unknown): WorkerAction[] {\n\tif (!Array.isArray(raw)) return [];\n\tconst actions: WorkerAction[] = [];\n\tfor (const entry of raw.slice(0, MAX_ACTIONS)) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst record = entry as Record<string, unknown>;\n\t\tif (record.op !== \"write\" && record.op !== \"edit\") continue;\n\t\tif (typeof record.path !== \"string\" || record.path.length === 0) continue;\n\t\tif (record.op === \"write\") {\n\t\t\tif (typeof record.content !== \"string\" || record.content.length > MAX_CONTENT_CHARS) continue;\n\t\t\tactions.push({ op: \"write\", path: record.path, content: record.content });\n\t\t} else {\n\t\t\tif (typeof record.old !== \"string\" || record.old.length === 0) continue;\n\t\t\tif (typeof record.new !== \"string\" || record.new.length > MAX_CONTENT_CHARS) continue;\n\t\t\tactions.push({ op: \"edit\", path: record.path, old: record.old, new: record.new });\n\t\t}\n\t}\n\treturn actions;\n}\n\nexport interface AppliedActionsReport {\n\t/** Repo-relative paths actually changed. */\n\tchangedFiles: string[];\n\t/** Envelope-scope refusals (execution-time enforcement) — surfaced, never silent. */\n\trefused: Array<{ path: string; reason: string }>;\n\t/** Actions that were in scope but could not be applied (missing file, old-text not found). */\n\tfailed: Array<{ path: string; reason: string }>;\n}\n\nexport interface WorkerActionFs {\n\texistsSync: typeof existsSync;\n\treadFileSync: (path: string, encoding: \"utf-8\") => string;\n\twriteFileSync: (path: string, content: string, encoding: \"utf-8\") => void;\n\tmkdirSync: (path: string, options: { recursive: true }) => unknown;\n}\n\nconst realFs: WorkerActionFs = { existsSync, readFileSync, writeFileSync, mkdirSync };\n\nexport function applyWorkerActions(args: {\n\tactions: readonly WorkerAction[];\n\tenvelope: CapabilityEnvelope;\n\tcwd: string;\n\tfs?: WorkerActionFs;\n}): AppliedActionsReport {\n\tconst fileSystem = args.fs ?? realFs;\n\tconst report: AppliedActionsReport = { changedFiles: [], refused: [], failed: [] };\n\tfor (const action of args.actions) {\n\t\tif (!isPathWithinEnvelope(args.envelope, action.path, args.cwd)) {\n\t\t\treport.refused.push({ path: action.path, reason: `outside envelope ${args.envelope.id} path scope` });\n\t\t\tcontinue;\n\t\t}\n\t\tconst target = resolve(args.cwd, action.path);\n\t\tconst relativePath = relative(args.cwd, target);\n\t\ttry {\n\t\t\tif (action.op === \"write\") {\n\t\t\t\tfileSystem.mkdirSync(dirname(target), { recursive: true });\n\t\t\t\tfileSystem.writeFileSync(target, action.content ?? \"\", \"utf-8\");\n\t\t\t} else {\n\t\t\t\tif (!fileSystem.existsSync(target)) {\n\t\t\t\t\treport.failed.push({ path: action.path, reason: \"file does not exist\" });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst current = fileSystem.readFileSync(target, \"utf-8\");\n\t\t\t\tif (!action.old || !current.includes(action.old)) {\n\t\t\t\t\treport.failed.push({ path: action.path, reason: \"edit old-text not found in file\" });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfileSystem.writeFileSync(target, current.replace(action.old, action.new ?? \"\"), \"utf-8\");\n\t\t\t}\n\t\t\tif (!report.changedFiles.includes(relativePath)) report.changedFiles.push(relativePath);\n\t\t} catch (error) {\n\t\t\treport.failed.push({ path: action.path, reason: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn report;\n}\n"]}
@@ -0,0 +1,70 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, relative, resolve } from "node:path";
3
+ import { isPathWithinEnvelope } from "../autonomy/envelope-enforcement.js";
4
+ const MAX_ACTIONS = 20;
5
+ const MAX_CONTENT_CHARS = 512 * 1024;
6
+ export function parseWorkerActions(raw) {
7
+ if (!Array.isArray(raw))
8
+ return [];
9
+ const actions = [];
10
+ for (const entry of raw.slice(0, MAX_ACTIONS)) {
11
+ if (!entry || typeof entry !== "object")
12
+ continue;
13
+ const record = entry;
14
+ if (record.op !== "write" && record.op !== "edit")
15
+ continue;
16
+ if (typeof record.path !== "string" || record.path.length === 0)
17
+ continue;
18
+ if (record.op === "write") {
19
+ if (typeof record.content !== "string" || record.content.length > MAX_CONTENT_CHARS)
20
+ continue;
21
+ actions.push({ op: "write", path: record.path, content: record.content });
22
+ }
23
+ else {
24
+ if (typeof record.old !== "string" || record.old.length === 0)
25
+ continue;
26
+ if (typeof record.new !== "string" || record.new.length > MAX_CONTENT_CHARS)
27
+ continue;
28
+ actions.push({ op: "edit", path: record.path, old: record.old, new: record.new });
29
+ }
30
+ }
31
+ return actions;
32
+ }
33
+ const realFs = { existsSync, readFileSync, writeFileSync, mkdirSync };
34
+ export function applyWorkerActions(args) {
35
+ const fileSystem = args.fs ?? realFs;
36
+ const report = { changedFiles: [], refused: [], failed: [] };
37
+ for (const action of args.actions) {
38
+ if (!isPathWithinEnvelope(args.envelope, action.path, args.cwd)) {
39
+ report.refused.push({ path: action.path, reason: `outside envelope ${args.envelope.id} path scope` });
40
+ continue;
41
+ }
42
+ const target = resolve(args.cwd, action.path);
43
+ const relativePath = relative(args.cwd, target);
44
+ try {
45
+ if (action.op === "write") {
46
+ fileSystem.mkdirSync(dirname(target), { recursive: true });
47
+ fileSystem.writeFileSync(target, action.content ?? "", "utf-8");
48
+ }
49
+ else {
50
+ if (!fileSystem.existsSync(target)) {
51
+ report.failed.push({ path: action.path, reason: "file does not exist" });
52
+ continue;
53
+ }
54
+ const current = fileSystem.readFileSync(target, "utf-8");
55
+ if (!action.old || !current.includes(action.old)) {
56
+ report.failed.push({ path: action.path, reason: "edit old-text not found in file" });
57
+ continue;
58
+ }
59
+ fileSystem.writeFileSync(target, current.replace(action.old, action.new ?? ""), "utf-8");
60
+ }
61
+ if (!report.changedFiles.includes(relativePath))
62
+ report.changedFiles.push(relativePath);
63
+ }
64
+ catch (error) {
65
+ report.failed.push({ path: action.path, reason: error instanceof Error ? error.message : String(error) });
66
+ }
67
+ }
68
+ return report;
69
+ }
70
+ //# sourceMappingURL=worker-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-actions.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, relative, resolve } from \"node:path\";\nimport type { CapabilityEnvelope } from \"../autonomy/contracts.ts\";\nimport { isPathWithinEnvelope } from \"../autonomy/envelope-enforcement.ts\";\n\n/**\n * Code-writing workers (G2): the worker MODEL never touches the filesystem — it emits strict-JSON\n * actions, and this RUNNER-side module applies them deterministically through the capability\n * envelope's path scope. That keeps the structural-contract philosophy (a local model without\n * tool-calling templates can still write code) and makes enforcement execution-time, not\n * validation-only: an out-of-scope action is REFUSED with a reason, never silently dropped, and\n * refusals surface as blockers on the result.\n */\n\nexport interface WorkerAction {\n\top: \"write\" | \"edit\";\n\tpath: string;\n\t/** write: full file content. */\n\tcontent?: string;\n\t/** edit: exact string to replace (must occur in the file). */\n\told?: string;\n\t/** edit: replacement text. */\n\tnew?: string;\n}\n\nconst MAX_ACTIONS = 20;\nconst MAX_CONTENT_CHARS = 512 * 1024;\n\nexport function parseWorkerActions(raw: unknown): WorkerAction[] {\n\tif (!Array.isArray(raw)) return [];\n\tconst actions: WorkerAction[] = [];\n\tfor (const entry of raw.slice(0, MAX_ACTIONS)) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst record = entry as Record<string, unknown>;\n\t\tif (record.op !== \"write\" && record.op !== \"edit\") continue;\n\t\tif (typeof record.path !== \"string\" || record.path.length === 0) continue;\n\t\tif (record.op === \"write\") {\n\t\t\tif (typeof record.content !== \"string\" || record.content.length > MAX_CONTENT_CHARS) continue;\n\t\t\tactions.push({ op: \"write\", path: record.path, content: record.content });\n\t\t} else {\n\t\t\tif (typeof record.old !== \"string\" || record.old.length === 0) continue;\n\t\t\tif (typeof record.new !== \"string\" || record.new.length > MAX_CONTENT_CHARS) continue;\n\t\t\tactions.push({ op: \"edit\", path: record.path, old: record.old, new: record.new });\n\t\t}\n\t}\n\treturn actions;\n}\n\nexport interface AppliedActionsReport {\n\t/** Repo-relative paths actually changed. */\n\tchangedFiles: string[];\n\t/** Envelope-scope refusals (execution-time enforcement) — surfaced, never silent. */\n\trefused: Array<{ path: string; reason: string }>;\n\t/** Actions that were in scope but could not be applied (missing file, old-text not found). */\n\tfailed: Array<{ path: string; reason: string }>;\n}\n\nexport interface WorkerActionFs {\n\texistsSync: typeof existsSync;\n\treadFileSync: (path: string, encoding: \"utf-8\") => string;\n\twriteFileSync: (path: string, content: string, encoding: \"utf-8\") => void;\n\tmkdirSync: (path: string, options: { recursive: true }) => unknown;\n}\n\nconst realFs: WorkerActionFs = { existsSync, readFileSync, writeFileSync, mkdirSync };\n\nexport function applyWorkerActions(args: {\n\tactions: readonly WorkerAction[];\n\tenvelope: CapabilityEnvelope;\n\tcwd: string;\n\tfs?: WorkerActionFs;\n}): AppliedActionsReport {\n\tconst fileSystem = args.fs ?? realFs;\n\tconst report: AppliedActionsReport = { changedFiles: [], refused: [], failed: [] };\n\tfor (const action of args.actions) {\n\t\tif (!isPathWithinEnvelope(args.envelope, action.path, args.cwd)) {\n\t\t\treport.refused.push({ path: action.path, reason: `outside envelope ${args.envelope.id} path scope` });\n\t\t\tcontinue;\n\t\t}\n\t\tconst target = resolve(args.cwd, action.path);\n\t\tconst relativePath = relative(args.cwd, target);\n\t\ttry {\n\t\t\tif (action.op === \"write\") {\n\t\t\t\tfileSystem.mkdirSync(dirname(target), { recursive: true });\n\t\t\t\tfileSystem.writeFileSync(target, action.content ?? \"\", \"utf-8\");\n\t\t\t} else {\n\t\t\t\tif (!fileSystem.existsSync(target)) {\n\t\t\t\t\treport.failed.push({ path: action.path, reason: \"file does not exist\" });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst current = fileSystem.readFileSync(target, \"utf-8\");\n\t\t\t\tif (!action.old || !current.includes(action.old)) {\n\t\t\t\t\treport.failed.push({ path: action.path, reason: \"edit old-text not found in file\" });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfileSystem.writeFileSync(target, current.replace(action.old, action.new ?? \"\"), \"utf-8\");\n\t\t\t}\n\t\t\tif (!report.changedFiles.includes(relativePath)) report.changedFiles.push(relativePath);\n\t\t} catch (error) {\n\t\t\treport.failed.push({ path: action.path, reason: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn report;\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import type { GateOutcome, WorkerRequest, WorkerResult } from "../autonomy/contracts.ts";
2
2
  import type { LaneTerminalStatus } from "../autonomy/lane-tracker.ts";
3
+ import { type AppliedActionsReport, type WorkerAction } from "./worker-actions.ts";
3
4
  /**
4
5
  * Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->
5
6
  * parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.
@@ -10,6 +11,9 @@ import type { LaneTerminalStatus } from "../autonomy/lane-tracker.ts";
10
11
  */
11
12
  /** Static across calls so callers can use `cacheRetention: "short"`. */
12
13
  export declare const WORKER_LANE_SYSTEM_PROMPT: string;
14
+ /** Write-capable variant (G2): same contract plus a structured actions array — the model never
15
+ * touches the filesystem; the runner applies actions through the envelope's path scope. */
16
+ export declare const WORKER_WRITE_LANE_SYSTEM_PROMPT: string;
13
17
  export interface WorkerCompletion {
14
18
  text: string;
15
19
  costUsd: number;
@@ -33,6 +37,10 @@ export interface WorkerRunnerOptions {
33
37
  }) => Promise<WorkerCompletion>;
34
38
  signal?: AbortSignal;
35
39
  now?: () => string;
40
+ /** Enables the WRITE lane: only honored when the request envelope grants "write_files". The
41
+ * runner applies the worker's structured actions through the envelope path scope; refusals
42
+ * and failures become blockers, never silent drops. */
43
+ applyActions?: (actions: readonly WorkerAction[]) => AppliedActionsReport;
36
44
  }
37
45
  export interface WorkerRunOutcome {
38
46
  result: WorkerResult;
@@ -52,6 +60,7 @@ export interface ParsedWorkerOutput {
52
60
  summary: string;
53
61
  confidence?: number;
54
62
  }>;
63
+ actions: WorkerAction[];
55
64
  }
56
65
  export declare function parseWorkerOutput(text: string): ParsedWorkerOutput | undefined;
57
66
  export declare function runWorker(options: WorkerRunnerOptions): Promise<WorkerRunOutcome>;
@@ -1 +1 @@
1
- {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAItE;;;;;;;GAOG;AAEH,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,QAO1B,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,8FAA8F;IAC9F,UAAU,EAAE,WAAW,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAEpE;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CA0C9E;AAgDD,wBAAsB,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqFvF","sourcesContent":["import { runBoundedCompletion } from \"../autonomy/bounded-completion.ts\";\nimport type { EvidenceRef, Finding, GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\nimport { createEvidenceBundle } from \"../research/evidence-bundle.ts\";\nimport { validateWorkerResult } from \"./worker-result.ts\";\n\n/**\n * Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->\n * parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.\n *\n * Slice scope: scout (read-only) workers only — the completion receives text prompts, no tools, so\n * `changedFiles` is always empty. Code-writing workers stay out until a real execution envelope\n * enforces path scope at tool level. Worker output is untrusted until the parent verifies it.\n */\n\n/** Static across calls so callers can use `cacheRetention: \"short\"`. */\nexport const WORKER_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded read-only scout worker delegated one task by a coding agent.\",\n\t\"You cannot run tools or change files; produce your best analysis of the delegated task.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you concluded>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[\"<why you are stuck>\"],\"findings\":[{\"summary\":\"<one concrete finding>\",\"confidence\":<0..1>}]}',\n\t'Use status \"blocked\" with blockers only when the task cannot be answered from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\nexport interface WorkerCompletion {\n\ttext: string;\n\tcostUsd: number;\n\tstopReason: string;\n}\n\nexport interface WorkerRunnerOptions {\n\trequest: WorkerRequest;\n\t/** Budget for this delegation; a post-hoc breach marks the lane budget_exhausted. */\n\tmaxUsd: number;\n\t/** Wall-clock budget in milliseconds; 0 disables. */\n\tmaxWallClockMs: number;\n\t/**\n\t * Pre-allocated spawned-usage report id. Always stamped on the result so parent validation can\n\t * enforce the cost-visibility invariant (a completed result without a usage report is blocked).\n\t */\n\tusageReportId: string;\n\tcomplete: (args: { systemPrompt: string; userPrompt: string; signal?: AbortSignal }) => Promise<WorkerCompletion>;\n\tsignal?: AbortSignal;\n\tnow?: () => string;\n}\n\nexport interface WorkerRunOutcome {\n\tresult: WorkerResult;\n\t/** Parent-review verdict from {@link validateWorkerResult}; worker output stays untrusted. */\n\tacceptance: GateOutcome;\n\taccepted: boolean;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}\n\nexport function buildWorkerUserPrompt(request: WorkerRequest): string {\n\treturn `Delegated task: ${request.instructions}`;\n}\n\nexport interface ParsedWorkerOutput {\n\tsummary: string;\n\tstatus: \"completed\" | \"blocked\";\n\tblockers: string[];\n\tfindings: Array<{ summary: string; confidence?: number }>;\n}\n\nexport function parseWorkerOutput(text: string): ParsedWorkerOutput | undefined {\n\tconst trimmed = text.trim();\n\tconst candidates: string[] = [trimmed];\n\tconst fenced = /```(?:json)?\\s*([\\s\\S]*?)```/.exec(trimmed);\n\tif (fenced?.[1]) candidates.push(fenced[1].trim());\n\tconst start = trimmed.indexOf(\"{\");\n\tconst end = trimmed.lastIndexOf(\"}\");\n\tif (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));\n\n\tfor (const candidate of candidates) {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(candidate);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tconst summary = record.summary;\n\t\tif (typeof summary !== \"string\" || summary.trim().length === 0) continue;\n\n\t\tconst status = record.status === \"blocked\" ? \"blocked\" : \"completed\";\n\t\tconst blockers = Array.isArray(record.blockers)\n\t\t\t? record.blockers.filter((blocker): blocker is string => typeof blocker === \"string\" && blocker.length > 0)\n\t\t\t: [];\n\t\tconst findings: Array<{ summary: string; confidence?: number }> = [];\n\t\tif (Array.isArray(record.findings)) {\n\t\t\tfor (const item of record.findings) {\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n\t\t\t\tconst findingSummary = (item as { summary?: unknown }).summary;\n\t\t\t\tif (typeof findingSummary !== \"string\" || findingSummary.trim().length === 0) continue;\n\t\t\t\tconst confidenceRaw = (item as { confidence?: unknown }).confidence;\n\t\t\t\tconst confidence =\n\t\t\t\t\ttypeof confidenceRaw === \"number\" && Number.isFinite(confidenceRaw)\n\t\t\t\t\t\t? Math.min(Math.max(confidenceRaw, 0), 1)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tfindings.push({ summary: findingSummary.trim(), confidence });\n\t\t\t}\n\t\t}\n\t\treturn { summary: summary.trim(), status, blockers, findings };\n\t}\n\treturn undefined;\n}\n\nfunction buildWorkerEvidence(request: WorkerRequest, findings: ParsedWorkerOutput[\"findings\"]) {\n\tif (findings.length === 0) return undefined;\n\tconst instructionsRef: EvidenceRef = {\n\t\tid: \"src-instructions\",\n\t\tkind: \"user\",\n\t\ttitle: \"Delegated task instructions\",\n\t\ttrusted: true,\n\t\texcerpt: request.instructions.slice(0, 2000),\n\t};\n\tconst synthesisRef: EvidenceRef = {\n\t\tid: \"src-worker\",\n\t\tkind: \"tool\",\n\t\ttitle: \"Scout-worker synthesis\",\n\t\ttrusted: false,\n\t};\n\tconst bundleFindings: Finding[] = findings.map((finding, index) => ({\n\t\tid: `finding-${index + 1}`,\n\t\tsummary: finding.summary,\n\t\tevidenceIds: [synthesisRef.id],\n\t\t...(finding.confidence !== undefined ? { confidence: finding.confidence } : {}),\n\t}));\n\treturn createEvidenceBundle({\n\t\tquery: `worker:${request.id}`,\n\t\tsources: [instructionsRef, synthesisRef],\n\t\tfindings: bundleFindings,\n\t});\n}\n\nfunction finishOutcome(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}): WorkerRunOutcome {\n\tconst acceptance = validateWorkerResult({ request: args.request, result: args.result });\n\treturn {\n\t\tresult: args.result,\n\t\tacceptance,\n\t\taccepted: acceptance.outcome === \"allow\",\n\t\tlaneStatus: args.laneStatus,\n\t\treasonCode: args.reasonCode,\n\t\tcostUsd: args.costUsd,\n\t};\n}\n\nexport async function runWorker(options: WorkerRunnerOptions): Promise<WorkerRunOutcome> {\n\tconst now = options.now ?? (() => new Date().toISOString());\n\tconst baseResult = {\n\t\trequestId: options.request.id,\n\t\tchangedFiles: [] as string[],\n\t\tusageReportId: options.usageReportId,\n\t\tcreatedAt: now(),\n\t};\n\n\tconst bounded = await runBoundedCompletion({\n\t\tmaxWallClockMs: options.maxWallClockMs,\n\t\tsignal: options.signal,\n\t\texecute: (signal) =>\n\t\t\toptions.complete({\n\t\t\t\tsystemPrompt: WORKER_LANE_SYSTEM_PROMPT,\n\t\t\t\tuserPrompt: buildWorkerUserPrompt(options.request),\n\t\t\t\tsignal,\n\t\t\t}),\n\t});\n\tconst costUsd = bounded.completion?.costUsd ?? 0;\n\n\tif (bounded.failure) {\n\t\tconst cancelled = bounded.failure.status === \"canceled\" || bounded.failure.status === \"timeout\";\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: {\n\t\t\t\t...baseResult,\n\t\t\t\tstatus: cancelled ? \"cancelled\" : \"failed\",\n\t\t\t\tsummary: `Worker did not complete: ${bounded.failure.reasonCode}`,\n\t\t\t},\n\t\t\tlaneStatus: bounded.failure.status,\n\t\t\treasonCode: bounded.failure.reasonCode,\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst completion = bounded.completion;\n\tif (!completion || completion.stopReason === \"error\" || completion.stopReason === \"aborted\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker model call failed.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"model_error\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst parsed = parseWorkerOutput(completion.text);\n\tif (!parsed) {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker output was not valid structured JSON.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"unparseable_output\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst evidence = buildWorkerEvidence(options.request, parsed.findings);\n\tconst result: WorkerResult = {\n\t\t...baseResult,\n\t\tstatus: parsed.status === \"blocked\" || parsed.blockers.length > 0 ? \"blocked\" : \"completed\",\n\t\tsummary: parsed.summary,\n\t\t...(parsed.blockers.length > 0 ? { blockers: parsed.blockers } : {}),\n\t\t...(evidence ? { evidence } : {}),\n\t};\n\n\tif (result.status === \"blocked\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult,\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"worker_blocked\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst overBudget = options.maxUsd > 0 && costUsd > options.maxUsd;\n\treturn finishOutcome({\n\t\trequest: options.request,\n\t\tresult,\n\t\tlaneStatus: overBudget ? \"budget_exhausted\" : \"succeeded\",\n\t\treasonCode: overBudget ? \"cost_budget_exceeded\" : \"worker_completed\",\n\t\tcostUsd,\n\t});\n}\n"]}
1
+ {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,KAAK,oBAAoB,EAAsB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGvG;;;;;;;GAOG;AAEH,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,QAO1B,CAAC;AAEb;2FAC2F;AAC3F,eAAO,MAAM,+BAA+B,QAQhC,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;2DAEuD;IACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,oBAAoB,CAAC;CAC1E;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,8FAA8F;IAC9F,UAAU,EAAE,WAAW,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAEpE;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CA0C9E;AAgDD,wBAAsB,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4GvF","sourcesContent":["import { runBoundedCompletion } from \"../autonomy/bounded-completion.ts\";\nimport type { EvidenceRef, Finding, GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\nimport { createEvidenceBundle } from \"../research/evidence-bundle.ts\";\nimport { type AppliedActionsReport, parseWorkerActions, type WorkerAction } from \"./worker-actions.ts\";\nimport { validateWorkerResult } from \"./worker-result.ts\";\n\n/**\n * Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->\n * parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.\n *\n * Slice scope: scout (read-only) workers only — the completion receives text prompts, no tools, so\n * `changedFiles` is always empty. Code-writing workers stay out until a real execution envelope\n * enforces path scope at tool level. Worker output is untrusted until the parent verifies it.\n */\n\n/** Static across calls so callers can use `cacheRetention: \"short\"`. */\nexport const WORKER_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded read-only scout worker delegated one task by a coding agent.\",\n\t\"You cannot run tools or change files; produce your best analysis of the delegated task.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you concluded>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[\"<why you are stuck>\"],\"findings\":[{\"summary\":\"<one concrete finding>\",\"confidence\":<0..1>}]}',\n\t'Use status \"blocked\" with blockers only when the task cannot be answered from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\n/** Write-capable variant (G2): same contract plus a structured actions array — the model never\n * touches the filesystem; the runner applies actions through the envelope's path scope. */\nexport const WORKER_WRITE_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded code-writing worker delegated one task by a coding agent.\",\n\t\"You cannot run tools; you CHANGE FILES only by listing actions the runner applies for you.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you did>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[],\"findings\":[{\"summary\":\"<finding>\",\"confidence\":<0..1>}],\"actions\":[{\"op\":\"write\",\"path\":\"<relative path>\",\"content\":\"<full file content>\"},{\"op\":\"edit\",\"path\":\"<relative path>\",\"old\":\"<exact text>\",\"new\":\"<replacement>\"}]}',\n\t\"Only touch paths inside your delegated scope. Keep edits minimal and exact.\",\n\t'Use status \"blocked\" with blockers when the task cannot be done from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\nexport interface WorkerCompletion {\n\ttext: string;\n\tcostUsd: number;\n\tstopReason: string;\n}\n\nexport interface WorkerRunnerOptions {\n\trequest: WorkerRequest;\n\t/** Budget for this delegation; a post-hoc breach marks the lane budget_exhausted. */\n\tmaxUsd: number;\n\t/** Wall-clock budget in milliseconds; 0 disables. */\n\tmaxWallClockMs: number;\n\t/**\n\t * Pre-allocated spawned-usage report id. Always stamped on the result so parent validation can\n\t * enforce the cost-visibility invariant (a completed result without a usage report is blocked).\n\t */\n\tusageReportId: string;\n\tcomplete: (args: { systemPrompt: string; userPrompt: string; signal?: AbortSignal }) => Promise<WorkerCompletion>;\n\tsignal?: AbortSignal;\n\tnow?: () => string;\n\t/** Enables the WRITE lane: only honored when the request envelope grants \"write_files\". The\n\t * runner applies the worker's structured actions through the envelope path scope; refusals\n\t * and failures become blockers, never silent drops. */\n\tapplyActions?: (actions: readonly WorkerAction[]) => AppliedActionsReport;\n}\n\nexport interface WorkerRunOutcome {\n\tresult: WorkerResult;\n\t/** Parent-review verdict from {@link validateWorkerResult}; worker output stays untrusted. */\n\tacceptance: GateOutcome;\n\taccepted: boolean;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}\n\nexport function buildWorkerUserPrompt(request: WorkerRequest): string {\n\treturn `Delegated task: ${request.instructions}`;\n}\n\nexport interface ParsedWorkerOutput {\n\tsummary: string;\n\tstatus: \"completed\" | \"blocked\";\n\tblockers: string[];\n\tfindings: Array<{ summary: string; confidence?: number }>;\n\tactions: WorkerAction[];\n}\n\nexport function parseWorkerOutput(text: string): ParsedWorkerOutput | undefined {\n\tconst trimmed = text.trim();\n\tconst candidates: string[] = [trimmed];\n\tconst fenced = /```(?:json)?\\s*([\\s\\S]*?)```/.exec(trimmed);\n\tif (fenced?.[1]) candidates.push(fenced[1].trim());\n\tconst start = trimmed.indexOf(\"{\");\n\tconst end = trimmed.lastIndexOf(\"}\");\n\tif (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));\n\n\tfor (const candidate of candidates) {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(candidate);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tconst summary = record.summary;\n\t\tif (typeof summary !== \"string\" || summary.trim().length === 0) continue;\n\n\t\tconst status = record.status === \"blocked\" ? \"blocked\" : \"completed\";\n\t\tconst blockers = Array.isArray(record.blockers)\n\t\t\t? record.blockers.filter((blocker): blocker is string => typeof blocker === \"string\" && blocker.length > 0)\n\t\t\t: [];\n\t\tconst findings: Array<{ summary: string; confidence?: number }> = [];\n\t\tif (Array.isArray(record.findings)) {\n\t\t\tfor (const item of record.findings) {\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n\t\t\t\tconst findingSummary = (item as { summary?: unknown }).summary;\n\t\t\t\tif (typeof findingSummary !== \"string\" || findingSummary.trim().length === 0) continue;\n\t\t\t\tconst confidenceRaw = (item as { confidence?: unknown }).confidence;\n\t\t\t\tconst confidence =\n\t\t\t\t\ttypeof confidenceRaw === \"number\" && Number.isFinite(confidenceRaw)\n\t\t\t\t\t\t? Math.min(Math.max(confidenceRaw, 0), 1)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tfindings.push({ summary: findingSummary.trim(), confidence });\n\t\t\t}\n\t\t}\n\t\treturn { summary: summary.trim(), status, blockers, findings, actions: parseWorkerActions(record.actions) };\n\t}\n\treturn undefined;\n}\n\nfunction buildWorkerEvidence(request: WorkerRequest, findings: ParsedWorkerOutput[\"findings\"]) {\n\tif (findings.length === 0) return undefined;\n\tconst instructionsRef: EvidenceRef = {\n\t\tid: \"src-instructions\",\n\t\tkind: \"user\",\n\t\ttitle: \"Delegated task instructions\",\n\t\ttrusted: true,\n\t\texcerpt: request.instructions.slice(0, 2000),\n\t};\n\tconst synthesisRef: EvidenceRef = {\n\t\tid: \"src-worker\",\n\t\tkind: \"tool\",\n\t\ttitle: \"Scout-worker synthesis\",\n\t\ttrusted: false,\n\t};\n\tconst bundleFindings: Finding[] = findings.map((finding, index) => ({\n\t\tid: `finding-${index + 1}`,\n\t\tsummary: finding.summary,\n\t\tevidenceIds: [synthesisRef.id],\n\t\t...(finding.confidence !== undefined ? { confidence: finding.confidence } : {}),\n\t}));\n\treturn createEvidenceBundle({\n\t\tquery: `worker:${request.id}`,\n\t\tsources: [instructionsRef, synthesisRef],\n\t\tfindings: bundleFindings,\n\t});\n}\n\nfunction finishOutcome(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}): WorkerRunOutcome {\n\tconst acceptance = validateWorkerResult({ request: args.request, result: args.result });\n\treturn {\n\t\tresult: args.result,\n\t\tacceptance,\n\t\taccepted: acceptance.outcome === \"allow\",\n\t\tlaneStatus: args.laneStatus,\n\t\treasonCode: args.reasonCode,\n\t\tcostUsd: args.costUsd,\n\t};\n}\n\nexport async function runWorker(options: WorkerRunnerOptions): Promise<WorkerRunOutcome> {\n\tconst now = options.now ?? (() => new Date().toISOString());\n\tconst baseResult = {\n\t\trequestId: options.request.id,\n\t\tchangedFiles: [] as string[],\n\t\tusageReportId: options.usageReportId,\n\t\tcreatedAt: now(),\n\t};\n\n\t// The WRITE lane requires BOTH the envelope grant and a caller-supplied applier — either\n\t// alone keeps the read-only scout contract byte-for-byte.\n\tconst writeCapable =\n\t\toptions.request.envelope.capabilities.includes(\"write_files\") && options.applyActions !== undefined;\n\n\tconst bounded = await runBoundedCompletion({\n\t\tmaxWallClockMs: options.maxWallClockMs,\n\t\tsignal: options.signal,\n\t\texecute: (signal) =>\n\t\t\toptions.complete({\n\t\t\t\tsystemPrompt: writeCapable ? WORKER_WRITE_LANE_SYSTEM_PROMPT : WORKER_LANE_SYSTEM_PROMPT,\n\t\t\t\tuserPrompt: buildWorkerUserPrompt(options.request),\n\t\t\t\tsignal,\n\t\t\t}),\n\t});\n\tconst costUsd = bounded.completion?.costUsd ?? 0;\n\n\tif (bounded.failure) {\n\t\tconst cancelled = bounded.failure.status === \"canceled\" || bounded.failure.status === \"timeout\";\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: {\n\t\t\t\t...baseResult,\n\t\t\t\tstatus: cancelled ? \"cancelled\" : \"failed\",\n\t\t\t\tsummary: `Worker did not complete: ${bounded.failure.reasonCode}`,\n\t\t\t},\n\t\t\tlaneStatus: bounded.failure.status,\n\t\t\treasonCode: bounded.failure.reasonCode,\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst completion = bounded.completion;\n\tif (!completion || completion.stopReason === \"error\" || completion.stopReason === \"aborted\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker model call failed.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"model_error\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst parsed = parseWorkerOutput(completion.text);\n\tif (!parsed) {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker output was not valid structured JSON.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"unparseable_output\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst evidence = buildWorkerEvidence(options.request, parsed.findings);\n\tlet changedFiles: string[] = [];\n\tconst actionBlockers: string[] = [];\n\tif (writeCapable && parsed.status !== \"blocked\" && parsed.actions.length > 0 && options.applyActions) {\n\t\t// Runner-side application through the envelope path scope: refusals and failures are\n\t\t// surfaced as blockers so a partially-applied change can never look like clean success.\n\t\tconst applied = options.applyActions(parsed.actions);\n\t\tchangedFiles = applied.changedFiles;\n\t\tfor (const refusal of applied.refused) {\n\t\t\tactionBlockers.push(`action refused (${refusal.path}): ${refusal.reason}`);\n\t\t}\n\t\tfor (const failure of applied.failed) {\n\t\t\tactionBlockers.push(`action failed (${failure.path}): ${failure.reason}`);\n\t\t}\n\t} else if (!writeCapable && parsed.actions.length > 0) {\n\t\tactionBlockers.push(\"worker emitted file actions without a write_files envelope grant; nothing was applied\");\n\t}\n\tconst allBlockers = [...parsed.blockers, ...actionBlockers];\n\tconst result: WorkerResult = {\n\t\t...baseResult,\n\t\tchangedFiles,\n\t\tstatus: parsed.status === \"blocked\" || allBlockers.length > 0 ? \"blocked\" : \"completed\",\n\t\tsummary: parsed.summary,\n\t\t...(allBlockers.length > 0 ? { blockers: allBlockers } : {}),\n\t\t...(evidence ? { evidence } : {}),\n\t};\n\n\tif (result.status === \"blocked\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tresult,\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"worker_blocked\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst overBudget = options.maxUsd > 0 && costUsd > options.maxUsd;\n\treturn finishOutcome({\n\t\trequest: options.request,\n\t\tresult,\n\t\tlaneStatus: overBudget ? \"budget_exhausted\" : \"succeeded\",\n\t\treasonCode: overBudget ? \"cost_budget_exceeded\" : \"worker_completed\",\n\t\tcostUsd,\n\t});\n}\n"]}