@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,52 @@
1
+ import { ILLMClient, MemoryAdapter } from '@buildaharness/runtime';
2
+ import { FileToolsContext } from './file-tools.js';
3
+ import { ShellToolsContext } from './shell-tools.js';
4
+ import { ActionToolsContext } from './action-tools.js';
5
+ import { AssistantTurnResult } from './assistant-types.js';
6
+ import { AssistantSession } from './assistant-session.js';
7
+ import { AgentLoop } from './agent-loop.js';
8
+ import { TraceEvent } from './trace-events.js';
9
+ import { DebugLogEntry } from './debug-log.js';
10
+ /**
11
+ * Owns the "approval-by-ID" pattern: a staged write/shell/revert/batch-research action resolved
12
+ * by ID, never re-derived from a second LLM call (see T4 of the file-tools plan). Split out in
13
+ * Phase 4d of the architecture remediation plan — do not change this behavior when relocating it,
14
+ * only where the code lives.
15
+ */
16
+ export declare class ActionApprovalService {
17
+ private readonly memory;
18
+ private readonly llmClient;
19
+ private readonly model;
20
+ private readonly fileTools;
21
+ private readonly shellTools;
22
+ private readonly actionTools;
23
+ private readonly session;
24
+ private readonly agentLoop;
25
+ private readonly onTrace;
26
+ private readonly onDebugLog;
27
+ constructor(memory: MemoryAdapter, llmClient: ILLMClient, model: () => string | undefined, fileTools: FileToolsContext | undefined, shellTools: ShellToolsContext | undefined, actionTools: ActionToolsContext | undefined, session: AssistantSession, agentLoop: AgentLoop, onTrace: ((event: TraceEvent) => void) | undefined, onDebugLog: ((entry: DebugLogEntry) => void) | undefined);
28
+ /** Resumes a staged action by ID instead of re-deriving *what to run* from a second LLM call — see T4 of the file-tools plan. `userMessage` is only used to synthesize an answer from a shell command's real output (see below); the command/content actually applied always comes from the staged record, never from a fresh model call. */
29
+ resolvePendingAction(sessionId: string, transcriptKey: string, pendingActionId: string, approved: boolean, userMessage: string): Promise<AssistantTurnResult>;
30
+ /**
31
+ * After resolvePendingAction resolves one staged action (approved or declined), checks whether
32
+ * it was chained to a second approval-gated action staged from the same originating turn (see
33
+ * file-tools-mcp-server.mjs's stagePendingAction doc comment — e.g. a single "run X AND write
34
+ * Y" request). Without this, the second action sat in `.pending-actions/` forever: never
35
+ * surfaced for approval, never executed, never even mentioned as still pending.
36
+ * `previousOutcome` (the reply text already computed for the just-resolved action) is folded
37
+ * into the next prompt's `reason` so cli.ts's recursive needs_approval → handleTurn flow reads
38
+ * as one continuous exchange rather than jumping straight to an unexplained second question.
39
+ * Returns undefined if the linked record is missing or of a kind that's never legitimately
40
+ * chained ('revert', staged only by /undo-action, alone) — a broken link must never crash the
41
+ * turn, just stop chaining and fall back to the caller's own `status: 'ok'`.
42
+ */
43
+ private loadChainedApproval;
44
+ /**
45
+ * Resolves a batch confirmation pause (see AgentLoop.runBatchToolLoop's confirmation gate)
46
+ * once the caller resumes via `turn(message, { approved, pendingActionId })`. Declining
47
+ * resolves the turn immediately with only the probed items' real results, explicitly listing
48
+ * every unprobed item as not attempted. Approving continues resolving the remaining items with
49
+ * zero re-probing — the probe results loaded from `batchState` are reused as-is.
50
+ */
51
+ private resolvePendingBatchConfirmation;
52
+ }
@@ -0,0 +1,173 @@
1
+ import { FsBackend } from '@buildaharness/runtime';
2
+ export type UndoLogEntry = {
3
+ id: string;
4
+ appliedActionId: string;
5
+ kind: 'write';
6
+ path: string;
7
+ previousContent: string | null;
8
+ appliedAt: string;
9
+ undoable: true;
10
+ } | {
11
+ id: string;
12
+ appliedActionId: string;
13
+ kind: 'write';
14
+ path: string;
15
+ appliedAt: string;
16
+ undoable: false;
17
+ reason: string;
18
+ } | {
19
+ id: string;
20
+ appliedActionId: string;
21
+ kind: 'shell';
22
+ command: string;
23
+ appliedAt: string;
24
+ undoable: true;
25
+ added: string[];
26
+ modified: {
27
+ path: string;
28
+ previousContent: string;
29
+ }[];
30
+ deleted: {
31
+ path: string;
32
+ previousContent: string;
33
+ }[];
34
+ unsnapshottableChanges: string[];
35
+ } | {
36
+ id: string;
37
+ appliedActionId: string;
38
+ kind: 'shell';
39
+ command: string;
40
+ appliedAt: string;
41
+ undoable: false;
42
+ reason: string;
43
+ };
44
+ export type SnapshotBeforeWriteResult = {
45
+ previousContent: string | null;
46
+ undoable: true;
47
+ } | {
48
+ previousContent?: undefined;
49
+ undoable: false;
50
+ reason: string;
51
+ };
52
+ /**
53
+ * Reads a write_file target's current content before it gets overwritten, so an undo-log entry
54
+ * can later restore it. `path` must already be resolved/verified against the workspace root by
55
+ * the caller (file-tools.ts's applyPendingAction) — this does no path validation of its own.
56
+ * `workspaceRoot` is accepted for signature parity with T2's snapshotWorkspaceTree, which does
57
+ * need it for a recursive walk; a single-file read has no use for it.
58
+ *
59
+ * A read error other than "file doesn't exist" (FsBackend.readTextFile resolves `undefined` only
60
+ * for that case) is intentionally left to propagate rather than caught here, so a genuine I/O
61
+ * failure surfaces as a failed approval instead of silently producing a wrong/empty snapshot.
62
+ */
63
+ export declare function snapshotBeforeWrite(backend: FsBackend, workspaceRoot: string, path: string): Promise<SnapshotBeforeWriteResult>;
64
+ /** One shared cap across every entry kind (write, and later shell/revert) — not a per-kind cap. */
65
+ export declare const UNDO_LOG_MAX_ENTRIES = 20;
66
+ /**
67
+ * Persists an undo-log entry, then prunes the oldest entries once UNDO_LOG_MAX_ENTRIES is
68
+ * exceeded — mirrors the "cap it, don't let it grow forever" convention already used elsewhere
69
+ * in this package (e.g. the batch-budget plan's ABSOLUTE_TURN_CEILING).
70
+ */
71
+ export declare function recordUndoLogEntry(backend: FsBackend, workspaceRoot: string, entry: UndoLogEntry): Promise<void>;
72
+ export declare function loadUndoLogEntry(backend: FsBackend, workspaceRoot: string, id: string): Promise<UndoLogEntry | undefined>;
73
+ /** Newest first (by appliedAt) — feeds /undo-action's no-argument listing view (T3). */
74
+ export declare function listUndoLogEntries(backend: FsBackend, workspaceRoot: string): Promise<UndoLogEntry[]>;
75
+ export declare function deleteUndoLogEntry(backend: FsBackend, workspaceRoot: string, id: string): Promise<void>;
76
+ /** Per-file cap — a file over this size is reported as skipped, never partially captured. */
77
+ export declare const UNDO_SNAPSHOT_MAX_FILE_BYTES = 1000000;
78
+ /** Overall file-count ceiling for one walk — hitting it truncates the whole snapshot (see
79
+ * WorkspaceSnapshot.truncated), not just the files past the ceiling, since a walk that stops
80
+ * partway through gives no reliable picture of "everything else is unchanged" either. */
81
+ export declare const UNDO_SNAPSHOT_MAX_FILES = 2000;
82
+ /**
83
+ * Directory names skipped by default during a workspace walk — this feature's own bookkeeping
84
+ * plus conventionally-heavy, rarely-hand-edited directories. Without this, a walk would blow
85
+ * UNDO_SNAPSHOT_MAX_FILES on node_modules/ alone for any real npm-monorepo workspace.
86
+ */
87
+ export declare const UNDO_SNAPSHOT_EXCLUDED_DIRS: string[];
88
+ export interface WorkspaceSnapshot {
89
+ files: Map<string, string>;
90
+ skipped: {
91
+ path: string;
92
+ reason: string;
93
+ size?: number;
94
+ }[];
95
+ truncated: boolean;
96
+ truncationReason?: string;
97
+ /**
98
+ * Deviation from the plan's summary interface (which lists only files/skipped/truncated/
99
+ * truncationReason): a cheap, non-recursive immediate-child-name listing taken once per
100
+ * excluded directory encountered, keyed by that directory's path. Excluded directories are
101
+ * never walked into (that's the whole point — see UNDO_SNAPSHOT_EXCLUDED_DIRS's doc comment),
102
+ * so there is no per-file record of their contents to diff. This one-level signature is the
103
+ * cheapest available signal that lets diffSnapshots notice "something was added to or removed
104
+ * from node_modules/ directly" without paying to walk its full subtree. It's necessarily
105
+ * best-effort — a content-only change to a file already inside an excluded directory, or a
106
+ * change nested more than one level deep inside it, isn't detected by this signature alone.
107
+ */
108
+ excludedDirSignatures: Map<string, string>;
109
+ }
110
+ /**
111
+ * Recursively snapshots every text file under `workspaceRoot`, for diffing before/after a
112
+ * run_shell_command execution (T2 step 6) since a shell command has no single known target path
113
+ * the way write_file does. Skips default-excluded directories, oversized files, and binary
114
+ * files (see the exported constants above) rather than either blowing past a sane cost ceiling
115
+ * or silently corrupting a binary file via a lossy UTF-8 round-trip.
116
+ *
117
+ * Requires `backend.stat` (optional on FsBackend — only backends doing a recursive walk need
118
+ * it). A backend without it can't distinguish a file from a directory, so this returns
119
+ * immediately with `truncated: true` rather than attempting an unreliable walk.
120
+ */
121
+ export declare function snapshotWorkspaceTree(backend: FsBackend, workspaceRoot: string): Promise<WorkspaceSnapshot>;
122
+ export interface WorkspaceDiff {
123
+ added: string[];
124
+ modified: {
125
+ path: string;
126
+ previousContent: string;
127
+ }[];
128
+ deleted: {
129
+ path: string;
130
+ previousContent: string;
131
+ }[];
132
+ unsnapshottableChanges: string[];
133
+ }
134
+ /**
135
+ * Diffs two workspace snapshots taken before/after a shell command ran. Only the actual diff is
136
+ * meant to be persisted to an undo-log entry, not two full tree copies.
137
+ */
138
+ export declare function diffSnapshots(before: WorkspaceSnapshot, after: WorkspaceSnapshot): WorkspaceDiff;
139
+ /**
140
+ * Builds the undo-log entry for a completed run_shell_command approval from its before/after
141
+ * workspace snapshots (T2 step 7).
142
+ *
143
+ * Deviation / judgment call: step 7's prose says a non-empty `unsnapshottableChanges` escalates
144
+ * the whole entry to `undoable: false`, but the Files tab's own type-level doc comment on the
145
+ * `undoable: true` shell variant says the opposite ("non-empty here still means undoable: true
146
+ * for the paths that WERE captured"), and the Test Plan tab's cases for node_modules-only and
147
+ * binary-file changes describe the command "executing normally" with the change merely
148
+ * "reported in unsnapshottableChanges" — neither test expects undoable: false for those cases.
149
+ * Following the type comment and the Test Plan tab (the more precise, mutually-consistent
150
+ * sources) over the summary prose: only a *truncated* tree walk — meaning the before/after
151
+ * picture itself is unreliable, not just a subset of paths within it — escalates to
152
+ * `undoable: false`. A non-empty `unsnapshottableChanges` on an otherwise-complete walk keeps
153
+ * `undoable: true`, offering a partial revert for the paths that were captured, with the
154
+ * incompleteness surfaced on the entry for a later /undo-action to warn about (T3).
155
+ */
156
+ export declare function buildShellUndoLogEntry(appliedActionId: string, command: string, before: WorkspaceSnapshot, after: WorkspaceSnapshot): UndoLogEntry;
157
+ /**
158
+ * The concrete filesystem operations a revert needs to perform, derived from one undo-log
159
+ * entry: every path in `restore` gets its `content` written back; every path in `remove` gets
160
+ * deleted (it didn't exist, or wasn't created, before the original action ran). Kept as its own
161
+ * plain-data shape (rather than handing the raw UndoLogEntry to applyPendingAction) so the
162
+ * staged `kind: 'revert'` PendingActionPayload carries exactly what it needs to apply, the same
163
+ * way `kind: 'write'`/`kind: 'shell'` already carry their own complete, self-sufficient payload.
164
+ */
165
+ export interface RevertPlan {
166
+ restore: {
167
+ path: string;
168
+ content: string;
169
+ }[];
170
+ remove: string[];
171
+ }
172
+ /** `undefined` when `entry.undoable` is false — an undoable:false entry has nothing to revert. */
173
+ export declare function buildRevertPlan(entry: UndoLogEntry): RevertPlan | undefined;
@@ -0,0 +1,38 @@
1
+ import { FsBackend, ToolDefinition } from '@buildaharness/runtime';
2
+ import { SendEmail } from './email.js';
3
+ export declare const SEND_EMAIL_TOOL: ToolDefinition;
4
+ export declare const ACTION_TOOLS: ToolDefinition[];
5
+ /** Everything executeActionTool needs to validate + stage a proposal — no delivery capability required. */
6
+ export interface ActionStagingContext {
7
+ backend: FsBackend;
8
+ workspaceRoot: string;
9
+ }
10
+ /**
11
+ * Everything PersonalAssistant needs to both stage and, once approved, actually deliver an effect
12
+ * action. `sendEmail` is the injected transport (email.ts's createResendSender / email-smtp.ts's
13
+ * createSmtpSender) — required, not optional, for the same reason ShellToolsContext.executeCommand
14
+ * is: assistant.ts/index.ts is bundled into the browser build and must never import a transport
15
+ * directly, so a Node caller (cli.ts) wires the real one in.
16
+ */
17
+ export interface ActionToolsContext extends ActionStagingContext {
18
+ sendEmail: SendEmail;
19
+ }
20
+ export type ActionToolResult = {
21
+ kind: 'staged_email';
22
+ id: string;
23
+ to: string;
24
+ subject: string;
25
+ body: string;
26
+ cc?: string;
27
+ bcc?: string;
28
+ };
29
+ export declare class InvalidEmailArgsError extends Error {
30
+ constructor(detail: string);
31
+ }
32
+ /**
33
+ * Executes send_email by name. Never delivers anything itself — only stages, exactly like
34
+ * write_file / run_shell_command. Validation (recipient address shape) runs unconditionally on the
35
+ * call's own concrete arguments, independent of whatever risk-classifier.ts concluded about the
36
+ * user's phrasing — see stagePendingAction's doc comment in file-tools.ts.
37
+ */
38
+ export declare function executeActionTool(ctx: ActionStagingContext, toolName: string, input: Record<string, unknown>): Promise<ActionToolResult>;
@@ -0,0 +1,372 @@
1
+ import { ToolExecutorContext, HarnessPauseSignal, InvestigationRequestData, InvestigationFinding } from '@buildaharness/harness';
2
+ import { MemoryAdapter, ILLMClient, ChatMessage, TokenUsage, ToolDefinition, ReminderStore } from '@buildaharness/runtime';
3
+ import { AssistantSource } from './assistant-source.js';
4
+ import { DebugLogEntry } from './debug-log.js';
5
+ import { TraceEvent } from './trace-events.js';
6
+ import { TurnIntentClassification, RiskLevel } from './turn-intent-classifier.js';
7
+ import { evaluateToolPolicy } from './tool-policy.js';
8
+ import { TurnControlPlaneState } from './tool-control-plane.js';
9
+ import { FileToolsContext } from './file-tools.js';
10
+ import { WebToolsContext } from './web-tools.js';
11
+ import { ShellToolsContext } from './shell-tools.js';
12
+ import { ActionToolsContext } from './action-tools.js';
13
+ import { AssistantToolStep } from './tool-step.js';
14
+ export type ToolLoopResult = {
15
+ kind: 'final';
16
+ content: string;
17
+ sources: AssistantSource[];
18
+ batchBudget?: BatchBudgetTrace;
19
+ } | {
20
+ kind: 'needs_approval';
21
+ reason: string;
22
+ pendingActionId: string;
23
+ pendingActionKind: 'write' | 'shell' | 'email' | 'batch';
24
+ } | {
25
+ kind: 'escalated';
26
+ reason: string;
27
+ };
28
+ /**
29
+ * R2 of plans/harness_d2_one_loop_rewire_plan.html: thrown by a harness-driven proposer
30
+ * (createHarnessProposer, below) when runToolIterationStep produces a 'needs_approval' or
31
+ * 'escalated' ToolLoopResult — the harness's own execute() rethrows anything satisfying
32
+ * isHarnessPauseSignal unexamined (no SYSTEM_ERROR evidence, no task-FAILED transition), so this
33
+ * is never misclassified as a tool failure. Carries the original ToolLoopResult so the eventual
34
+ * catcher (assistant.ts's runTurn, alongside its existing `catch (err) { if (err instanceof
35
+ * EscalationHalt) ... }` branch — see R3) can build the exact same needs_approval/escalated
36
+ * AssistantTurnResult it already builds from a flag-OFF ToolLoopResult, without re-deriving it.
37
+ */
38
+ export declare class OneLoopPause extends Error implements HarnessPauseSignal {
39
+ readonly result: Extract<ToolLoopResult, {
40
+ kind: 'needs_approval' | 'escalated';
41
+ }>;
42
+ /**
43
+ * P10 of plans/ask_question_and_plan_mode_plan.html: the plan task (if any) that was RUNNING
44
+ * — i.e. whichever task `execute()` was calling this proposer for — when `result` was
45
+ * produced, threaded from `ToolExecutorContext.currentTaskId`. Lets the catcher
46
+ * (assistant.ts's `buildToolLoopPauseResult`) check INV-36's trust condition without
47
+ * re-deriving "which task was this" from anything else. `undefined` for the batch proposer's
48
+ * own confirmation-gate throw (not a write/shell/email action, so trust mode never applies to
49
+ * it) and for any pre-P10 construction.
50
+ */
51
+ readonly currentTaskId?: string | undefined;
52
+ readonly __harnessPause: true;
53
+ constructor(result: Extract<ToolLoopResult, {
54
+ kind: 'needs_approval' | 'escalated';
55
+ }>,
56
+ /**
57
+ * P10 of plans/ask_question_and_plan_mode_plan.html: the plan task (if any) that was RUNNING
58
+ * — i.e. whichever task `execute()` was calling this proposer for — when `result` was
59
+ * produced, threaded from `ToolExecutorContext.currentTaskId`. Lets the catcher
60
+ * (assistant.ts's `buildToolLoopPauseResult`) check INV-36's trust condition without
61
+ * re-deriving "which task was this" from anything else. `undefined` for the batch proposer's
62
+ * own confirmation-gate throw (not a write/shell/email action, so trust mode never applies to
63
+ * it) and for any pre-P10 construction.
64
+ */
65
+ currentTaskId?: string | undefined);
66
+ }
67
+ /** Per-item calibration inputs — see nextItemBudget. */
68
+ export interface BatchBudgetState {
69
+ callsPerItemHistory: number[];
70
+ perItemFloor: number;
71
+ slackFactor: number;
72
+ absoluteTurnCeiling: number;
73
+ }
74
+ /**
75
+ * Drops one min and one max before averaging once there are enough samples for that to be
76
+ * meaningful (fewer than 3 just averages plain) — so a single unusually cheap or expensive item
77
+ * can't swing the projection to either extreme on its own.
78
+ */
79
+ export declare function trimmedAverage(counts: number[]): number;
80
+ /** Budget for the next item to resolve — floored so a suspiciously cheap item can't starve the
81
+ * rest, with slack headroom on top of the calibrated average. */
82
+ export declare function nextItemBudget(state: BatchBudgetState): number;
83
+ /**
84
+ * One batch item's outcome from its own per-item sub-loop (see resolveBatchItem). `exhausted`
85
+ * means the sub-loop ran out of its budget without the model producing a final answer for this
86
+ * item. `status` distinguishes *why* a non-'found' outcome happened: 'not_found' covers both the
87
+ * item-scoped dead-end window tripping early (BATCH_DEAD_END_WINDOW consecutive dead_end tool
88
+ * results) and a needs_approval bail-out; 'truncated_while_productive' means the budget ran out
89
+ * while the trailing window was still turning up plausibly-relevant content.
90
+ */
91
+ export interface BatchItemResolution {
92
+ item: string;
93
+ content: string;
94
+ callsUsed: number;
95
+ exhausted: boolean;
96
+ status: 'found' | 'not_found' | 'truncated_while_productive';
97
+ sources: AssistantSource[];
98
+ }
99
+ /**
100
+ * Persisted across the confirmation round trip (see runBatchToolLoop's confirmation gate and
101
+ * ActionApprovalService.resolvePendingBatchConfirmation) so approving resumes with the probe
102
+ * items' real results intact instead of re-probing them, and declining can still return those
103
+ * real results instead of discarding them.
104
+ */
105
+ export interface BatchPendingState {
106
+ userMessage: string;
107
+ systemPrompt: string;
108
+ sessionId: string;
109
+ probedResults: BatchItemResolution[];
110
+ remainingItems: string[];
111
+ /** The projection computed at the confirmation gate — carried across the round trip so the
112
+ * resume/decline paths can report the same number in the batch trace instead of re-deriving
113
+ * it (or leaving it absent) after the fact. */
114
+ projectedTotal: number;
115
+ }
116
+ /**
117
+ * Present only when the batch-research path (batch-list-detector.ts / runBatchToolLoop) drove
118
+ * this turn — absent otherwise, same "absent when unused" convention as sources/usage. Turns
119
+ * `should we raise the ceiling/floor/slack factor` from a guess into a measurement.
120
+ */
121
+ export interface BatchBudgetTrace {
122
+ itemCount: number;
123
+ callsPerItemHistory: number[];
124
+ projectedTotal: number;
125
+ totalCallsUsed: number;
126
+ perItemOutcomes: {
127
+ item: string;
128
+ status: 'found' | 'not_found' | 'truncated_while_productive';
129
+ callsUsed: number;
130
+ }[];
131
+ }
132
+ /** Builds a batch trace from a batch turn's resolved items — shared by runBatchToolLoop's direct
133
+ * path and both resolvePendingBatchConfirmation outcomes (see ActionApprovalService). */
134
+ export declare function buildBatchBudgetTrace(itemCount: number, projectedTotal: number, resolutions: BatchItemResolution[]): BatchBudgetTrace;
135
+ export declare function looksLikeUnparsedToolCall(content: string): boolean;
136
+ /**
137
+ * Bounded ReAct loop plus the batch-research sub-loop — the "actually call the model and its
138
+ * tools" half of PersonalAssistant, split out in Phase 4d of the architecture remediation plan.
139
+ * Owns tool-control-plane enforcement (checkToolPolicy, below) as part of the same loop it gates,
140
+ * per Phase 4's design: a deterministic, harness-state-informed check before each call executes,
141
+ * not just advisory classification checked after the fact.
142
+ */
143
+ export declare class AgentLoop {
144
+ private readonly memory;
145
+ private readonly llmClient;
146
+ private readonly model;
147
+ private readonly fileTools;
148
+ private readonly webTools;
149
+ private readonly shellTools;
150
+ private readonly actionTools;
151
+ private readonly reminderStore;
152
+ private readonly maxSteps;
153
+ private readonly onTrace;
154
+ private readonly onDebugLog;
155
+ constructor(memory: MemoryAdapter, llmClient: ILLMClient, model: () => string | undefined, fileTools: FileToolsContext | undefined, webTools: WebToolsContext | undefined, shellTools: ShellToolsContext | undefined, actionTools: ActionToolsContext | undefined, reminderStore: ReminderStore, maxSteps: number, onTrace: ((event: TraceEvent) => void) | undefined, onDebugLog: ((entry: DebugLogEntry) => void) | undefined);
156
+ /**
157
+ * Phase 4c: builds one fresh, turn-scoped live ControlState (tool-control-plane.ts), seeded
158
+ * with the same tool-name list runToolLoop/resolveBatchItem already build internally — kept
159
+ * here rather than in the sequencer (assistant.ts) so the caller never needs to re-import
160
+ * FILE_TOOLS/WEB_TOOLS/SHELL_TOOLS/REMINDER_TOOLS just to construct this; AgentLoop already
161
+ * privately owns which tools are configured. Call once per turn (when a tool loop will run)
162
+ * and thread the result through runToolLoop/runBatchToolLoop so it's shared across every tool
163
+ * call the turn makes, including across batch items.
164
+ */
165
+ createControlPlaneState(): TurnControlPlaneState;
166
+ /**
167
+ * R2 of plans/harness_d2_one_loop_rewire_plan.html: builds the real toolExecutors['default']
168
+ * entry HarnessBridge.run() swaps in when the one-loop flag is enabled — a proposer that calls
169
+ * runToolIterationStep once per driveMainLoop iteration and translates its discriminated result
170
+ * into the primitives D0/D1 already built: a plain return (status 'continue', no output yet) for
171
+ * `{ done: false }`, the final answer text for a `{ done: true, result: { kind: 'final' } }`
172
+ * step, or a thrown OneLoopPause for `needs_approval`/`escalated` — never a thrown plain Error,
173
+ * so execute() never misclassifies this as a tool failure.
174
+ *
175
+ * Control-plane state: the proposer holds one real, turn-scoped `TurnControlPlaneState`
176
+ * (`createControlPlaneState`, the exact object the flag-OFF flat path threads through
177
+ * `runToolLoop`) for its whole lifetime — so `runToolIterationStep`'s `recordToolOutcome` has
178
+ * real evidence/worldModel/diagnostics/failureDiagnostics stores to write each dispatched call's
179
+ * outcome into, and same-turn tool-failure-pattern gating behaves identically to flag-OFF. On
180
+ * top of that, the harness's own live per-iteration `toolCtx.controlState` is pinned onto that
181
+ * state at the start of every iteration as the gate's floor (D2's "one composition, not two"
182
+ * intent): a harness DENY / HUMAN_REQUIRED escalation is honored by `checkToolPolicy` before the
183
+ * iteration's first call executes. `recordToolOutcome` re-resolves `.controlState` from the
184
+ * turn-local stores after each recorded outcome — that only ever escalates further (8+ same-turn
185
+ * failures) and `evaluateToolPolicy` ignores `execution_mode`, so it never downgrades a harness
186
+ * escalation that would have mattered.
187
+ */
188
+ createHarnessProposer(input: {
189
+ messages: ChatMessage[];
190
+ tools: ToolDefinition[];
191
+ sessionId: string;
192
+ userMessage: string;
193
+ maxIterations: number;
194
+ sources: AssistantSource[];
195
+ onToken?: (token: string) => void;
196
+ onToolStep?: (step: AssistantToolStep) => void;
197
+ onUsage?: (usage: TokenUsage) => void;
198
+ riskHint?: TurnIntentClassification['riskLevel'];
199
+ }): (toolCtx: ToolExecutorContext) => Promise<unknown>;
200
+ /**
201
+ * R3 of plans/harness_d2_one_loop_rewire_plan.html: the wiring-level counterpart to
202
+ * `createHarnessProposer` above — builds the same `messages`/`tools` shape `runToolLoop` builds
203
+ * (system prompt + transcript + user message; whichever of FILE_TOOLS/WEB_TOOLS/SHELL_TOOLS/
204
+ * ACTION_TOOLS/REMINDER_TOOLS are configured) so `assistant.ts`'s `runTurn` doesn't need to
205
+ * duplicate that list-building just to wire the flag-ON path, and caps iterations at
206
+ * `this.maxSteps` — the same cap `runToolLoop` applies — rather than something the caller has to
207
+ * choose. Returns the mutable `sources` array threaded into the proposer so the caller can read
208
+ * back whatever got pushed to it once the harness run using this proposer has finished (mirrors
209
+ * `ToolLoopResult`'s `sources` field on the flag-OFF path).
210
+ */
211
+ createOneLoopProposer(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel']): {
212
+ proposer: (toolCtx: ToolExecutorContext) => Promise<unknown>;
213
+ sources: AssistantSource[];
214
+ };
215
+ /**
216
+ * R4 of plans/harness_d2_one_loop_rewire_plan.html: the batch-research counterpart to
217
+ * `createOneLoopProposer` above — routes `runBatchToolLoop`'s probe → calibrate → confirm-gate
218
+ * → resolve-remaining → synthesize sequence through the same harness-driven proposer mechanism,
219
+ * instead of batch research remaining a second code path that (per R3) never reached the
220
+ * harness regardless of the flag. Reuses `resolveBatchItem`/`resolveRemainingBatchItems`/
221
+ * `synthesizeBatchReply` verbatim — the exact same methods the flag-OFF path calls — so the
222
+ * dead-end window (local `toolYields`, scoped inside `resolveBatchItem`) and the calibrated
223
+ * per-item budget (`resolveRemainingBatchItems`'s own `Budget`/`nextItemBudget` logic) keep
224
+ * bounding a hard item's spend identically; nothing here reimplements that math.
225
+ *
226
+ * The closure advances through three phases, one per `driveMainLoop` iteration (i.e. one
227
+ * `__harnessExecutionStatus: 'continue'` per phase transition) rather than one per underlying
228
+ * tool call the way the flat proposer does: 'probe' (resolve every probe item, calibrate, and
229
+ * either throw a `needs_approval` OneLoopPause — mirroring `runBatchToolLoop`'s own confirmation
230
+ * gate, including persisting the same `BatchPendingState` so the existing, flag-independent
231
+ * `resolvePendingBatchConfirmation` resume path needs no changes — or fall through), 'resolve'
232
+ * (resolve every remaining item in one `resolveRemainingBatchItems` call), and 'synthesize'
233
+ * (stream the final reply via `synthesizeBatchReply`, preserving its existing token-by-token
234
+ * `onToken` semantics, then return `'complete'`). This is coarser-grained resumability than the
235
+ * flat proposer's per-tool-call steps — a deliberate scope decision (see this phase's
236
+ * implementation note) rather than reimplementing `resolveRemainingBatchItems`'s calibration
237
+ * loop one item at a time just to get finer-grained checkpoints.
238
+ */
239
+ createBatchOneLoopProposer(items: string[], sessionId: string, userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void): {
240
+ proposer: (toolCtx: ToolExecutorContext) => Promise<unknown>;
241
+ sources: AssistantSource[];
242
+ getBatchBudget: () => BatchBudgetTrace | undefined;
243
+ };
244
+ /**
245
+ * Phase 4/4c: the deterministic, harness-state-informed authority for whether `toolName` may
246
+ * proceed — see tool-policy.ts. `controlState` is the live, per-turn ControlState built by
247
+ * tool-control-plane.ts and threaded down from runTurn (see createTurnControlPlaneState);
248
+ * `undefined` at the very first tool call of a turn (nothing recorded yet — the pre-evidence
249
+ * baseline tool-policy.ts's own doc comment describes) or for a caller that never wired one in.
250
+ */
251
+ private checkToolPolicy;
252
+ /**
253
+ * Bounded ReAct loop: calls callChatStructured with whichever of file/web/shell/reminder
254
+ * tools are configured, executing real (non-mutating) tool calls and looping, until
255
+ * either a final text reply comes back, a write_file/run_shell_command call needs
256
+ * staging + approval, or the iteration cap is hit. Only ever invoked when `fileTools`,
257
+ * `webTools`, or `shellTools` is configured (reminder tools ride along whenever any of
258
+ * those does, since `reminderStore` always exists).
259
+ */
260
+ runToolLoop(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel'], controlPlaneState?: TurnControlPlaneState): Promise<ToolLoopResult>;
261
+ /**
262
+ * The actual ReAct-style tool-calling loop, factored out of runToolLoop so a batch sub-loop
263
+ * (resolveBatchItem, below) can run the exact same iteration logic — including the
264
+ * looksLikeUnparsedToolCall retry guard and write/shell staging — scoped to its own message
265
+ * history and its own (usually much smaller) iteration budget, instead of duplicating it.
266
+ * `maxIterations` replaces runToolLoop's former direct use of `this.maxSteps`; passing
267
+ * `this.maxSteps` here reproduces that method's exact prior behavior unchanged.
268
+ *
269
+ * `onToolResult`, when provided, is called after every tool result is folded into `messages`
270
+ * and may return `'stop'` to end the loop immediately (see resolveBatchItem's item-scoped
271
+ * dead-end window) — a caller that never passes it (runToolLoop, the flat non-batch path)
272
+ * gets today's unmodified behavior: the loop only ever ends via a final answer, an
273
+ * needs_approval bail-out, or maxIterations.
274
+ *
275
+ * Thin wrapper (R1 of the D2 one-loop-rewire follow-up plan) around
276
+ * `runToolIterationStep`, which does one iteration's worth of work and returns a discriminated
277
+ * `done`/`not done` result — the shape a harness-driven `driveMainLoop` can call once per its
278
+ * own iteration (R2), instead of only ever being driven by this method's own `for` loop.
279
+ */
280
+ private runToolIterations;
281
+ /**
282
+ * One iteration's worth of `runToolIterations`' ReAct loop, extracted (R1 of the D2
283
+ * one-loop-rewire follow-up plan, `plans/harness_d2_one_loop_rewire_plan.html`) so a
284
+ * harness-driven proposer (R2) can call this directly, once per `driveMainLoop` iteration,
285
+ * instead of only ever being driven by `runToolIterations`' own `for` loop. Preserves every
286
+ * early-return branch of the original loop body exactly — the unparsed-tool-call retry,
287
+ * `__staged_action` adoption, write/shell/email staging, cached-shell replay, per-call
288
+ * `tool-policy.ts` DENY/REQUIRE_APPROVAL, and the `onToolResult` dead-end-window stop — as a
289
+ * `{ done: true, result }` return; the two cases that used to `continue` the outer `for` loop
290
+ * (the unparsed-tool-call retry and a cached-shell replay) become `{ done: false, ... }`
291
+ * instead, with `dispatchedAnyToolCall` threaded back to the caller since it must persist
292
+ * across iterations. `sources` is mutated in place (pushed to) rather than threaded through
293
+ * the result, since it's a plain accumulator shared by reference across every step call for
294
+ * the same loop.
295
+ */
296
+ private runToolIterationStep;
297
+ /**
298
+ * Resolves one batch item in its own bounded sub-loop — structurally the same
299
+ * runToolIterations call the flat loop uses, just seeded with a single-item-focused user
300
+ * message and a per-item budget instead of the whole conversation and `this.maxSteps`.
301
+ *
302
+ * The dead-end window (`toolYields`) is a local array, created fresh on every call to this
303
+ * method — never shared across items: a hard item that trips BATCH_DEAD_END_WINDOW
304
+ * consecutive dead_end results only stops *this* item's sub-loop early (rather than spending
305
+ * the rest of its budget on a dead page) and can never poison an easier item queued behind it.
306
+ */
307
+ private resolveBatchItem;
308
+ /**
309
+ * Resolves every item in `remainingItems` in its own sub-loop, recalibrating the per-item
310
+ * budget after each one via nextItemBudget instead of freezing it at the initial probe
311
+ * average, and stopping once the running total hits BATCH_ABSOLUTE_TURN_CEILING regardless of
312
+ * how favorable calibration still looks. Returns every resolution so far (probed + newly
313
+ * resolved) plus the names of any items never attempted because the ceiling was hit first.
314
+ * Public — also called by ActionApprovalService.resolvePendingBatchConfirmation on the
315
+ * approved-continuation path.
316
+ */
317
+ resolveRemainingBatchItems(probedResults: BatchItemResolution[], remainingItems: string[], systemPrompt: string, sessionId: string, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, controlPlaneState?: TurnControlPlaneState): Promise<{
318
+ resolutions: BatchItemResolution[];
319
+ notAttempted: string[];
320
+ }>;
321
+ /**
322
+ * Synthesizes one final reply from every item's per-item findings — same shape as the flat
323
+ * loop's own final-answer call, just seeded with structured per-item results instead of raw
324
+ * tool-call history for every item at once.
325
+ *
326
+ * Any item in `notAttempted` (the absolute ceiling was hit before it was ever reached) is
327
+ * appended as a deterministic, guaranteed-present list rather than left to the synthesis
328
+ * call's prose — an LLM asked to "write one well-organized reply" over many items can drop one
329
+ * from its summary the same way it can drop one from a longer todo list; the per-item
330
+ * `resolutions` themselves stay inside the model's synthesis (their found/not_found/
331
+ * truncated_while_productive wording is already baked into `content` by resolveBatchItem, so
332
+ * the model has no need to invent that part), but which items were never even attempted this
333
+ * turn is a plain fact, not something worth trusting to how well the model followed
334
+ * instructions. Public — also called by ActionApprovalService.
335
+ */
336
+ synthesizeBatchReply(userMessage: string, systemPrompt: string, resolutions: BatchItemResolution[], notAttempted: string[], onToken?: (token: string) => void, onUsage?: (usage: TokenUsage) => void): Promise<string>;
337
+ /**
338
+ * Gated entry point for the batch-research path: probes the first 1-2 items (keeping at least
339
+ * one item unprobed so a single sample can't swing the whole projection), calibrates a
340
+ * per-item budget off their real cost, and either pauses for confirmation (a large projection)
341
+ * or resolves every remaining item and synthesizes the final reply.
342
+ */
343
+ runBatchToolLoop(items: string[], sessionId: string, userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, controlPlaneState?: TurnControlPlaneState): Promise<ToolLoopResult>;
344
+ /**
345
+ * Dispatches one tool call by name to its executor. web_search/fetch_url results
346
+ * are wrapped as untrusted external content (and flagged if they look like an
347
+ * injection attempt) before they ever reach the model — file and reminder results
348
+ * are not, since they're the assistant's own workspace/state, not adversarial input.
349
+ */
350
+ private executeToolCall;
351
+ /**
352
+ * Trajectory Supervisor GATHER_EVIDENCE host (S5 of
353
+ * plans/harness_trajectory_supervisor_plan.html). A bounded, strictly read-only tool
354
+ * loop the harness calls (via HarnessRunOptions.runInvestigation) when the supervisor
355
+ * decides the run is stuck for lack of a fact.
356
+ *
357
+ * - Read-only only: suggested_tools is filtered through validateInvestigationTools()
358
+ * (INV-23) and then further to the subset this loop knows how to invoke from just a
359
+ * question string. write_file / run_shell_command / send_email are never in the
360
+ * allowlist and executeToolCall() has no branch for them here — no __staged_action
361
+ * path is reachable.
362
+ * - Its own Budget (INV-25), independent of the turn's batch ceiling: maxCalls =
363
+ * min(req.budget, allowed tools). Exhaustion returns whatever was found.
364
+ * - Every call is gated by the same evaluateToolPolicy() the manual dispatch loop uses;
365
+ * a DENY / REQUIRE_APPROVAL skips that tool (an investigation never prompts).
366
+ * - Faults are isolated: a throwing / empty tool call is skipped, never propagated.
367
+ */
368
+ runSupervisorInvestigation(req: InvestigationRequestData, opts?: {
369
+ riskHint: RiskLevel;
370
+ controlState?: Parameters<typeof evaluateToolPolicy>[0]['controlState'];
371
+ }): Promise<InvestigationFinding[]>;
372
+ }