@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.
- package/README.md +832 -0
- package/dist/action-approval-service.d.ts +52 -0
- package/dist/action-snapshot.d.ts +173 -0
- package/dist/action-tools.d.ts +38 -0
- package/dist/agent-loop.d.ts +372 -0
- package/dist/answer-claim.d.ts +51 -0
- package/dist/ask-clarification-service.d.ts +70 -0
- package/dist/ask-mode-flag.d.ts +25 -0
- package/dist/ask-response-format.d.ts +8 -0
- package/dist/assistant-session.d.ts +267 -0
- package/dist/assistant-source.d.ts +14 -0
- package/dist/assistant-types.d.ts +158 -0
- package/dist/assistant.d.ts +419 -0
- package/dist/batch-list-detector.d.ts +15 -0
- package/dist/batch-research-fixtures.d.ts +50 -0
- package/dist/benchmark-injected-failure.d.ts +27 -0
- package/dist/claude-cli-llm-client.d.ts +139 -0
- package/dist/claude-cli-prompt.d.ts +86 -0
- package/dist/cli-config.d.ts +27 -0
- package/dist/cli-session-D0uLdT2A.js +7863 -0
- package/dist/cli-session-D0uLdT2A.js.map +1 -0
- package/dist/cli-session.d.ts +63 -0
- package/dist/cli.d.ts +65 -0
- package/dist/cli.js +1693 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +175 -0
- package/dist/contradiction-checker.d.ts +27 -0
- package/dist/debug-log.d.ts +16 -0
- package/dist/decomposition-classifier.d.ts +38 -0
- package/dist/doctor-checks.d.ts +8 -0
- package/dist/email-smtp.d.ts +19 -0
- package/dist/email.d.ts +60 -0
- package/dist/error-classifier.d.ts +13 -0
- package/dist/execution-mode.d.ts +85 -0
- package/dist/fact-extraction.d.ts +143 -0
- package/dist/failure-mode-matcher.d.ts +32 -0
- package/dist/file-tools-mcp-server.mjs +1027 -0
- package/dist/file-tools.d.ts +179 -0
- package/dist/first-run.d.ts +22 -0
- package/dist/harness-bridge.d.ts +103 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/ink-test-render.d.ts +24 -0
- package/dist/lexical/patterns/batch-list-markers.json +8 -0
- package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/coding-fact-markers.json +8 -0
- package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/enumeration-markers.json +20 -0
- package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
- package/dist/lexical/patterns/fact-markers.json +16 -0
- package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
- package/dist/lexical/patterns/injection-patterns.json +20 -0
- package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
- package/dist/lexical/patterns/plan-mode-markers.json +10 -0
- package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
- package/dist/lexical/patterns/risk-patterns.json +122 -0
- package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
- package/dist/lexical/patterns/task-cancel-markers.json +22 -0
- package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
- package/dist/lexical/patterns/template-keywords.json +20 -0
- package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
- package/dist/lexical/patterns/tool-yield-markers.json +22 -0
- package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
- package/dist/lexical/patterns.d.ts +64 -0
- package/dist/memory-service.d.ts +219 -0
- package/dist/model-pricing.d.ts +19 -0
- package/dist/network-containment.d.ts +21 -0
- package/dist/node-config-store.d.ts +29 -0
- package/dist/node-display-names.d.ts +26 -0
- package/dist/node-fs-backend.d.ts +8 -0
- package/dist/non-interactive-mode.d.ts +22 -0
- package/dist/one-loop-flag.d.ts +38 -0
- package/dist/plan-approval-service.d.ts +62 -0
- package/dist/plan-builder.d.ts +32 -0
- package/dist/plan-drafting-service.d.ts +87 -0
- package/dist/plan-drafting.d.ts +58 -0
- package/dist/plan-mode-flag.d.ts +34 -0
- package/dist/plan-service.d.ts +61 -0
- package/dist/plan-sketch-service.d.ts +24 -0
- package/dist/plan-sketch.d.ts +20 -0
- package/dist/plan-store.d.ts +232 -0
- package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
- package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
- package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
- package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
- package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
- package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
- package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
- package/dist/plan-templates/index.d.ts +33 -0
- package/dist/plan-verification.d.ts +31 -0
- package/dist/reminder-tools.d.ts +13 -0
- package/dist/response-service.d.ts +73 -0
- package/dist/review-checker.d.ts +22 -0
- package/dist/risk-classifier.d.ts +7 -0
- package/dist/scripted-llm-client.d.ts +37 -0
- package/dist/semantic-criterion-coverage.d.ts +30 -0
- package/dist/shell-executor.d.ts +14 -0
- package/dist/shell-tools.d.ts +84 -0
- package/dist/spend-cap.d.ts +41 -0
- package/dist/spike.d.ts +1 -0
- package/dist/spike.js +18 -0
- package/dist/spike.js.map +1 -0
- package/dist/supervisor-decider.d.ts +3 -0
- package/dist/system-prompt.d.ts +8 -0
- package/dist/task-mapping.d.ts +42 -0
- package/dist/tool-control-plane.d.ts +67 -0
- package/dist/tool-policy.d.ts +45 -0
- package/dist/tool-step.d.ts +23 -0
- package/dist/tool-yield-classifier.d.ts +7 -0
- package/dist/trace-events.d.ts +126 -0
- package/dist/transcript-compaction.d.ts +13 -0
- package/dist/trust-tagging.d.ts +38 -0
- package/dist/tui-app-DxLPSGh2.js +481 -0
- package/dist/tui-app-DxLPSGh2.js.map +1 -0
- package/dist/tui-app.d.ts +125 -0
- package/dist/tui-input.d.ts +51 -0
- package/dist/tui-mode-flag.d.ts +36 -0
- package/dist/tui-output-capture.d.ts +30 -0
- package/dist/tui-spike.d.ts +1 -0
- package/dist/turn-intent-classifier.d.ts +96 -0
- package/dist/turn-interpreter.d.ts +71 -0
- package/dist/turn-policy.d.ts +45 -0
- package/dist/web-fetch-core.d.ts +65 -0
- package/dist/web-search-provider.d.ts +12 -0
- package/dist/web-tools.d.ts +26 -0
- 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
|
+
}
|