@buildaharness/aielia 0.3.4 → 0.3.6

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 (113) hide show
  1. package/README.md +156 -35
  2. package/dist/PlanGraphPane.d.ts +19 -0
  3. package/dist/_memory-core-generated.d.ts +1764 -0
  4. package/dist/action-approval-service.d.ts +34 -2
  5. package/dist/action-record.d.ts +13 -0
  6. package/dist/activity-log.d.ts +4 -0
  7. package/dist/agent-loop.d.ts +106 -9
  8. package/dist/ambiguity-guard-flag.d.ts +12 -0
  9. package/dist/ambiguity-guard.d.ts +20 -0
  10. package/dist/answer-claim.d.ts +13 -1
  11. package/dist/ask-clarification-service.d.ts +12 -1
  12. package/dist/ask-response-format.d.ts +6 -0
  13. package/dist/assistant-session.d.ts +32 -2
  14. package/dist/assistant-source.d.ts +7 -0
  15. package/dist/assistant-types.d.ts +20 -2
  16. package/dist/assistant.d.ts +160 -6
  17. package/dist/benchmark-injected-failure.d.ts +6 -0
  18. package/dist/bin-BYRl4xXM.js +4985 -0
  19. package/dist/bin-BYRl4xXM.js.map +1 -0
  20. package/dist/cli-config.d.ts +3 -0
  21. package/dist/cli-session.d.ts +19 -5
  22. package/dist/cli.d.ts +26 -2
  23. package/dist/cli.js +1 -1
  24. package/dist/code-plan-templates.d.ts +21 -0
  25. package/dist/config.d.ts +31 -0
  26. package/dist/constraint-check.d.ts +35 -0
  27. package/dist/contradiction-checker.d.ts +7 -0
  28. package/dist/debug-log.d.ts +10 -1
  29. package/dist/decomposed-answer.d.ts +13 -0
  30. package/dist/decomposition-classifier.d.ts +2 -0
  31. package/dist/doctor-checks.d.ts +3 -0
  32. package/dist/email.d.ts +5 -3
  33. package/dist/episodic-digest.d.ts +89 -0
  34. package/dist/fact-extraction.d.ts +39 -0
  35. package/dist/file-tools-mcp-server.mjs +288 -37
  36. package/dist/file-tools.d.ts +6 -0
  37. package/dist/first-run.d.ts +0 -2
  38. package/dist/git-exclude.d.ts +9 -0
  39. package/dist/goal-graph-store.d.ts +18 -18
  40. package/dist/grounding-check.d.ts +33 -0
  41. package/dist/harness-bridge.d.ts +75 -2
  42. package/dist/harness-token-budget.d.ts +9 -0
  43. package/dist/index.d.ts +21 -2
  44. package/dist/index.js +166 -94
  45. package/dist/index.js.map +1 -1
  46. package/dist/ink-select-prompt.d.ts +2 -0
  47. package/dist/ink-test-render.d.ts +2 -0
  48. package/dist/layer-policy-flag.d.ts +13 -0
  49. package/dist/layer-policy-wiring.d.ts +77 -0
  50. package/dist/layer-settings.d.ts +49 -0
  51. package/dist/layer-telemetry.d.ts +2 -0
  52. package/dist/lexical/lexical-mode.d.ts +29 -0
  53. package/dist/memory-consolidation.d.ts +80 -0
  54. package/dist/memory-governance.d.ts +30 -0
  55. package/dist/memory-reviewer.d.ts +114 -0
  56. package/dist/memory-service.d.ts +356 -9
  57. package/dist/model-json.d.ts +17 -0
  58. package/dist/next-step-context.d.ts +51 -0
  59. package/dist/next-step-proposer.d.ts +10 -2
  60. package/dist/plan-drafting-service.d.ts +3 -0
  61. package/dist/plan-graph-flag.d.ts +20 -0
  62. package/dist/plan-question.d.ts +60 -0
  63. package/dist/plan-service.d.ts +6 -2
  64. package/dist/plan-step-prompt.d.ts +15 -0
  65. package/dist/plan-store.d.ts +12 -1
  66. package/dist/plan-viz/normalize.d.ts +14 -0
  67. package/dist/plan-viz/plan-detail-lines.d.ts +3 -0
  68. package/dist/plan-viz/plan-layout.d.ts +48 -0
  69. package/dist/plan-viz/plan-nav.d.ts +29 -0
  70. package/dist/plan-viz/plan-raster.d.ts +36 -0
  71. package/dist/plan-viz/plan-routes.d.ts +6 -0
  72. package/dist/plan-viz/plan-snapshot.d.ts +73 -0
  73. package/dist/plan-viz/plan-viewport.d.ts +29 -0
  74. package/dist/plan-viz/plan-width.d.ts +15 -0
  75. package/dist/plan-viz/testkit/display-width.d.ts +12 -0
  76. package/dist/plan-viz/testkit/generators.d.ts +61 -0
  77. package/dist/plan-viz/testkit/grid-oracles.d.ts +38 -0
  78. package/dist/plan-viz/testkit/nav-oracles.d.ts +79 -0
  79. package/dist/plan-viz/testkit/types.d.ts +8 -0
  80. package/dist/plan-viz/types.d.ts +34 -0
  81. package/dist/{provider-setup-CPbB5y_V.js → provider-setup-CV3S_TAS.js} +10792 -5731
  82. package/dist/provider-setup-CV3S_TAS.js.map +1 -0
  83. package/dist/recall-tool.d.ts +43 -0
  84. package/dist/recovery-note.d.ts +22 -0
  85. package/dist/reply-audit.d.ts +67 -0
  86. package/dist/response-service.d.ts +11 -1
  87. package/dist/review-checker.d.ts +10 -4
  88. package/dist/reviewer-revision.d.ts +23 -0
  89. package/dist/risk-classifier.d.ts +7 -0
  90. package/dist/scripted-llm-client.d.ts +26 -1
  91. package/dist/semantic-compaction.d.ts +15 -0
  92. package/dist/semantic-hypotheses.d.ts +40 -0
  93. package/dist/side-call-policy.d.ts +30 -0
  94. package/dist/source-reliability.d.ts +71 -0
  95. package/dist/task-completion-check.d.ts +24 -0
  96. package/dist/tool-control-plane.d.ts +33 -1
  97. package/dist/tool-effect-class.d.ts +9 -0
  98. package/dist/tool-step.d.ts +6 -0
  99. package/dist/trace-events.d.ts +1 -1
  100. package/dist/transcript-compaction.d.ts +9 -0
  101. package/dist/trust-tagging.d.ts +2 -0
  102. package/dist/{tui-app-DeyZ4Pr9.js → tui-app-CJq6YaiP.js} +436 -21
  103. package/dist/tui-app-CJq6YaiP.js.map +1 -0
  104. package/dist/tui-app.d.ts +73 -0
  105. package/dist/turn-abort.d.ts +18 -0
  106. package/dist/turn-intent-classifier.d.ts +81 -14
  107. package/dist/turn-interpreter.d.ts +27 -3
  108. package/dist/version.d.ts +1 -1
  109. package/package.json +6 -6
  110. package/dist/bin-CdVTAe7j.js +0 -2209
  111. package/dist/bin-CdVTAe7j.js.map +0 -1
  112. package/dist/provider-setup-CPbB5y_V.js.map +0 -1
  113. package/dist/tui-app-DeyZ4Pr9.js.map +0 -1
@@ -4,9 +4,24 @@ import { ShellToolsContext } from './shell-tools.js';
4
4
  import { ActionToolsContext } from './action-tools.js';
5
5
  import { AssistantTurnResult } from './assistant-types.js';
6
6
  import { AssistantSession } from './assistant-session.js';
7
- import { AgentLoop } from './agent-loop.js';
7
+ import { AgentLoop, ToolLoopResult } from './agent-loop.js';
8
+ import { AssistantToolStep } from './tool-step.js';
8
9
  import { TraceEvent } from './trace-events.js';
9
10
  import { DebugLogEntry } from './debug-log.js';
11
+ /**
12
+ * Optional hooks for resuming a staged action that a flat tool loop paused on (see
13
+ * AgentLoop.resumeToolLoop): the live token/tool-step callbacks of the turn being resumed, and how
14
+ * to turn a *further* pause (another approval, or an escalation) into the turn result — the caller
15
+ * owns that because auto-approve modes live there, not here.
16
+ */
17
+ export interface ResumeOptions {
18
+ onToken?: (token: string) => void;
19
+ onToolStep?: (step: AssistantToolStep) => void;
20
+ handleLoopPause?: (result: Extract<ToolLoopResult, {
21
+ kind: 'needs_approval' | 'escalated';
22
+ }>) => Promise<AssistantTurnResult>;
23
+ }
24
+ export { actionRecordSuffix } from './action-record.js';
10
25
  /**
11
26
  * Owns the "approval-by-ID" pattern: a staged write/shell/revert/batch-research action resolved
12
27
  * by ID, never re-derived from a second LLM call (see T4 of the file-tools plan). Split out in
@@ -25,8 +40,25 @@ export declare class ActionApprovalService {
25
40
  private readonly onTrace;
26
41
  private readonly onDebugLog;
27
42
  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);
43
+ /** Every write, command and email carried out through an approval, in order (a turn's slice is what the system recorded for it). */
44
+ readonly appliedActions: string[];
45
+ /** Parallel to appliedActions (same indices): for a write, the lines it changed as a short diff, so the reply audit can tell whether a reply's account of the file is true (W1); the same clause as appliedActions for every other kind. */
46
+ readonly appliedActionDetails: string[];
47
+ /** The command and the start/end of the output of each shell command run through an approval (for the reply audit). */
48
+ readonly recentCommandOutputs: {
49
+ command: string;
50
+ output: string;
51
+ }[];
52
+ /** Sessions in which a command got the network-containment refusal. */
53
+ private readonly networkDeniedSessions;
54
+ /** Pending-action ids currently being resolved (see resolvePendingAction). */
55
+ private readonly inFlightResolutions;
56
+ networkDenied(sessionId: string): boolean;
28
57
  /** 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>;
58
+ resolvePendingAction(sessionId: string, transcriptKey: string, pendingActionId: string, approved: boolean, userMessage: string, resume?: ResumeOptions): Promise<AssistantTurnResult>;
59
+ private resolvePendingActionOnce;
60
+ /** The conversation before the current user message, for the no-tools synthesis call. The current message was already appended to the transcript when the turn paused, so it is dropped here. */
61
+ private recentHistory;
30
62
  /**
31
63
  * After resolvePendingAction resolves one staged action (approved or declined), checks whether
32
64
  * it was chained to a second approval-gated action staged from the same originating turn (see
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The "[Recorded by the system ...]" action list. Only the system writes it, into the stored transcript, from the
3
+ * approved actions it really carried out. The model sees it in earlier stored replies and was observed imitating it
4
+ * (benchmark scenarios 05 and 13: a reply listed a test file and writes that never happened), which makes a false
5
+ * claim look like a system record. Anything shaped like the marker in model-written text is therefore removed.
6
+ */
7
+ export declare const ACTION_RECORD_MARKER = "[Recorded by the system, not part of the reply";
8
+ export declare function actionRecordSuffix(actions: string[]): string;
9
+ export declare function containsActionRecord(text: string): boolean;
10
+ /** Removes every block that starts at the marker and runs to its closing bracket, with the blank lines before it. */
11
+ export declare function stripActionRecord(text: string): string;
12
+ /** Shown in place of a forged record, so the user knows why the list is gone. */
13
+ export declare const FORGED_ACTION_RECORD_NOTE = "[Note from the system: the reply listed actions as \"recorded by the system\", but the system recorded no such action this turn, so the list was removed.]";
@@ -0,0 +1,4 @@
1
+ import { DebugLogEntry } from './debug-log.js';
2
+ export declare function activityLogEnabled(env: NodeJS.ProcessEnv): boolean;
3
+ /** Returns an `onDebugLog` callback appending `{at, sessionId, kind, content}` lines to `filePath`. Never throws. */
4
+ export declare function createActivityLogger(filePath: string, now?: () => Date): (entry: DebugLogEntry) => void;
@@ -1,5 +1,5 @@
1
1
  import { ToolExecutorContext, HarnessPauseSignal, InvestigationRequestData, InvestigationFinding } from '@buildaharness/harness';
2
- import { MemoryAdapter, ILLMClient, ChatMessage, TokenUsage, ToolDefinition, ReminderStore } from '@buildaharness/runtime';
2
+ import { MemoryAdapter, ILLMClient, ChatMessage, ToolCallResult, TokenUsage, ToolDefinition, ReminderStore } from '@buildaharness/runtime';
3
3
  import { AssistantSource } from './assistant-source.js';
4
4
  import { DebugLogEntry } from './debug-log.js';
5
5
  import { TraceEvent } from './trace-events.js';
@@ -10,7 +10,9 @@ import { FileToolsContext } from './file-tools.js';
10
10
  import { WebToolsContext } from './web-tools.js';
11
11
  import { ShellToolsContext } from './shell-tools.js';
12
12
  import { ActionToolsContext } from './action-tools.js';
13
+ import { DigestReader } from './recall-tool.js';
13
14
  import { AssistantToolStep } from './tool-step.js';
15
+ import { EscalationPlan } from './layer-policy-wiring.js';
14
16
  export type ToolLoopResult = {
15
17
  kind: 'final';
16
18
  content: string;
@@ -21,10 +23,54 @@ export type ToolLoopResult = {
21
23
  reason: string;
22
24
  pendingActionId: string;
23
25
  pendingActionKind: 'write' | 'shell' | 'email' | 'batch';
26
+ resume?: StagedCallResume;
24
27
  } | {
25
28
  kind: 'escalated';
26
29
  reason: string;
27
30
  };
31
+ /**
32
+ * Set on a needs_approval result when the pause came from a tool call this loop dispatched itself
33
+ * (a proxy-backend `write_file`/`run_shell_command`/`send_email` call): the exact call the model
34
+ * made, so the loop can be continued once the staged action resolves instead of ending the turn.
35
+ * Absent for a pause whose call lives inside a backend's own agentic loop (claude-cli's
36
+ * `__staged_action`) — there is no message history to continue from.
37
+ */
38
+ export interface StagedCallResume {
39
+ toolCall: ToolCallResult;
40
+ assistantContent: string;
41
+ }
42
+ /**
43
+ * Persisted under `loop-pending:<pendingActionId>` while a flat tool loop waits on an approval, so
44
+ * an approved action's result can be folded back into the same conversation (see
45
+ * AgentLoop.resumeToolLoop). `messages` is everything sent to the model so far, system prompt
46
+ * included, *excluding* the assistant message holding the staged call (rebuilt on resume).
47
+ */
48
+ export interface ToolLoopPendingState {
49
+ sessionId: string;
50
+ userMessage: string;
51
+ riskHint: TurnIntentClassification['riskLevel'];
52
+ messages: ChatMessage[];
53
+ sources: AssistantSource[];
54
+ iterationsUsed: number;
55
+ toolCall: ToolCallResult;
56
+ assistantContent: string;
57
+ /** Approved actions already carried out earlier in this same turn (see describeAppliedAction) — recorded in the transcript with the turn's final reply so later turns know what was done. */
58
+ actions?: string[];
59
+ }
60
+ /** Sent when the model returns an empty final answer, before the one retry. */
61
+ /** For a reply that held only a line shaped like the system's action record: the actions it listed were not run, so unlike the blank-reply nudge this one sends the model back to its tools. */
62
+ export declare const RECORD_ONLY_REPLY_NUDGE = "Your last reply held only a line shaped like the system's action record, no answer, and the system ran nothing it listed. Do what the user asked now with your tools if it is still wanted, then reply in plain text with what you did and found. Never write a \"Recorded by the system\" line yourself.";
63
+ export declare const EMPTY_REPLY_NUDGE = "Your last reply was empty. This check is automatic and is not from the user. Answer the user's message now in plain text: if you did work this turn, say what you did and what you found; if the message needs no work, just answer it. Do not call a tool unless something is still unfinished.";
64
+ /** The nudge with the user's message quoted, so the model answers that message and not the nudge (benchmark scenario 12 turn 1: told to "summarize", it answered a summary request the user never made). */
65
+ export declare function withUserMessage(nudge: string, userMessage: string): string;
66
+ /** Sent when a turn used all its tool steps, before the one tool-less summary call. */
67
+ export declare const OUT_OF_STEPS_NUDGE = "You have used all the tool steps available for this turn. Reply now in plain text: say what you did, what worked, and what is still unfinished. Do not call any tool.";
68
+ /**
69
+ * The reply used when the model returns nothing twice in a row: built only from what is on record, so the user
70
+ * never gets an empty message. `actions` is the turn's list of carried-out approved actions (see describeAppliedAction).
71
+ */
72
+ export declare function fallbackFinalReply(actions: string[] | undefined): string;
73
+ export declare const toolLoopPendingKey: (pendingActionId: string) => string;
28
74
  /**
29
75
  * R2 of the internal plan: thrown by a harness-driven proposer
30
76
  * (createHarnessProposer, below) when runToolIterationStep produces a 'needs_approval' or
@@ -133,13 +179,6 @@ export interface BatchBudgetTrace {
133
179
  * path and both resolvePendingBatchConfirmation outcomes (see ActionApprovalService). */
134
180
  export declare function buildBatchBudgetTrace(itemCount: number, projectedTotal: number, resolutions: BatchItemResolution[]): BatchBudgetTrace;
135
181
  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
182
  export declare class AgentLoop {
144
183
  private readonly memory;
145
184
  private readonly llmClient;
@@ -204,6 +243,19 @@ export declare class AgentLoop {
204
243
  * caller-constraint output contract.
205
244
  */
206
245
  takeSteeringNotes?: () => string[];
246
+ /**
247
+ * Plan steps: the instruction to give the model when the harness starts working on `taskId`, or
248
+ * undefined for a task that is just "answer the user's message" (every ordinary turn). Sent once per
249
+ * task; the step's final answer is then kept in the conversation so a later step can build on it.
250
+ */
251
+ stepInstruction?: (taskId: string) => string | undefined;
252
+ /**
253
+ * True while the harness is running several subtasks that all stand for ONE answer to the user's
254
+ * message (an ordinary decomposed turn, not a plan). The first subtask to finish produces the answer;
255
+ * the rest reuse it instead of asking the model the same question again. A task the harness runs a
256
+ * second time (a retry after a rejection) gets a fresh call, never the answer that was rejected.
257
+ */
258
+ shareAnswer?: () => boolean;
207
259
  }): (toolCtx: ToolExecutorContext) => Promise<unknown>;
208
260
  /**
209
261
  * R3 of the internal plan: the wiring-level counterpart to
@@ -216,7 +268,7 @@ export declare class AgentLoop {
216
268
  * back whatever got pushed to it once the harness run using this proposer has finished (mirrors
217
269
  * `ToolLoopResult`'s `sources` field on the flag-OFF path).
218
270
  */
219
- createOneLoopProposer(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel'], takeSteeringNotes?: () => string[]): {
271
+ createOneLoopProposer(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel'], takeSteeringNotes?: () => string[], stepInstruction?: (taskId: string) => string | undefined, shareAnswer?: () => boolean): {
220
272
  proposer: (toolCtx: ToolExecutorContext) => Promise<unknown>;
221
273
  sources: AssistantSource[];
222
274
  };
@@ -266,6 +318,25 @@ export declare class AgentLoop {
266
318
  * those does, since `reminderStore` always exists).
267
319
  */
268
320
  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>;
321
+ /** One last model call without tools after the step budget ran out; undefined if it fails or returns nothing. */
322
+ private summarizeWhenOutOfSteps;
323
+ /** Replaces an empty final answer (the model returned nothing twice) with fallbackFinalReply, shown through onToken like any answer. */
324
+ private withFallbackReply;
325
+ private flatLoopTools;
326
+ /**
327
+ * When the flat loop pauses on a staged call it dispatched itself, saves what is needed to keep
328
+ * going after the approval (see ToolLoopPendingState) and strips the in-memory `resume` handle
329
+ * from the result the caller sees. Any other result is returned untouched.
330
+ */
331
+ private persistPausedLoop;
332
+ /**
333
+ * Continues a flat tool loop that paused on an approval-gated call, after that call's staged
334
+ * action has been resolved: `toolResultText` (the command's output, a write confirmation, ...)
335
+ * goes back to the model as the tool result for the exact call it made, and the loop runs on —
336
+ * more reads, further approvals (which pause again and persist again), then a final answer —
337
+ * within whatever iteration budget the turn had left.
338
+ */
339
+ resumeToolLoop(state: ToolLoopPendingState, toolResultText: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, actions?: string[] | undefined): Promise<ToolLoopResult>;
269
340
  /**
270
341
  * The actual ReAct-style tool-calling loop, factored out of runToolLoop so a batch sub-loop
271
342
  * (resolveBatchItem, below) can run the exact same iteration logic — including the
@@ -301,7 +372,21 @@ export declare class AgentLoop {
301
372
  * the result, since it's a plain accumulator shared by reference across every step call for
302
373
  * the same loop.
303
374
  */
375
+ /**
376
+ * The tool error for a write_file over an existing file the session has not read, or undefined when the write may go ahead (a new
377
+ * file, or one this session read or wrote). Benchmark scenario 13 turn 4: after a turn with no tool call at all, the model answered
378
+ * "fix that too" with a write_file that replaced `calculate_priority` with an invented function of a different signature, never having
379
+ * read the file. A write is a full replacement, so it has to be based on what the file really holds.
380
+ */
381
+ private unreadExistingFile;
304
382
  private runToolIterationStep;
383
+ /**
384
+ * A staging call (write_file / run_shell_command / send_email) was rejected before anything was staged — a path or
385
+ * cwd outside the workspace, a malformed recipient. The tool descriptions promise "rejected immediately": that is a
386
+ * tool error the model sees and can correct, exactly like a failed read, not an exception that ends the turn.
387
+ * Every tool call in the response gets a result (the provider requires one per call id).
388
+ */
389
+ private stagingRejected;
305
390
  /**
306
391
  * Resolves one batch item in its own bounded sub-loop — structurally the same
307
392
  * runToolIterations call the flat loop uses, just seeded with a single-item-focused user
@@ -355,6 +440,18 @@ export declare class AgentLoop {
355
440
  * injection attempt) before they ever reach the model — file and reminder results
356
441
  * are not, since they're the assistant's own workspace/state, not adversarial input.
357
442
  */
443
+ /** AL8b: optional policy gate for LLM injection detection; `undefined` (default) ⇒ always detect. */
444
+ injectionDetectionGate?: () => boolean;
445
+ /** Episodic digest source for `recall_memory` (M3); set by the assistant. The tool is offered only when this is set AND AUDIT_RECALL_TOOL is on. */
446
+ digestReader?: DigestReader;
447
+ /**
448
+ * Set by the assistant: whether this session has already read (or itself written) a workspace file. A write over an existing file
449
+ * that the session never read is refused (see unreadExistingFile), so the model cannot replace a file with contents it guessed.
450
+ */
451
+ sessionKnowsFile?: (sessionId: string, workspaceRelativePath: string) => boolean;
452
+ private recallTools;
453
+ /** The opt-in layers' plan for the turn in flight (adaptive mode only), set by the assistant each turn; see resolveOptInPlan. */
454
+ optInPlan?: EscalationPlan;
358
455
  private executeToolCall;
359
456
  /**
360
457
  * Trajectory Supervisor GATHER_EVIDENCE host (S5 of
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Flag for the AL3a ambiguity guard — same shape as ask-mode-flag.ts (module-level default, value-level
3
+ * normalizer that warns on a typo, env resolver). 'disabled' (the default, AL-8) keeps every turn on the
4
+ * pre-AL3a routing byte-for-byte: an under-specified consequential request still reaches `needs_approval`
5
+ * with a guessed action. 'enabled' runs one bounded scope check (ambiguity-guard.ts) on consequential
6
+ * turns and answers with a clarifying question *before* anything is staged when the request does not
7
+ * determine the action.
8
+ */
9
+ export type AmbiguityGuardMode = 'enabled' | 'disabled';
10
+ export declare const DEFAULT_AMBIGUITY_GUARD_MODE: AmbiguityGuardMode;
11
+ export declare function normalizeAmbiguityGuardMode(raw: string | undefined, varName?: string): AmbiguityGuardMode;
12
+ export declare function resolveAmbiguityGuardMode(env: NodeJS.ProcessEnv): AmbiguityGuardMode;
@@ -0,0 +1,20 @@
1
+ import { ChatMessage, ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ /**
3
+ * AL3a — semantic pre-staging scope check. Root cause it addresses: the classifier gates a
4
+ * consequential request on *risk*, never on whether the request is *determinate*, and the tool loop
5
+ * stages whatever action the proposer guesses (write_file/run_shell_command stage unconditionally). So
6
+ * "Clear out the old ones." reached `needs_approval` with a guessed deletion — the approval prompt then
7
+ * authorizes a guess instead of resolving it. The system prompt's "ask when genuinely ambiguous" is
8
+ * advisory and the proposer ignores it once a tool is in reach.
9
+ *
10
+ * This is one bounded LLM call, made only on consequential turns and only when the guard flag is
11
+ * enabled — a semantic judgment, not a lexical gate (AL-2). It fails open to "not ambiguous": staging
12
+ * and the approval gate remain the safety net, so a failed check never blocks a clear request.
13
+ */
14
+ export interface AmbiguityCheck {
15
+ ambiguous: boolean;
16
+ /** One concrete clarifying question; only meaningful when `ambiguous`. */
17
+ question: string;
18
+ }
19
+ export declare function parseAmbiguityCheck(content: string): AmbiguityCheck | null;
20
+ export declare function checkRequestAmbiguity(message: string, llmClient: ILLMClient, recentTranscript: ChatMessage[], model?: string, onUsage?: (usage: TokenUsage) => void): Promise<AmbiguityCheck>;
@@ -1,4 +1,5 @@
1
1
  import { Evidence, VerificationResult } from '@buildaharness/harness';
2
+ import { GroundingResult } from './grounding-check.js';
2
3
  /**
3
4
  * Where the evidence backing a reply actually came from — 'tool_evidence' when at least one
4
5
  * real observation (a gather_evidence call, a real read_file/web_search result, ...) was
@@ -10,7 +11,9 @@ export type AnswerClaimSourceType = 'tool_evidence' | 'model_reasoning';
10
11
  * The four branches this plan's Validation section names explicitly — each backed by a real,
11
12
  * mechanically-derived signal (see buildAnswerClaim), never guessed from the reply text itself:
12
13
  * - 'verified': the verification layer found no critical failure and at least one layer
13
- * actually PASSed (not just SKIPPED) against real evidence.
14
+ * actually PASSed (not just SKIPPED) against real evidence — and, when the grounding check is
15
+ * on, the reply itself was compared against the raw tool results and matched them. The
16
+ * mechanical layers alone never look at the reply's content, so they cannot earn this.
14
17
  * - 'unverified_attempted': evidence was gathered, but no layer could independently confirm it
15
18
  * (all SKIPPED/no tool to check with) — an honest "couldn't verify," not a fake PASS.
16
19
  * - 'contradicted': the Contradiction layer flagged a conflict with an existing belief this turn.
@@ -32,6 +35,8 @@ export interface AnswerClaim {
32
35
  freshness: string | null;
33
36
  source_type: AnswerClaimSourceType;
34
37
  verification_status: AnswerClaimVerificationStatus;
38
+ /** One sentence naming what in the reply didn't match the tool results — set only when the grounding check found a discrepancy. */
39
+ grounding_note?: string;
35
40
  }
36
41
  /**
37
42
  * Builds an AnswerClaim from one turn's real harness output. `verification` is the last
@@ -48,4 +53,11 @@ export declare function buildAnswerClaim(input: {
48
53
  strength: number;
49
54
  feasibility: number;
50
55
  };
56
+ /**
57
+ * The reply-vs-tool-results check (grounding-check.ts). `undefined` means the check is switched
58
+ * off (`AUDIT_SEMANTIC_GROUNDING`) and `verified` keeps its mechanical-only meaning; any
59
+ * `GroundingResult` — including `not_checked` — means the check is on, so `verified` requires
60
+ * `grounded`.
61
+ */
62
+ grounding?: GroundingResult;
51
63
  }): AnswerClaim;
@@ -66,5 +66,16 @@ export declare class AskClarificationService {
66
66
  * back through `stageAndRespond` exactly like the first, via the same catch this resume goes
67
67
  * through.
68
68
  */
69
- resolvePendingClarification(sessionId: string, transcriptKey: string, pendingClarificationId: string, response: AskResponse | undefined, askModeEnabled: boolean): Promise<AssistantTurnResult>;
69
+ resolvePendingClarification(sessionId: string, transcriptKey: string, pendingClarificationId: string, response: AskResponse | undefined, askModeEnabled: boolean,
70
+ /**
71
+ * True when tool turns run through the one-loop proposer (the default). A resumed harness run has NO proposer, so it
72
+ * could only return the stored draft reply — empty for a tool turn, which is what the user got — and the answer,
73
+ * fed into the constraint pipeline, is never read by the proposer's model. In that mode the validated answer is
74
+ * handed to the ordinary turn pipeline instead (`{ fallThrough: true }`, the same shape a plan approval uses), and
75
+ * the paused run is discarded.
76
+ */
77
+ handOffToOrdinaryTurn?: boolean): Promise<AssistantTurnResult | {
78
+ fallThrough: true;
79
+ answerText: string;
80
+ }>;
70
81
  }
@@ -5,4 +5,10 @@ import { AskQuestion, AskResponse } from '@buildaharness/harness';
5
5
  * resolution (P8, continuing a plan draft), both of which fold the user's answer into a plain-text
6
6
  * follow-up message for their own "next call" rather than duplicating this formatting logic.
7
7
  */
8
+ /**
9
+ * The user-visible text of a batch of structured questions: each question, then its numbered options. Used for the
10
+ * debug log (a `needs_clarification` turn has `reply: null`, so its log line used to read "(no reply)") and by the
11
+ * eval arm, so the transcript a judge reads carries what the user was actually asked.
12
+ */
13
+ export declare function formatAskQuestions(questions: AskQuestion[]): string;
8
14
  export declare function formatAskResponse(questions: AskQuestion[], response: AskResponse): string;
@@ -64,6 +64,13 @@ export declare class AssistantSession {
64
64
  private readonly notifiedContradictions;
65
65
  constructor(memory: MemoryAdapter, checkpointStore: CheckpointStore, spendCap: SpendCapConfig | undefined, model: () => string | undefined, fileTools: FileToolsContext | undefined, shellTools: ShellToolsContext | undefined, actionTools: ActionToolsContext | undefined);
66
66
  private static notifiedContradictionsKey;
67
+ private static standingConstraintsKey;
68
+ /** Constraints the user stated earlier in this session ("Do not use tabs") — they govern later turns too. */
69
+ getStandingConstraints(sessionId: string): Promise<string[]>;
70
+ /** Drops the standing constraints at these 1-based positions (as shown to the classifier); returns what remains. */
71
+ liftStandingConstraints(sessionId: string, positions: readonly number[]): Promise<string[]>;
72
+ /** Adds constraints stated this turn to the session's standing list; returns the merged list. */
73
+ recordStandingConstraints(sessionId: string, stated: readonly string[]): Promise<string[]>;
67
74
  getNotifiedContradictions(sessionId: string): Promise<Set<string>>;
68
75
  recordNotifiedContradiction(sessionId: string, seen: Set<string>, value: string): Promise<void>;
69
76
  /** findContradictionNotice's own text, deduped once per session — see notifiedContradictions'
@@ -98,7 +105,10 @@ export declare class AssistantSession {
98
105
  * already correctly no-ops whenever no cap is configured, so this never enforces a cap that
99
106
  * isn't set.
100
107
  */
108
+ /** Tail of the chain serialising recordSpend's read-modify-write: the background memory reviewer records spend while a turn does, and the later write used to drop the other's cost. */
109
+ private spendChain;
101
110
  recordSpend(sessionId: string, usage: TokenUsage | undefined): Promise<void>;
111
+ private recordSpendNow;
102
112
  /** Read-only — null means plan mode has never been entered (or was already exited/cancelled) for this session (or this `threadId`, once Phase 5's per-thread rescoping applies — see `planModeKey`). */
103
113
  getPlanModeState(sessionId: string, threadId?: string): Promise<PlanModeState | null>;
104
114
  /**
@@ -120,7 +130,11 @@ export declare class AssistantSession {
120
130
  * (rather than the sequencer reading `memory` directly) so this remains the one place that reads
121
131
  * `transcript:${sessionId}` for a live turn.
122
132
  */
123
- loadAndCompactTranscript(sessionId: string): Promise<ChatMessage[]>;
133
+ loadAndCompactTranscript(sessionId: string,
134
+ /** AUDIT_SEMANTIC_COMPACTION: summarizes the older messages instead of truncating them (semantic-compaction.ts). */
135
+ summarize?: (older: ChatMessage[]) => Promise<string | null>,
136
+ /** AUDIT_EPISODIC_DIGEST: called with exactly the messages about to be dropped, before they are (M3 pre-compaction flush). Fail-open: a throw never blocks compaction. */
137
+ flush?: (older: ChatMessage[]) => Promise<unknown>): Promise<ChatMessage[]>;
124
138
  /**
125
139
  * Appends `message` to `transcriptKey`'s array — every site that used to call
126
140
  * `this.memory.set(transcriptKey, message, 'append')` directly now goes through here instead, so
@@ -129,9 +143,25 @@ export declare class AssistantSession {
129
143
  * caught and logged, never thrown — a search-indexing problem must never be able to break an
130
144
  * ordinary turn or lose the transcript message itself.
131
145
  */
132
- appendTranscriptMessage(sessionId: string, transcriptKey: string, message: {
146
+ /**
147
+ * A message the user sent while a turn was running that the turn's model call already took into account
148
+ * (a mid-turn steering note). It belongs in the conversation as its own user message, in order: held here
149
+ * and written just before the next assistant message, i.e. after the running turn's own user message and
150
+ * before the reply it shaped. Without this the note vanished from the transcript.
151
+ */
152
+ recordAbsorbedSteering(sessionId: string, message: string): void;
153
+ private readonly absorbedSteering;
154
+ /**
155
+ * Called by ResponseService the moment a turn's final reply is stored, before the slower post-reply work
156
+ * (fact recording, goal evidence, grounding check) that still has to finish inside the turn. The assistant uses it
157
+ * to put the reply in the activity log right away (benchmark issue N2).
158
+ */
159
+ replyReadyHook?: (sessionId: string, reply: string) => void;
160
+ replyReady(sessionId: string, reply: string): void;
161
+ appendTranscriptMessage(sessionId: string, transcriptKey: string, input: {
133
162
  role: 'user' | 'assistant';
134
163
  content: string;
164
+ actionRecord?: string[];
135
165
  }): Promise<void>;
136
166
  /**
137
167
  * One-off, idempotent backfill for installs that already had transcript history before the
@@ -11,4 +11,11 @@
11
11
  export interface AssistantSource {
12
12
  tool: 'read_file' | 'list_directory' | 'web_search' | 'fetch_url';
13
13
  path: string;
14
+ /**
15
+ * The (truncated) raw text the tool returned — what the answer-claim grounding check compares the reply against
16
+ * (grounding-check.ts). Internal only: ResponseService strips it before a source reaches AssistantTurnResult, so it
17
+ * never lands in a transcript, a trace or the UI. Absent when the result text isn't visible to the parent process
18
+ * (claude-cli runs the tool loop inside its own subprocess).
19
+ */
20
+ excerpt?: string;
14
21
  }
@@ -1,5 +1,5 @@
1
1
  import { NextStepSuggestion } from './next-step-proposer.js';
2
- import { RiskState, LayerActivityEvent, AskQuestion } from '@buildaharness/harness';
2
+ import { RiskState, LayerActivityEvent, AskQuestion, TaskStatus } from '@buildaharness/harness';
3
3
  import { TokenUsage } from '@buildaharness/runtime';
4
4
  import { AnswerClaim } from './answer-claim.js';
5
5
  import { TurnIntentClassification } from './turn-intent-classifier.js';
@@ -62,7 +62,7 @@ export interface AssistantTurnResult {
62
62
  * passes through this exactly once, regardless of risk (no risk-tiered skip — see
63
63
  * PlanApprovalService's doc comment).
64
64
  */
65
- status: 'ok' | 'needs_approval' | 'escalated' | 'needs_clarification' | 'needs_plan_approval';
65
+ status: 'ok' | 'needs_approval' | 'escalated' | 'needs_clarification' | 'needs_plan_approval' | 'cancelled';
66
66
  reply: string | null;
67
67
  /**
68
68
  * Up to three concrete next steps proposed after a full (non-trivial, `ok`) turn, most confident
@@ -101,10 +101,15 @@ export interface AssistantTurnResult {
101
101
  templateName: string | null;
102
102
  successCriteria: string;
103
103
  completionPct: number;
104
+ /** `note`: why a FAILED task did not count as done (set only by the semantic task-completion check). */
104
105
  tasks: {
105
106
  id: string;
106
107
  description: string;
107
108
  status: string;
109
+ note?: string;
110
+ dependsOn?: string[];
111
+ cancelled?: boolean;
112
+ riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH';
108
113
  }[];
109
114
  };
110
115
  /**
@@ -117,6 +122,10 @@ export interface AssistantTurnResult {
117
122
  usage?: TokenUsage;
118
123
  /** Set when the Contradiction layer flagged a conflict with an existing belief this turn — see assistant-session.ts's findContradictionNotice doc comment for why this is a separate field instead of folded into `reply`. */
119
124
  contradictionNotice?: string;
125
+ /** Set when the semantic change reviewer found the request conflicts with something the user stated earlier AND the answer could not be told so directly (a tool-less turn's draft already exists, so the proposer never reads the review note). Advisory only — the turn still completed. Mirrors `contradictionNotice`: a separate field, not folded into `reply`, because the CLI streams `reply` before the check runs. */
126
+ reviewNotice?: string;
127
+ /** Set when the reply audit (reply-audit.ts) found a claim of work the system did not record, a promise of work not done, or unverified facts about outside sources. Separate from `reply` for the same reason as `reviewNotice`. */
128
+ auditNotice?: string;
120
129
  /**
121
130
  * An epistemic-honesty signal for replies that went through the harness loop (absent on the
122
131
  * triviality fast path, same "absent when unused" convention as trace/sources — a
@@ -163,4 +172,13 @@ export interface AssistantProgress {
163
172
  currentNode?: string;
164
173
  /** Live, mid-run position within a durable plan — set only while a plan is actually driving this turn. Absent for an ad hoc single-task/decomposed turn, same "absent when unused" convention as AssistantTurnResult.planStatus. */
165
174
  planPosition?: PlanPosition;
175
+ /**
176
+ * Live per-task statuses of the plan driving this turn, for the plan graph view. Present only while a
177
+ * plan drives the turn (same "absent when unused" convention as `planPosition`). Granularity is per
178
+ * harness checkpoint (once per task), not per token: it changes when a task starts or finishes.
179
+ */
180
+ planTasks?: {
181
+ id: string;
182
+ status: TaskStatus;
183
+ }[];
166
184
  }