@buildaharness/aielia 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +832 -0
  2. package/dist/action-approval-service.d.ts +52 -0
  3. package/dist/action-snapshot.d.ts +173 -0
  4. package/dist/action-tools.d.ts +38 -0
  5. package/dist/agent-loop.d.ts +372 -0
  6. package/dist/answer-claim.d.ts +51 -0
  7. package/dist/ask-clarification-service.d.ts +70 -0
  8. package/dist/ask-mode-flag.d.ts +25 -0
  9. package/dist/ask-response-format.d.ts +8 -0
  10. package/dist/assistant-session.d.ts +267 -0
  11. package/dist/assistant-source.d.ts +14 -0
  12. package/dist/assistant-types.d.ts +158 -0
  13. package/dist/assistant.d.ts +419 -0
  14. package/dist/batch-list-detector.d.ts +15 -0
  15. package/dist/batch-research-fixtures.d.ts +50 -0
  16. package/dist/benchmark-injected-failure.d.ts +27 -0
  17. package/dist/claude-cli-llm-client.d.ts +139 -0
  18. package/dist/claude-cli-prompt.d.ts +86 -0
  19. package/dist/cli-config.d.ts +27 -0
  20. package/dist/cli-session-D0uLdT2A.js +7863 -0
  21. package/dist/cli-session-D0uLdT2A.js.map +1 -0
  22. package/dist/cli-session.d.ts +63 -0
  23. package/dist/cli.d.ts +65 -0
  24. package/dist/cli.js +1693 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +175 -0
  27. package/dist/contradiction-checker.d.ts +27 -0
  28. package/dist/debug-log.d.ts +16 -0
  29. package/dist/decomposition-classifier.d.ts +38 -0
  30. package/dist/doctor-checks.d.ts +8 -0
  31. package/dist/email-smtp.d.ts +19 -0
  32. package/dist/email.d.ts +60 -0
  33. package/dist/error-classifier.d.ts +13 -0
  34. package/dist/execution-mode.d.ts +85 -0
  35. package/dist/fact-extraction.d.ts +143 -0
  36. package/dist/failure-mode-matcher.d.ts +32 -0
  37. package/dist/file-tools-mcp-server.mjs +1027 -0
  38. package/dist/file-tools.d.ts +179 -0
  39. package/dist/first-run.d.ts +22 -0
  40. package/dist/harness-bridge.d.ts +103 -0
  41. package/dist/index.d.ts +58 -0
  42. package/dist/index.js +212 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/ink-test-render.d.ts +24 -0
  45. package/dist/lexical/patterns/batch-list-markers.json +8 -0
  46. package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
  47. package/dist/lexical/patterns/coding-fact-markers.json +8 -0
  48. package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
  49. package/dist/lexical/patterns/enumeration-markers.json +20 -0
  50. package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
  51. package/dist/lexical/patterns/fact-markers.json +16 -0
  52. package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
  53. package/dist/lexical/patterns/injection-patterns.json +20 -0
  54. package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
  55. package/dist/lexical/patterns/plan-mode-markers.json +10 -0
  56. package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
  57. package/dist/lexical/patterns/risk-patterns.json +122 -0
  58. package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
  59. package/dist/lexical/patterns/task-cancel-markers.json +22 -0
  60. package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
  61. package/dist/lexical/patterns/template-keywords.json +20 -0
  62. package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
  63. package/dist/lexical/patterns/tool-yield-markers.json +22 -0
  64. package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
  65. package/dist/lexical/patterns.d.ts +64 -0
  66. package/dist/memory-service.d.ts +219 -0
  67. package/dist/model-pricing.d.ts +19 -0
  68. package/dist/network-containment.d.ts +21 -0
  69. package/dist/node-config-store.d.ts +29 -0
  70. package/dist/node-display-names.d.ts +26 -0
  71. package/dist/node-fs-backend.d.ts +8 -0
  72. package/dist/non-interactive-mode.d.ts +22 -0
  73. package/dist/one-loop-flag.d.ts +38 -0
  74. package/dist/plan-approval-service.d.ts +62 -0
  75. package/dist/plan-builder.d.ts +32 -0
  76. package/dist/plan-drafting-service.d.ts +87 -0
  77. package/dist/plan-drafting.d.ts +58 -0
  78. package/dist/plan-mode-flag.d.ts +34 -0
  79. package/dist/plan-service.d.ts +61 -0
  80. package/dist/plan-sketch-service.d.ts +24 -0
  81. package/dist/plan-sketch.d.ts +20 -0
  82. package/dist/plan-store.d.ts +232 -0
  83. package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
  84. package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
  85. package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
  86. package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
  87. package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
  88. package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
  89. package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
  90. package/dist/plan-templates/index.d.ts +33 -0
  91. package/dist/plan-verification.d.ts +31 -0
  92. package/dist/reminder-tools.d.ts +13 -0
  93. package/dist/response-service.d.ts +73 -0
  94. package/dist/review-checker.d.ts +22 -0
  95. package/dist/risk-classifier.d.ts +7 -0
  96. package/dist/scripted-llm-client.d.ts +37 -0
  97. package/dist/semantic-criterion-coverage.d.ts +30 -0
  98. package/dist/shell-executor.d.ts +14 -0
  99. package/dist/shell-tools.d.ts +84 -0
  100. package/dist/spend-cap.d.ts +41 -0
  101. package/dist/spike.d.ts +1 -0
  102. package/dist/spike.js +18 -0
  103. package/dist/spike.js.map +1 -0
  104. package/dist/supervisor-decider.d.ts +3 -0
  105. package/dist/system-prompt.d.ts +8 -0
  106. package/dist/task-mapping.d.ts +42 -0
  107. package/dist/tool-control-plane.d.ts +67 -0
  108. package/dist/tool-policy.d.ts +45 -0
  109. package/dist/tool-step.d.ts +23 -0
  110. package/dist/tool-yield-classifier.d.ts +7 -0
  111. package/dist/trace-events.d.ts +126 -0
  112. package/dist/transcript-compaction.d.ts +13 -0
  113. package/dist/trust-tagging.d.ts +38 -0
  114. package/dist/tui-app-DxLPSGh2.js +481 -0
  115. package/dist/tui-app-DxLPSGh2.js.map +1 -0
  116. package/dist/tui-app.d.ts +125 -0
  117. package/dist/tui-input.d.ts +51 -0
  118. package/dist/tui-mode-flag.d.ts +36 -0
  119. package/dist/tui-output-capture.d.ts +30 -0
  120. package/dist/tui-spike.d.ts +1 -0
  121. package/dist/turn-intent-classifier.d.ts +96 -0
  122. package/dist/turn-interpreter.d.ts +71 -0
  123. package/dist/turn-policy.d.ts +45 -0
  124. package/dist/web-fetch-core.d.ts +65 -0
  125. package/dist/web-search-provider.d.ts +12 -0
  126. package/dist/web-tools.d.ts +26 -0
  127. package/package.json +69 -0
@@ -0,0 +1,14 @@
1
+ import { ShellCommandExecutor } from './shell-tools.js';
2
+ /**
3
+ * Actually runs a previously staged, already-sandboxed command — the real child_process.spawn call,
4
+ * invoked only at approval time via file-tools.ts's applyPendingAction(..., { executeShell }). `cwd`
5
+ * is pinned to the staged (already-validated) path; `env` is reduced to an explicit allowlist so a
6
+ * secret like ASSISTANT_PROXY_TOKEN/ANTHROPIC_API_KEY can't leak into the command's environment,
7
+ * plus network-containment.ts's loopback-only proxy forced via HTTP(S)_PROXY (Decision 6 — see
8
+ * networkContainmentEnv's doc comment). A hard timeout SIGKILLs the whole process group (not just
9
+ * the immediate child — `detached: true` + a negative-pid kill reaches anything the shell itself
10
+ * spawned) rather than leaving it running. A non-zero exit code is not a thrown error — it's
11
+ * reported normally, same as a real shell; only a spawn failure (e.g. the shell itself couldn't
12
+ * start) throws.
13
+ */
14
+ export declare const runApprovedShellCommand: ShellCommandExecutor;
@@ -0,0 +1,84 @@
1
+ import { FsBackend, ToolDefinition } from '@buildaharness/runtime';
2
+ import { ShellExecutionResult } from './file-tools.js';
3
+ /**
4
+ * Executes a previously staged, already-sandboxed command for real — see shell-executor.ts's
5
+ * runApprovedShellCommand for the Node implementation. Typed here (not in shell-executor.ts)
6
+ * so ShellToolsContext can reference it without this module — which assistant.ts/index.ts
7
+ * import unconditionally, including into the browser build — ever importing node:child_process.
8
+ */
9
+ export type ShellCommandExecutor = (command: string, cwd: string, options?: {
10
+ timeoutMs?: number;
11
+ maxOutputBytes?: number;
12
+ networkAllowlist?: string[];
13
+ }) => Promise<ShellExecutionResult>;
14
+ /**
15
+ * Heuristic-only: flags a command whose text contains a literal `..` parent-directory path
16
+ * segment (e.g. `cd ..`, `mkdir ../foo`, `cat ../../etc/passwd`). Unlike write_file, there is no
17
+ * real filesystem containment for run_shell_command once approved — shell-executor.ts's
18
+ * runApprovedShellCommand validates only that the *starting* `cwd` resolves inside the workspace
19
+ * (assertRealPathInWorkspace below); the command text itself then runs with the process's real
20
+ * OS-level filesystem access, so `cd ..` or a `../`-relative path genuinely escapes the workspace
21
+ * root on disk (confirmed live — see the conv06 batch finding this heuristic exists to surface).
22
+ * This exists purely to append an honest heads-up to the approval prompt so a human approver
23
+ * isn't misled by write_file's "sandboxed workspace" framing into assuming the same containment
24
+ * applies here. It is NOT a guard: it doesn't block anything, and it cannot catch every escape
25
+ * vector (absolute paths, symlinks, `cd $(pwd)/..`, indirection through env vars, etc.) — treat a
26
+ * `false` result as "this particular heuristic didn't fire," not "this command is contained."
27
+ */
28
+ export declare function commandMayLeaveWorkspace(command: string): boolean;
29
+ /**
30
+ * Heuristic-only: flags a command that looks like it makes an outbound HTTP(S) request (curl,
31
+ * wget, or a bare http(s):// URL), the shape network-containment.ts's proxy actually intercepts.
32
+ * Used to decide whether an executed command's output needs an explicit note that a deny-all
33
+ * network allowlist (the default — see AssistantConfig.shellNetworkAllowlist's doc comment)
34
+ * turns any such request into an immediate local 403, indistinguishable in the raw output from a
35
+ * real server response — found live (conv B, 2026-08-18 batch): without this, the LLM call that
36
+ * synthesizes a reply from the raw output has no way to know containment was even in play (it
37
+ * never sees run_shell_command's tool description, only the command + its output) and reliably
38
+ * misattributes the block to the destination server. A tool-description-only fix does not reach
39
+ * this synthesis call, so this deterministic check is required in addition to it.
40
+ */
41
+ export declare function commandLooksLikeNetworkRequest(command: string): boolean;
42
+ export declare const RUN_SHELL_COMMAND_TOOL: ToolDefinition;
43
+ export declare const SHELL_TOOLS: ToolDefinition[];
44
+ /** Everything executeShellTool needs to validate + stage a proposal — no execution capability required. */
45
+ export interface ShellStagingContext {
46
+ backend: FsBackend;
47
+ workspaceRoot: string;
48
+ }
49
+ /**
50
+ * Everything PersonalAssistant needs to both stage and, once approved, actually apply a shell
51
+ * action. `executeCommand` is required (not just an optional extra) because assistant.ts itself
52
+ * never imports node:child_process — it's bundled into the browser build (via index.ts) too, so
53
+ * the real Node implementation (shell-executor.ts's runApprovedShellCommand) is only ever wired
54
+ * in by a Node-only caller (cli.ts), exactly like node-fs-backend.ts already is.
55
+ */
56
+ export interface ShellToolsContext extends ShellStagingContext {
57
+ /** Hard timeout for an approved command, in ms. Passed through to executeCommand at apply time. Default 30000. */
58
+ timeoutMs?: number;
59
+ /**
60
+ * Hostnames an approved command's HTTP(S)_PROXY traffic may reach (exact match or subdomain) —
61
+ * see network-containment.ts and shell-executor.ts's networkContainmentEnv. Passed through to
62
+ * executeCommand at apply time, same as timeoutMs. Undefined/empty denies all network access,
63
+ * the safe default (Decision 6, plans/lexical_functions_hardening_plan.html Phase 4).
64
+ */
65
+ networkAllowlist?: string[];
66
+ executeCommand: ShellCommandExecutor;
67
+ }
68
+ export type ShellToolResult = {
69
+ kind: 'staged_shell';
70
+ id: string;
71
+ command: string;
72
+ cwd: string;
73
+ };
74
+ /**
75
+ * Executes run_shell_command by name. Never spawns anything itself — only stages, exactly like
76
+ * write_file does for file-tools, every time — no cached-result shortcut, since a command's real
77
+ * output can change from one run to the next regardless of whether its text is identical.
78
+ *
79
+ * The cwd validation above and the stagePendingAction call below both run unconditionally on this
80
+ * call's own concrete (command, cwd) arguments, regardless of what risk-classifier.ts concluded
81
+ * about the user's message text — see stagePendingAction's doc comment in file-tools.ts for the
82
+ * general "gate on the concrete tool call, not the free text" principle this follows.
83
+ */
84
+ export declare function executeShellTool(ctx: ShellStagingContext, toolName: string, input: Record<string, unknown>): Promise<ShellToolResult>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Cross-turn, cross-session spend enforcement — the review's §5.2 finding was that `/cost`
3
+ * reports usage but nothing ever refuses a turn over it. Pure math/formatting only; PersonalAssistant
4
+ * (assistant.ts) owns the actual persisted SpendState and calls checkSpendCap() once per turn,
5
+ * before any LLM call for that turn is made (see assistant.ts's turn() wrapper).
6
+ */
7
+ export interface SpendCapConfig {
8
+ /** Undefined = unbounded, today's default behavior. */
9
+ sessionCostLimitUsd?: number;
10
+ /**
11
+ * Optional secondary ceiling on completed turns this session (not raw internal LLM call
12
+ * count — a single turn can make several real LLM calls of its own, e.g. decomposition +
13
+ * tool-loop round trips, and none of those sub-calls can trigger a mid-turn refusal per
14
+ * checkSpendCap's "pre-turn only" contract, so counting at that finer grain would have
15
+ * nothing to enforce against). Named sessionCallLimit to match the review's own "dollar/call
16
+ * ceiling" framing (§11).
17
+ */
18
+ sessionCallLimit?: number;
19
+ }
20
+ export interface SpendState {
21
+ cumulativeCostUsd: number;
22
+ cumulativeCalls: number;
23
+ /** Tracked alongside cumulativeCostUsd (same cross-turn, cross-session lifetime) so /cost's
24
+ * token tally and its dollar/ceiling figures always describe the same scope — previously the
25
+ * token counts lived only in cli.ts's in-process `sessionUsage`, which resets on /new (and on
26
+ * every fresh process) while this store does not, producing a "0 in / 0 out tokens" line next
27
+ * to a real nonzero dollar cost once any turn had run before the current /new. */
28
+ cumulativeInputTokens: number;
29
+ cumulativeOutputTokens: number;
30
+ }
31
+ export declare const EMPTY_SPEND_STATE: SpendState;
32
+ export type SpendCapCheck = {
33
+ allowed: true;
34
+ } | {
35
+ allowed: false;
36
+ reason: string;
37
+ };
38
+ /** Called once at the start of turn(), before any LLM call for that turn is made. A turn already in flight is never interrupted mid-turn. */
39
+ export declare function checkSpendCap(state: SpendState, config: SpendCapConfig): SpendCapCheck;
40
+ /** Shared by /status and /cost (cli.ts) so the ceiling reads the same way in both places — undefined when no ceiling is configured, so callers can omit the line entirely rather than showing a meaningless "N% of nothing". */
41
+ export declare function formatSpendCapStatus(state: SpendState, config: SpendCapConfig): string | undefined;
@@ -0,0 +1 @@
1
+ export {}
package/dist/spike.js ADDED
@@ -0,0 +1,18 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { render, useInput, Box, Text } from "ink";
4
+ function Spike() {
5
+ const [lastKey, setLastKey] = useState("(none yet)");
6
+ useInput((input, key) => {
7
+ if (key.escape || key.ctrl && input === "c") {
8
+ process.exit(0);
9
+ }
10
+ setLastKey(key.return ? "<enter>" : input || JSON.stringify(key));
11
+ });
12
+ return /* @__PURE__ */ jsx(Box, { borderStyle: "round", padding: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
13
+ "Ink spike — hello world. Last key: ",
14
+ lastKey
15
+ ] }) });
16
+ }
17
+ render(/* @__PURE__ */ jsx(Spike, {}));
18
+ //# sourceMappingURL=spike.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spike.js","sources":["../src/tui-spike.tsx"],"sourcesContent":["// Phase 0 feasibility spike for plans/personal_assistant_cli_pinned_input_plan.html.\n// Throwaway — not part of the shipped feature. Delete once Phase 5 confirms Ink\n// works over a real live terminal (raw-mode input + redraw), along with its\n// temporary build entry in vite.config.lib.ts.\nimport React, { useState } from 'react'\nimport { Box, Text, render, useInput } from 'ink'\n\nfunction Spike() {\n const [lastKey, setLastKey] = useState('(none yet)')\n\n useInput((input, key) => {\n if (key.escape || (key.ctrl && input === 'c')) {\n process.exit(0)\n }\n setLastKey(key.return ? '<enter>' : input || JSON.stringify(key))\n })\n\n return (\n <Box borderStyle=\"round\" padding={1}>\n <Text>Ink spike — hello world. Last key: {lastKey}</Text>\n </Box>\n )\n}\n\nrender(<Spike />)\n"],"names":[],"mappings":";;;AAOA,SAAS,QAAQ;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,YAAY;AAEnD,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,IAAI,UAAW,IAAI,QAAQ,UAAU,KAAM;AAC7C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,eAAW,IAAI,SAAS,YAAY,SAAS,KAAK,UAAU,GAAG,CAAC;AAAA,EAClE,CAAC;AAED,6BACG,KAAA,EAAI,aAAY,SAAQ,SAAS,GAChC,+BAAC,MAAA,EAAK,UAAA;AAAA,IAAA;AAAA,IAAoC;AAAA,EAAA,EAAA,CAAQ,EAAA,CACpD;AAEJ;AAEA,OAAO,oBAAC,SAAM,CAAE;"}
@@ -0,0 +1,3 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ import { SupervisorDirectiveData, TrajectoryDigestData } from '@buildaharness/harness';
3
+ export declare function decideSupervisorDirective(digest: TrajectoryDigestData, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<Partial<SupervisorDirectiveData> | null>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The assistant's base system prompt — split into its own module so both assistant.ts's
3
+ * sequencer (which splices in the per-turn facts/reminders blocks) and
4
+ * action-approval-service.ts's shell-output synthesis call (SYNTHESIS_SYSTEM_PROMPT below) can
5
+ * depend on it without either module owning the other.
6
+ */
7
+ export declare const SYSTEM_PROMPT: string;
8
+ export declare const SYNTHESIS_SYSTEM_PROMPT: string;
@@ -0,0 +1,42 @@
1
+ import { Task } from '@buildaharness/harness';
2
+ import { TurnIntentClassification } from './turn-intent-classifier.js';
3
+ /**
4
+ * Builds a fresh harness Task[] from a flat task-spec list — shared by the single-task
5
+ * fallback, the ad hoc decomposition path, a newly built plan, and a resumed plan.
6
+ * `status` defaults to 'PENDING' when omitted (decomposeObjective's and
7
+ * buildPlanFromTemplate's output never carries one). A resumed plan passes its tasks'
8
+ * real statuses through *including* COMPLETE ones — TaskGraph.selectUnblockedLeaf
9
+ * resolves depends_on by looking up each dependency's status in the current graph, so
10
+ * a completed dependency that got filtered out of initialTasks would never register as
11
+ * satisfied and its dependents would stay permanently blocked.
12
+ *
13
+ * A task's own `riskLevel` (classifyTurnIntent's per-task judgment for ad hoc decompositions,
14
+ * or buildPlanFromTemplate's template-curated risk_level for a plan — see plan-store.ts's
15
+ * PlanTaskRecord) is used directly when present — see Phase 4.2 of the harness layer activation
16
+ * plan: a plan step like "delete the draft file" shouldn't inherit an unrelated sibling step's
17
+ * risk profile just because they were rendered from the same turn-level classification.
18
+ * `fallbackRiskLevel` (one flat level, or a per-task function keyed off description) is only
19
+ * consulted when a task has no `riskLevel` of its own — the single-task path (never has one,
20
+ * always uses the flat turn-level classification.riskLevel) and a plan persisted before this
21
+ * field existed (falls back to lexical planTaskRiskLevel/classifyRisk, exactly as every task
22
+ * used to unconditionally).
23
+ */
24
+ export declare function toHarnessTasks(tasks: {
25
+ id: string;
26
+ description: string;
27
+ depends_on: string[];
28
+ status?: Task['status'];
29
+ riskLevel?: Task['risk_level'];
30
+ }[], fallbackRiskLevel: Task['risk_level'] | ((description: string) => Task['risk_level'])): Task[];
31
+ /**
32
+ * packages/harness's own risk-level types (Task['risk_level'], TurnComplexitySignal['riskLevel'])
33
+ * are a strict LOW/MEDIUM/HIGH enum with no UNKNOWN concept — classification.riskLevel can be
34
+ * 'UNKNOWN' only via failSafeClassification (turn-intent-classifier.ts), which also always leaves
35
+ * decomposedTasks null, so in practice this only ever maps the flat, single-task fallback path.
36
+ * Mapped to 'HIGH' (not 'LOW') as the conservative choice: an unknown risk should get the
37
+ * harness's most cautious handling, not its least. Shared by every module that crosses into a
38
+ * packages/harness-typed field (task-interpreter.ts and harness-bridge.ts).
39
+ */
40
+ export declare function toTaskRiskLevel(riskLevel: TurnIntentClassification['riskLevel']): Task['risk_level'];
41
+ /** Lexical fallback for a durable plan's steps that were persisted before per-task riskLevel existed (see toHarnessTasks) — reuses classifyRisk's own keyword patterns against each step's own description. */
42
+ export declare function planTaskRiskLevel(description: string): Task['risk_level'];
@@ -0,0 +1,67 @@
1
+ import { WorldModel, EvidenceStore, Diagnostics, FailureDiagnostics, ControlState, ToolAvailability } from '@buildaharness/harness';
2
+ /**
3
+ * Phase 4c of plans/harness_and_assistant_architecture_remediation_plan.html — a live, per-turn
4
+ * harness ControlState, so tool-policy.ts's evaluateToolPolicy() can be given a real
5
+ * controlState instead of the pre-evidence-baseline `undefined` every call site passed before
6
+ * this phase. Deliberately builds on the fallback control-plane design (no packages/harness core
7
+ * changes): composes the harness's own pure, already-exported node functions directly, never
8
+ * touching HarnessRuntime's generator.
9
+ *
10
+ * DESIGN NOTE — why this never calls the generic updateDiagnostics() node: that function's
11
+ * coverage_health.symptom_coverage is computed from HypothesisSet.active, which nothing in this
12
+ * minimal tool-call-outcome-only pipeline ever populates (there's no hypothesis-generation/
13
+ * task-graph engine backing it here). symptom_coverage would therefore read structurally as 0 —
14
+ * below CRITICAL_THRESHOLD — the moment updateDiagnostics() ran, which would spuriously DENY
15
+ * almost every second-or-later tool call in every tool-using turn, regardless of whether
16
+ * anything actually failed. Verified against adapter-equivalent source
17
+ * (update-diagnostics.ts:150-167, resolve-control-state.ts:96-97,174-178) before this file was
18
+ * written. Instead: coverage_health/execution_health.{progress_rate,oscillation_score} are left
19
+ * at their constructor-safe defaults (confirmed outside the CAUTIOUS band), and only
20
+ * execution_health.failure_recurrence is hand-updated, using update-diagnostics.ts's own exact
21
+ * formula. That crosses CRITICAL_THRESHOLD at 8+ same-turn tool failures — a real, honest bar,
22
+ * not a hair-trigger. (Not exactly the 9 a back-of-envelope 1-in-10 reading suggests: at exactly
23
+ * 8 failures, failure_recurrence is 0.8, and resolve-control-state.ts's own pre-existing
24
+ * `1 - failure_recurrence` comparison evaluates to 0.19999999999999996 in IEEE754 double
25
+ * arithmetic — a floating-point rounding quirk already present in that comparison, not
26
+ * introduced here — landing just under CRITICAL_THRESHOLD (0.2) one failure earlier than exact
27
+ * decimal math would suggest. See tool-control-plane.test.ts for the pinned boundary.)
28
+ *
29
+ * SCOPE BOUNDARY, explicit and deliberate: this state is turn-scoped and in-memory only. A
30
+ * persisted resume path (e.g. resolvePendingBatchConfirmation's batch-approval round trip
31
+ * through storage) does not carry a live ControlState across the gap — the original turn's
32
+ * state is gone by the time a resume happens. Real, buildable future work; out of scope here.
33
+ */
34
+ export interface TurnControlPlaneState {
35
+ readonly evidenceStore: EvidenceStore;
36
+ readonly worldModel: WorldModel;
37
+ readonly diagnostics: Diagnostics;
38
+ readonly failureDiagnostics: FailureDiagnostics;
39
+ controlState: ControlState;
40
+ }
41
+ /**
42
+ * A fresh EvidenceStore defaults to an EMPTY tool_availability_manifest, which makes
43
+ * gatherEvidence() silently no-op for every tool. Seed it with every tool name this turn's
44
+ * config actually enables, so tool outcomes are never silently dropped.
45
+ */
46
+ export declare function toolAvailabilityManifest(toolNames: string[]): Record<string, ToolAvailability>;
47
+ export declare function createTurnControlPlaneState(toolNames: string[]): TurnControlPlaneState;
48
+ export interface ToolOutcome {
49
+ toolName: string;
50
+ ok: boolean;
51
+ /** Short, human-readable — never the full resultText (can be large/untrusted). */
52
+ summary: string;
53
+ }
54
+ /**
55
+ * The more tool-restrictive of two ControlStates (ties → `a`). A harness-driven proposer must
56
+ * honor BOTH the harness's own per-iteration ControlState AND the turn-local one accumulated
57
+ * from this turn's prior tool-call outcomes (via recordToolOutcome) — neither may silently
58
+ * downgrade the other. Overwriting one with the other (which the one-loop proposer used to do)
59
+ * loses whichever restriction the discarded side carried; this keeps the stricter one.
60
+ */
61
+ export declare function moreRestrictiveControlState(a: ControlState, b: ControlState): ControlState;
62
+ /**
63
+ * Feeds one tool call's outcome through the partial pipeline described in this file's own
64
+ * top-of-file doc comment, mutates `state` in place, and returns the freshly-resolved
65
+ * ControlState (also left on `state.controlState` for convenience).
66
+ */
67
+ export declare function recordToolOutcome(state: TurnControlPlaneState, outcome: ToolOutcome): ControlState;
@@ -0,0 +1,45 @@
1
+ import { RiskLevel } from './turn-intent-classifier.js';
2
+ /**
3
+ * Phase 4 of plans/harness_and_assistant_architecture_remediation_plan.html: the deterministic,
4
+ * authoritative decision point for whether a specific tool call may proceed — replacing "advisory
5
+ * LLM classification, checked after the fact" with "deterministic policy informed by real harness
6
+ * state, checked before execution" (the substantive fix both external architecture critiques
7
+ * asked for).
8
+ *
9
+ * Modeled on file-tools.ts's stagePendingAction gate, which already runs unconditionally on a
10
+ * tool call's own concrete arguments (the literal path/content or command/cwd), independent of
11
+ * whatever risk-classifier text patterns concluded about the user's phrasing — see that function's
12
+ * own doc comment for the "gate on the concrete tool call, not the free text" rationale this
13
+ * module generalizes into a single, reusable, unit-testable decision function instead of the
14
+ * pattern living only inline at each tool's own call site.
15
+ *
16
+ * `riskHint` (turn-intent-classifier.ts's RiskLevel, including the fail-safe 'UNKNOWN') is an
17
+ * input/signal, never itself the gate — a HIGH riskHint can't downgrade a real harness DENY, and a
18
+ * LOW riskHint can't upgrade past what the concrete tool call and control state already require.
19
+ */
20
+ export type ToolPolicyDecision = 'ALLOW' | 'REQUIRE_APPROVAL' | 'DENY';
21
+ /** Structural subset of packages/harness's ControlState — only the fields this policy reads,
22
+ * so callers can pass either a live ControlState instance or a plain matching object without an
23
+ * import-time dependency on the class itself. */
24
+ export interface ToolPolicyControlState {
25
+ permission: 'ALLOW' | 'DENY';
26
+ execution_mode: 'NORMAL' | 'CAUTIOUS' | 'RECOVERY';
27
+ escalation: 'NONE' | 'HUMAN_REQUIRED' | 'SYSTEM_BREAKING';
28
+ }
29
+ export interface ToolPolicyInput {
30
+ toolName: string;
31
+ riskHint: RiskLevel;
32
+ /**
33
+ * The harness's live ControlState for this turn, when one has actually been computed —
34
+ * `undefined` for the pre-evidence baseline before any tool call this turn has produced
35
+ * observations to resolve a real ControlState from (see resolve-control-state.ts: a fresh
36
+ * WorldModel with zero evidence resolves to an uninformative NORMAL/ALLOW every time, so
37
+ * treating "not yet computed" the same as "computed and permissive" costs nothing here).
38
+ */
39
+ controlState?: ToolPolicyControlState;
40
+ }
41
+ export interface ToolPolicyResult {
42
+ decision: ToolPolicyDecision;
43
+ reason: string;
44
+ }
45
+ export declare function evaluateToolPolicy(input: ToolPolicyInput): ToolPolicyResult;
@@ -0,0 +1,23 @@
1
+ import { ToolStepEvent } from '@buildaharness/runtime';
2
+ /**
3
+ * Live, UI-facing tool-call event — deliberately distinct from trace-events.ts's TraceEvent
4
+ * (`kind: 'tool_call'`), which is intentionally name/status-only telemetry safe to hand to
5
+ * an arbitrary logging sink. This one exists purely so a front end can show "what is the
6
+ * assistant doing right now" the way Claude Code's own CLI does, so it carries a
7
+ * human-readable summary and the raw input.
8
+ */
9
+ export interface AssistantToolStep extends ToolStepEvent {
10
+ summary: string;
11
+ }
12
+ /**
13
+ * Strips an MCP-qualified tool name (`mcp__<server>__<tool>`, how Claude Code CLI surfaces
14
+ * a tool registered via --mcp-config) down to the bare name our own tool defs use — a
15
+ * plain API/proxy backend's tool names never have this prefix, so this is a no-op there.
16
+ */
17
+ export declare function stripMcpToolPrefix(name: string): string;
18
+ /**
19
+ * One human-readable line per tool call, shared across every front end (CLI/chat-ui/desktop)
20
+ * and every backend (the proxy's own tool loop in assistant.ts, ClaudeCliLLMClient's stream
21
+ * parsing) so "what step is this" reads identically no matter where it's rendered.
22
+ */
23
+ export declare function summarizeToolStep(tool: string, input: Record<string, unknown>): string;
@@ -0,0 +1,7 @@
1
+ export type ToolYield = 'productive' | 'dead_end';
2
+ /**
3
+ * Zero-LLM-call heuristic run on every web tool result inside a batch sub-loop, so it has to stay
4
+ * cheap. Defaults to 'productive' whenever the result isn't unambiguously a dead end — see
5
+ * DEAD_END_MARKERS' comment for why that asymmetry is deliberate.
6
+ */
7
+ export declare function classifyToolYield(toolName: 'web_search' | 'fetch_url', resultText: string): ToolYield;
@@ -0,0 +1,126 @@
1
+ import { RiskLevel } from './turn-intent-classifier.js';
2
+ import { ExecutionMode } from './execution-mode.js';
3
+ import { ToolPolicyDecision } from './tool-policy.js';
4
+ import { ProposerKind } from './assistant-types.js';
5
+ /**
6
+ * Structured turn telemetry — deliberately name/status-only, never full message
7
+ * content, so it's cheap to log and safe to hand to an arbitrary sink (Langfuse,
8
+ * console, a custom collector). See PersonalAssistantOptions.onTrace.
9
+ */
10
+ export type TraceEvent = {
11
+ kind: 'turn_start';
12
+ sessionId: string;
13
+ message: string;
14
+ } | {
15
+ kind: 'turn_end';
16
+ sessionId: string;
17
+ status: 'ok' | 'needs_approval' | 'escalated' | 'needs_clarification' | 'needs_plan_approval';
18
+ } | {
19
+ kind: 'risk_classified';
20
+ riskLevel: RiskLevel;
21
+ requiresApproval: boolean;
22
+ } | {
23
+ kind: 'triviality_classified';
24
+ isTrivial: boolean;
25
+ } | {
26
+ kind: 'plan_classified';
27
+ isCandidate: boolean;
28
+ matchedTemplate: string | null;
29
+ } | {
30
+ kind: 'plan_updated';
31
+ templateName: string | null;
32
+ completionPct: number;
33
+ } | {
34
+ kind: 'harness_node';
35
+ node: string;
36
+ stepsUsed: number;
37
+ } | {
38
+ kind: 'tool_call';
39
+ tool: string;
40
+ ok: boolean;
41
+ } | {
42
+ kind: 'escalation';
43
+ reason: string;
44
+ } | {
45
+ kind: 'error';
46
+ message: string;
47
+ }
48
+ /**
49
+ * One of the harness's 11 layers did (or explicitly skipped) real work this step —
50
+ * see plans/harness_layer_activation_plan.html Phase 2/3.1. `layer` is a stable slug
51
+ * ('world_model' | 'evidence_reasoning' | 'hypothesis' | 'contradiction' | 'diagnostics' |
52
+ * 'control_state' | 'planning' | 'execution' | 'verification' | 'recovery' | 'reviewer_pass'),
53
+ * not a free-text name, so a "Why?"/`/layers` renderer can key off it directly.
54
+ */
55
+ | {
56
+ kind: 'layer_activity';
57
+ layer: string;
58
+ fired: boolean;
59
+ reason: string;
60
+ }
61
+ /**
62
+ * A prior-turn harness checkpoint (see runTurn's runId doc comment — left behind when a
63
+ * process died mid-run before reaching normal cleanup) failed to resume `failedAttempts` times
64
+ * in a row and was discarded automatically instead of being retried again; this turn started
65
+ * fresh instead of resuming it. See PersonalAssistant.clearCheckpoint for the manual
66
+ * equivalent, and RESUME_ATTEMPT_CAP in assistant.ts for the threshold.
67
+ */
68
+ | {
69
+ kind: 'checkpoint_discarded';
70
+ sessionId: string;
71
+ failedAttempts: number;
72
+ }
73
+ /**
74
+ * Phase 4 of plans/harness_and_assistant_architecture_remediation_plan.html — see
75
+ * execution-mode.ts's own doc comment for each mode's guarantee level.
76
+ */
77
+ | {
78
+ kind: 'execution_mode_classified';
79
+ mode: ExecutionMode;
80
+ }
81
+ /**
82
+ * ToolPolicy's decision for one specific tool call, before it runs — the deterministic,
83
+ * harness-state-informed gate replacing "advisory classification checked after the fact" (see
84
+ * tool-policy.ts).
85
+ */
86
+ | {
87
+ kind: 'tool_policy_decision';
88
+ tool: string;
89
+ decision: ToolPolicyDecision;
90
+ reason: string;
91
+ }
92
+ /**
93
+ * Which proposer drove this turn — 'posthoc' (flag OFF, or a trivial / no-tool turn),
94
+ * 'flat-oneloop', or 'batch-oneloop' (both flag ON). Emitted once per turn from runTurn, right
95
+ * after `useOneLoop` is resolved. See AssistantTurnResult.proposerKind and
96
+ * plans/chat_ui_browser_e2e_plan.html phase B1 for why this exists.
97
+ */
98
+ | {
99
+ kind: 'proposer_selected';
100
+ proposerKind: ProposerKind;
101
+ }
102
+ /**
103
+ * P9 of plans/ask_question_and_plan_mode_plan.html — the lightweight plan-sketch delegate
104
+ * (PlanSketchService) ran. `requestPreview` is truncated to stay name/status-only per this
105
+ * type's own doc comment, not the full request text. Distinct from `plan_classified`/
106
+ * `plan_updated`, which are about the stateful drafting/approval machinery (PlanDraftingService)
107
+ * this delegate deliberately never touches (INV-35).
108
+ */
109
+ | {
110
+ kind: 'plan_sketch';
111
+ requestPreview: string;
112
+ }
113
+ /**
114
+ * P10 of plans/ask_question_and_plan_mode_plan.html — a write_file/run_shell_command/send_email
115
+ * proposal was auto-applied instead of staged for approval because it was proposed while
116
+ * `taskId` (part of a plan approved with `'approve_trusted'`) was the currently RUNNING task —
117
+ * see PlanRecord.trustApprovedSteps and INV-36. The action itself still went through
118
+ * ActionApprovalService.resolvePendingAction exactly as a manually-approved one would (same
119
+ * transcript entry, same real-undo record) — this event is purely additional "why no approval
120
+ * prompt" observability, name/status-only per this type's own doc comment.
121
+ */
122
+ | {
123
+ kind: 'plan_trust_auto_applied';
124
+ pendingActionKind: 'write' | 'shell' | 'email';
125
+ taskId: string;
126
+ };
@@ -0,0 +1,13 @@
1
+ import { ChatMessage } from '@buildaharness/runtime';
2
+ export interface CompactionResult {
3
+ transcript: ChatMessage[];
4
+ compacted: boolean;
5
+ }
6
+ /**
7
+ * Collapses the oldest messages beyond the most recent `KEEP_RECENT` into one
8
+ * synthetic summary message once the transcript crosses a message-count or
9
+ * char-count threshold. Deliberately a truncated concatenation, not an LLM
10
+ * summary — keeps compaction free instead of costing an extra call every time
11
+ * a long session crosses the threshold.
12
+ */
13
+ export declare function compactTranscript(transcript: ChatMessage[]): CompactionResult;
@@ -0,0 +1,38 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ /**
3
+ * Wraps content fetched from the web before it enters the model's context —
4
+ * `read_file`/`list_directory` results are untouched (file content is
5
+ * user-owned, not adversarial-by-default); only web_search/fetch_url results
6
+ * go through this. SYSTEM_PROMPT in assistant.ts carries the matching
7
+ * instruction that content in this tag is data, never instructions.
8
+ */
9
+ export declare function wrapUntrusted(text: string): string;
10
+ export interface InjectionDetection {
11
+ flagged: boolean;
12
+ reason?: string;
13
+ }
14
+ /** Content is always still returned to the model, flagged or not — this only decides whether a warning gets prepended. */
15
+ export declare function detectInjectionLikely(text: string): InjectionDetection;
16
+ /**
17
+ * `AUDIT_LLM_INJECTION_DETECT` gate — feature-value audit (Phase A5 of
18
+ * plans/feature_audit_automation_plan.html). Default **ON**: the semantic injection-detection LLM
19
+ * call ships enabled, so an unset / empty / truthy value keeps today's behaviour. Set to a falsy
20
+ * value (`0` / `false` / `off` / `no` / `disabled`) to skip the LLM escalation entirely and fall
21
+ * back to the always-on deterministic regex/pattern pass only. Read at exactly one place —
22
+ * `detectInjectionLikelyWithLLM` below, the single helper both `agent-loop.ts` and
23
+ * `action-approval-service.ts` route their tool-output checks through. Same shape as
24
+ * `semanticContradictionEnabled()` (contradiction-checker.ts) and the harness's `supervisorEnabled()`.
25
+ */
26
+ export declare function llmInjectionDetectEnabled(env?: Record<string, string | undefined>): boolean;
27
+ /**
28
+ * Semantic escalation layered on top of the regex check above — only reached when the regex
29
+ * found nothing (a real injection attempt this codebase has already seen looks nothing like
30
+ * the fixed phrase list) and the content is long enough to plausibly hide one. Uses a
31
+ * structured JSON response (not a free chat reply) and an explicit "you are not the assistant
32
+ * being targeted" framing so the untrusted content itself has no natural surface to hijack —
33
+ * the same defense the main model already gets via SYSTEM_PROMPT's own instruction, applied to
34
+ * this classifier too. Falls back to "not flagged" on any parse failure or LLM error, matching
35
+ * this codebase's other LLM-backed classifiers — a missed detection costs nothing worse than
36
+ * the regex-only behavior this is layered on top of.
37
+ */
38
+ export declare function detectInjectionLikelyWithLLM(text: string, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<InjectionDetection>;