@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,51 @@
1
+ import { Evidence, VerificationResult } from '@buildaharness/harness';
2
+ /**
3
+ * Where the evidence backing a reply actually came from — 'tool_evidence' when at least one
4
+ * real observation (a gather_evidence call, a real read_file/web_search result, ...) was
5
+ * recorded this turn, 'model_reasoning' when the harness ran but produced no such observation
6
+ * (a plain-reasoning reply, still gated through the full loop).
7
+ */
8
+ export type AnswerClaimSourceType = 'tool_evidence' | 'model_reasoning';
9
+ /**
10
+ * The four branches this plan's Validation section names explicitly — each backed by a real,
11
+ * mechanically-derived signal (see buildAnswerClaim), never guessed from the reply text itself:
12
+ * - 'verified': the verification layer found no critical failure and at least one layer
13
+ * actually PASSed (not just SKIPPED) against real evidence.
14
+ * - 'unverified_attempted': evidence was gathered, but no layer could independently confirm it
15
+ * (all SKIPPED/no tool to check with) — an honest "couldn't verify," not a fake PASS.
16
+ * - 'contradicted': the Contradiction layer flagged a conflict with an existing belief this turn.
17
+ * - 'no_evidence': no real observation was gathered at all — a plain-reasoning answer.
18
+ */
19
+ export type AnswerClaimVerificationStatus = 'verified' | 'unverified_attempted' | 'contradicted' | 'no_evidence';
20
+ /**
21
+ * Phase 6 of the harness/assistant remediation plan: a first-class epistemic-honesty signal
22
+ * attached to a reply that went through the harness loop, distinguishing "this is true" from
23
+ * "I found X but couldn't independently verify it." Built entirely from real, already-computed
24
+ * harness signal (Phase 2/3's now-real verification layers, the EvidenceStore's real
25
+ * observations, the Contradiction layer's own finding) — never a new LLM judgment call.
26
+ */
27
+ export interface AnswerClaim {
28
+ evidence: Evidence[];
29
+ /** min(verificationHealth.strength, verificationHealth.feasibility) — the same real number chat-ui's "Why?" panel already buckets into plain language. */
30
+ confidence: number;
31
+ /** ISO timestamp of the least-fresh evidence backing this claim, or null when there's no evidence at all. */
32
+ freshness: string | null;
33
+ source_type: AnswerClaimSourceType;
34
+ verification_status: AnswerClaimVerificationStatus;
35
+ }
36
+ /**
37
+ * Builds an AnswerClaim from one turn's real harness output. `verification` is the last
38
+ * VerificationResult HarnessRuntime's onVerification hook reported this turn (null when the
39
+ * turn never reached verify() at all — e.g. the harness was skipped by the triviality fast
40
+ * path, in which case the caller should not attach an AnswerClaim at all rather than call this
41
+ * with a fabricated null).
42
+ */
43
+ export declare function buildAnswerClaim(input: {
44
+ evidence: Evidence[];
45
+ verification: VerificationResult | null;
46
+ contradicted: boolean;
47
+ verificationHealth: {
48
+ strength: number;
49
+ feasibility: number;
50
+ };
51
+ }): AnswerClaim;
@@ -0,0 +1,70 @@
1
+ import { AskQuestion, AskResponse } from '@buildaharness/harness';
2
+ import { MemoryAdapter } from '@buildaharness/runtime';
3
+ import { AssistantSession } from './assistant-session.js';
4
+ import { HarnessBridge } from './harness-bridge.js';
5
+ import { ResponseService } from './response-service.js';
6
+ import { PlanRecord } from './plan-store.js';
7
+ import { TurnIntentClassification } from './turn-intent-classifier.js';
8
+ import { UserFact } from './fact-extraction.js';
9
+ import { AssistantTurnResult } from './assistant-types.js';
10
+ import { TraceEvent } from './trace-events.js';
11
+ /**
12
+ * Everything AskClarificationService.resolvePendingClarification needs to re-drive the harness
13
+ * run once the user answers — persisted alongside the staged questions themselves (mirrors
14
+ * AgentLoop's BatchPendingState, staged the same `memory.set('ask-pending:${id}', ...)` way, see
15
+ * ActionApprovalService's own "resolved by ID, never re-derived from a second LLM call"
16
+ * discipline this mirrors, T4 of the file-tools plan).
17
+ */
18
+ export interface AskClarificationPendingState {
19
+ sessionId: string;
20
+ questions: AskQuestion[];
21
+ classification: TurnIntentClassification;
22
+ activePlan: PlanRecord | null;
23
+ facts: UserFact[];
24
+ draftReply: string;
25
+ }
26
+ /**
27
+ * Owns the ask-question "staged clarification, resolved by ID" pattern — Q2 of
28
+ * plans/ask_question_and_plan_mode_plan.html, structurally mirroring ActionApprovalService (T4 of
29
+ * the file-tools plan: what gets resolved is exactly what was staged, never re-derived from a
30
+ * second LLM call). Unlike a write/shell action, resolving a clarification means resuming a
31
+ * *harness run* — the checkpoint HarnessBridge would otherwise have deleted on any thrown
32
+ * EscalationHalt (see HarnessBridge.run()'s askModeEnabled/preserveForClarification handling) is
33
+ * kept alive specifically so this service can hand it back to HarnessBridge with the user's
34
+ * answer folded in via a one-shot UpdateChannel, instead of losing the run's progress and
35
+ * starting over.
36
+ */
37
+ export declare class AskClarificationService {
38
+ private readonly memory;
39
+ private readonly session;
40
+ private readonly harnessBridge;
41
+ private readonly responseService;
42
+ private readonly onTrace;
43
+ constructor(memory: MemoryAdapter, session: AssistantSession, harnessBridge: HarnessBridge, responseService: ResponseService, onTrace: ((event: TraceEvent) => void) | undefined);
44
+ private pendingKey;
45
+ /**
46
+ * Stages a batch of questions and returns the `needs_clarification` AssistantTurnResult — the
47
+ * ResponseService.buildEscalatedResult counterpart for a structured-question escalation
48
+ * (called instead of it, from assistant.ts's EscalationHalt catch, when
49
+ * `err.blocker.questions` is populated and the effective askMode is enabled).
50
+ */
51
+ stageAndRespond(params: {
52
+ sessionId: string;
53
+ transcriptKey: string;
54
+ userMessage: string;
55
+ questions: AskQuestion[];
56
+ classification: TurnIntentClassification;
57
+ activePlan: PlanRecord | null;
58
+ facts: UserFact[];
59
+ draftReply: string;
60
+ }): Promise<AssistantTurnResult>;
61
+ /**
62
+ * Resolves a staged clarification by ID — validates the response against exactly the
63
+ * questions that were staged (INV-28: a malformed/incomplete payload is rejected, never
64
+ * silently coerced), folds the answer into the paused harness run via a one-shot
65
+ * UpdateChannel, and resumes it. A follow-up escalation (INV-37's deferred batch two) routes
66
+ * back through `stageAndRespond` exactly like the first, via the same catch this resume goes
67
+ * through.
68
+ */
69
+ resolvePendingClarification(sessionId: string, transcriptKey: string, pendingClarificationId: string, response: AskResponse | undefined, askModeEnabled: boolean): Promise<AssistantTurnResult>;
70
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pure logic backing PersonalAssistant's ASSISTANT_ASK_MODE handling — split out so it's
3
+ * unit-testable in isolation, mirroring one-loop-flag.ts's exact shape (module-level default,
4
+ * an injectable resolver so tests never touch real process.env).
5
+ *
6
+ * Q2 of plans/ask_question_and_plan_mode_plan.html: the "global flag" control point (tier 1 of
7
+ * Q1's three-tier INV-29 resolution) for the batched-questions ask-question mechanism, resolved
8
+ * at this package's boundary and handed to `@buildaharness/harness`'s `resolveAskMode()` as
9
+ * `globalEnabled`. 'disabled' (the default, for the whole rollout window, per Q1's
10
+ * `DEFAULT_ASK_MODE`) means every escalation stays on today's plain `escalated`/`reply: null`
11
+ * path — AskClarificationService is never reached. 'enabled' lets a populated
12
+ * `EscalationHalt.blocker.questions` promote to `needs_clarification` instead.
13
+ */
14
+ export type AskMode = 'enabled' | 'disabled';
15
+ export declare const DEFAULT_ASK_MODE: AskMode;
16
+ /**
17
+ * Value-level resolver, shared by every surface: `resolveAskMode` (CLI, reads
18
+ * `process.env.ASSISTANT_ASK_MODE`) and chat-ui's `envOverridesFromImportMetaEnv` (browser
19
+ * build, reads Vite's `import.meta.env.VITE_ASSISTANT_ASK_MODE`). An unset or empty value falls
20
+ * back to the default silently; an unrecognized non-empty value falls back with a startup
21
+ * warning naming `varName` — same typo-tolerance-with-a-warning convention
22
+ * `normalizeOneLoopMode` uses, since a silently-misread flag here is safety-relevant.
23
+ */
24
+ export declare function normalizeAskMode(raw: string | undefined, varName?: string): AskMode;
25
+ export declare function resolveAskMode(env: NodeJS.ProcessEnv): AskMode;
@@ -0,0 +1,8 @@
1
+ import { AskQuestion, AskResponse } from '@buildaharness/harness';
2
+ /**
3
+ * Renders a full AskResponse as one transcript-ready message — shared by
4
+ * AskClarificationService (Q2, resuming a paused harness run) and PlanDraftingService's nested-ask
5
+ * resolution (P8, continuing a plan draft), both of which fold the user's answer into a plain-text
6
+ * follow-up message for their own "next call" rather than duplicating this formatting logic.
7
+ */
8
+ export declare function formatAskResponse(questions: AskQuestion[], response: AskResponse): string;
@@ -0,0 +1,267 @@
1
+ import { CheckpointStore, LayerActivityEvent, ExternalContradictionInput } from '@buildaharness/harness';
2
+ import { MemoryAdapter, ChatMessage, TokenUsage, FsBackend } from '@buildaharness/runtime';
3
+ import { FileToolsContext } from './file-tools.js';
4
+ import { UndoLogEntry } from './action-snapshot.js';
5
+ import { ShellToolsContext } from './shell-tools.js';
6
+ import { ActionToolsContext } from './action-tools.js';
7
+ import { SpendCapConfig, SpendState } from './spend-cap.js';
8
+ /**
9
+ * P1 of plans/ask_question_and_plan_mode_plan.html — session/turn-routing switch, distinct from a
10
+ * tool-permission list. While `active`, `assistant.ts`'s turn entry point routes every incoming
11
+ * message for this session to `PlanDraftingService` instead of `TurnInterpreter`'s normal
12
+ * classify/tool-loop pipeline (see PlanDraftingService's own doc comment for the two designed
13
+ * exits). `draftId` is an opaque per-entry token (not a lookup key into anything today — the
14
+ * drafting `PlanRecord` itself still lives at plan-store.ts's single `plan:${sessionId}` slot,
15
+ * same as an ordinary plan) reserved for P2's approval flow to correlate "the draft that was
16
+ * staged" against "the draft the user is approving", the same "resolved by exactly what was
17
+ * staged, never re-derived" discipline ActionApprovalService/AskClarificationService already
18
+ * follow.
19
+ */
20
+ export interface PlanModeState {
21
+ active: boolean;
22
+ draftId: string;
23
+ }
24
+ export declare const RESUME_ATTEMPT_CAP = 2;
25
+ export declare const resumeAttemptsKey: (sessionId: string) => string;
26
+ /**
27
+ * One searchable, individually-addressable transcript entry — key `transcript-msg:<sessionId>:<n>`
28
+ * (see messageIndexKey). Written alongside every `transcript:<sessionId>` append by
29
+ * appendTranscriptMessage so scoreEntries() (via MemoryAdapter.search()) can resolve a hit to this
30
+ * one exchange instead of the whole session array. Derived, not authoritative: the per-session
31
+ * `transcript:<sessionId>` array remains the one source of truth for conversation replay,
32
+ * compaction, and /export — losing an index entry (a failed write, or a pre-backfill install) only
33
+ * ever degrades search recall, never conversation behavior.
34
+ */
35
+ export interface IndexedMessage {
36
+ sessionId: string;
37
+ role: 'user' | 'assistant';
38
+ content: string;
39
+ at: string;
40
+ }
41
+ /** One ranked `/search` result — an `IndexedMessage` plus the graduated relevance score `scoreEntries()` gave it. */
42
+ export interface TranscriptSearchHit {
43
+ sessionId: string;
44
+ role: 'user' | 'assistant';
45
+ content: string;
46
+ at: string;
47
+ score: number;
48
+ }
49
+ /**
50
+ * Owns session/transcript/checkpoint/spend/undo bookkeeping — the "everyday persistence" half of
51
+ * PersonalAssistant, split out in Phase 4d of the architecture remediation plan. `fileTools`/
52
+ * `shellTools` are the same shared option objects AgentLoop also holds (needed here for the
53
+ * shell-result-cache clear in clearSession, and the workspace lookup in undo/sweep) — not a
54
+ * duplication, just two collaborators independently needing the same read-only context.
55
+ */
56
+ export declare class AssistantSession {
57
+ private readonly memory;
58
+ private readonly checkpointStore;
59
+ private readonly spendCap;
60
+ private readonly model;
61
+ private readonly fileTools;
62
+ private readonly shellTools;
63
+ private readonly actionTools;
64
+ private readonly notifiedContradictions;
65
+ constructor(memory: MemoryAdapter, checkpointStore: CheckpointStore, spendCap: SpendCapConfig | undefined, model: () => string | undefined, fileTools: FileToolsContext | undefined, shellTools: ShellToolsContext | undefined, actionTools: ActionToolsContext | undefined);
66
+ private static notifiedContradictionsKey;
67
+ getNotifiedContradictions(sessionId: string): Promise<Set<string>>;
68
+ recordNotifiedContradiction(sessionId: string, seen: Set<string>, value: string): Promise<void>;
69
+ /** findContradictionNotice's own text, deduped once per session — see notifiedContradictions'
70
+ * doc comment and findContradictionNotice's for why this exists: without it, the always-on
71
+ * lexical Contradiction layer re-fires the identical notice on every subsequent non-trivial
72
+ * turn, since the WorldModel it runs against is rebuilt fresh (re-seeded from all known facts)
73
+ * each turn with no memory of its own that this exact conflict was already surfaced. */
74
+ dedupedContradictionNotice(sessionId: string, layerActivity: LayerActivityEvent[], extraContradictions?: ExternalContradictionInput[]): Promise<string | undefined>;
75
+ /** Persisted alongside transcript/facts/plan (this.memory) — survives a process restart, same as everything else keyed by sessionId, so the ceiling is genuinely cross-session, not just cross-turn within one process lifetime. */
76
+ getSpendState(sessionId: string): Promise<SpendState>;
77
+ /**
78
+ * Pre-turn spend-cap check — a no-op (`{ allowed: true }`) whenever no cap is configured. Only
79
+ * ever checked before a turn starts, never mid-turn (see spend-cap.ts's checkSpendCap doc
80
+ * comment) — `turn()` skips this entirely for a pendingActionId continuation, since that's a
81
+ * resumption of a turn that already passed this check when it first started.
82
+ */
83
+ checkSpendCapForTurn(sessionId: string): Promise<{
84
+ allowed: boolean;
85
+ reason?: string;
86
+ }>;
87
+ /**
88
+ * Called once per successfully completed ('ok') turn — counts turns, not raw internal LLM
89
+ * calls (see SpendCapConfig's doc comment for why). Estimates cost the same way cli.ts's
90
+ * withCostEstimate does for a backend that doesn't report a real costUsd, so the cap enforces
91
+ * against the same number /cost displays, not a second cost model.
92
+ *
93
+ * Always records, even when no cap is currently configured — the ledger must reflect true
94
+ * cumulative spend regardless of whether a cap happens to exist yet, or a session that chats
95
+ * for a while uncapped and only later runs `/config set sessionCostLimitUsd ...` would have
96
+ * every turn before that point silently excluded from the cumulative total checkSpendCap
97
+ * enforces against, understating real spend. The enforcement gate itself (checkSpendCapForTurn)
98
+ * already correctly no-ops whenever no cap is configured, so this never enforces a cap that
99
+ * isn't set.
100
+ */
101
+ recordSpend(sessionId: string, usage: TokenUsage | undefined): Promise<void>;
102
+ /** Read-only — null means plan mode has never been entered (or was already exited/cancelled) for this session. */
103
+ getPlanModeState(sessionId: string): Promise<PlanModeState | null>;
104
+ /**
105
+ * The one place `planMode.active` ever flips true — a fresh `draftId` per entry, even if a
106
+ * prior drafting session was cancelled without ever reaching approval, so a stale draftId can
107
+ * never be mistaken for the current one. Idempotent to call again while already active (returns
108
+ * a new state, same as a fresh entry) — callers that only ever call this once per drafting
109
+ * session (P3's future auto-trigger) never observe that.
110
+ */
111
+ enterPlanMode(sessionId: string): Promise<PlanModeState>;
112
+ /** Clears `planMode.active` — the only two designed callers are an explicit cancel phrase (PlanDraftingService) and P2's future approval resolution. */
113
+ exitPlanMode(sessionId: string): Promise<void>;
114
+ /** The session's conversation transcript, oldest first — same array `turn()` reads/appends to. Used by `/export`. */
115
+ getTranscript(sessionId: string): Promise<ChatMessage[]>;
116
+ /**
117
+ * Reads `sessionId`'s transcript and runs it through transcript-compaction.ts's compactTranscript,
118
+ * persisting the compacted array back when compaction actually collapsed anything — the first
119
+ * thing `runTurn()` did with the raw transcript before this split. Kept on AssistantSession
120
+ * (rather than the sequencer reading `memory` directly) so this remains the one place that reads
121
+ * `transcript:${sessionId}` for a live turn.
122
+ */
123
+ loadAndCompactTranscript(sessionId: string): Promise<ChatMessage[]>;
124
+ /**
125
+ * Appends `message` to `transcriptKey`'s array — every site that used to call
126
+ * `this.memory.set(transcriptKey, message, 'append')` directly now goes through here instead, so
127
+ * a per-message search index entry (transcript-msg:<sessionId>:<n> — see messageIndexKey) is
128
+ * always written alongside it, with no call site able to forget. The index write is best-effort:
129
+ * caught and logged, never thrown — a search-indexing problem must never be able to break an
130
+ * ordinary turn or lose the transcript message itself.
131
+ */
132
+ appendTranscriptMessage(sessionId: string, transcriptKey: string, message: {
133
+ role: 'user' | 'assistant';
134
+ content: string;
135
+ }): Promise<void>;
136
+ /**
137
+ * One-off, idempotent backfill for installs that already had transcript history before the
138
+ * message index existed: scans every `transcript:*` session currently in `this.memory` and
139
+ * indexes whichever messages don't already have a `transcript-msg:` entry, so pre-existing
140
+ * conversations become searchable too, not just messages sent after this shipped. Guarded by
141
+ * MESSAGE_INDEX_BACKFILL_VERSION_KEY so it only does real work once per install (and once more
142
+ * per future version bump); a second call is a cheap no-op. Only covers what's still present in
143
+ * the live (possibly already-compacted) transcript array — a session already compacted before
144
+ * backfill ran has already lost its older messages the same way /search would, a known,
145
+ * documented limitation rather than a bug (see README).
146
+ *
147
+ * Run fire-and-forget from PersonalAssistant's constructor, never awaited by a turn — a large
148
+ * pre-existing history must not delay the first prompt/render.
149
+ */
150
+ backfillMessageIndex(): Promise<void>;
151
+ /**
152
+ * Prunes stale `.pending-actions/` records on startup. A leftover record is harmless (never
153
+ * applied without a matching id) but unbounded, so this keeps the directory from growing
154
+ * forever across crashed/abandoned turns.
155
+ *
156
+ * Safety-first, not per-record: `stagePendingAction`'s records carry no `sessionId` (the id is a
157
+ * random UUID, unrelated to any session — see file-tools.ts), so there is no direct way to tie
158
+ * one staged record to one session's checkpoint. Rather than sweep blind, this skips the sweep
159
+ * entirely for this startup if ANY known session still has a checkpoint eligible for resume —
160
+ * the coarser, but always-safe, version of "never sweep a record that could still be
161
+ * legitimately resumed". Known sessions are discovered the same way backfillMessageIndex
162
+ * already does (transcript: key prefixes), so no new bookkeeping is added.
163
+ */
164
+ sweepAbandonedPendingActionsOnStartup(): Promise<void>;
165
+ /**
166
+ * Records a message-level risk-gate decline (the `needs_approval` branch with no
167
+ * `pendingActionId`) as a resolved, paired exchange, once the caller (cli.ts) knows the final
168
+ * answer was "no". Unlike the eager-append this deliberately avoids inside runTurn itself (the
169
+ * outcome isn't known yet at that point), this is safe: both the user message and a "declined"
170
+ * reply are appended together, atomically, only after the decline is already final — there is
171
+ * never a dangling, un-replied-to turn a later tool-enabled call could mistake for a live
172
+ * request.
173
+ *
174
+ * Without this, a message-level decline (unlike a tool-call-level one, which resolvePendingAction
175
+ * already persists) left zero trace at all: a later "did that unsubscribe actually happen?"
176
+ * question found nothing in the transcript and confidently denied the request was ever made,
177
+ * instead of correctly recalling that it was asked and declined.
178
+ */
179
+ recordDeclinedRequest(sessionId: string, userMessage: string, reason: string): Promise<void>;
180
+ /**
181
+ * Ends the current conversation: deletes the transcript, extracted facts, any active plan for
182
+ * this session, and the shell-result cache (see file-tools.ts's shell-result-cache doc comment
183
+ * — a fresh conversation shouldn't silently answer from a previous, unrelated conversation's
184
+ * shell results), plus a leftover in-flight-turn checkpoint if one exists (from an abandoned
185
+ * turn that never reached its normal cleanup). Deliberately leaves
186
+ * `experienceStore`/`reminderStore`/DURABLE_FACTS_KEY untouched — those are durable,
187
+ * cross-conversation learning, not per-conversation scratch state (see the README's "Three
188
+ * things live outside a single harness run" section).
189
+ */
190
+ clearSession(sessionId: string): Promise<void>;
191
+ /**
192
+ * Scoped recovery for a stuck harness checkpoint: clears just `turn:${sessionId}`'s checkpoint
193
+ * (and its resume-attempt count) without touching transcript/facts/plan — unlike clearSession
194
+ * (`/clear`/`/new`), which wipes the whole conversation. Returns `{ cleared: false }` when there
195
+ * was nothing to clear, so a caller can report "nothing stuck" instead of a false "cleared".
196
+ */
197
+ clearCheckpoint(sessionId: string): Promise<{
198
+ cleared: boolean;
199
+ stepsUsed?: number;
200
+ currentNode?: string;
201
+ }>;
202
+ /**
203
+ * Read-only counterpart to clearCheckpoint — reports whether `sessionId` has a checkpoint left
204
+ * behind by a prior turn and how many times in a row it has already failed to resume, without
205
+ * clearing anything. Lets a caller (cli.ts's `/checkpoint`) inspect before deciding whether to
206
+ * clear.
207
+ */
208
+ getCheckpointStatus(sessionId: string): Promise<{
209
+ present: boolean;
210
+ stepsUsed?: number;
211
+ currentNode?: string;
212
+ failedResumeAttempts: number;
213
+ }>;
214
+ /**
215
+ * Removes the most recent exchange from conversation history — a completed turn drops its
216
+ * user message and assistant reply (2 entries); a turn that ended in `needs_approval` before
217
+ * any reply was appended drops just the pending user message (1 entry). Only affects what the
218
+ * model remembers: a real `write_file`/`run_shell_command` effect from the undone turn is not
219
+ * reversed. Returns `{ undone: false }` on an empty transcript instead of throwing.
220
+ */
221
+ undoLastTurn(sessionId: string): Promise<{
222
+ undone: boolean;
223
+ }>;
224
+ /** The workspace backend/root a staged write/shell/revert action lives under — `undefined` when neither fileTools nor shellTools is configured (e.g. a webTools-only assistant). Public: also used by ActionApprovalService for the same lookup. */
225
+ undoWorkspace(): {
226
+ backend: FsBackend;
227
+ workspaceRoot: string;
228
+ } | undefined;
229
+ /** Real filesystem effects still on record as revertible, newest first — bounded by action-snapshot.ts's UNDO_LOG_MAX_ENTRIES retention cap. Backs `/undo-action` with no argument. Distinct from `/undo` (undoLastTurn above), which only forgets conversation history — see README's /undo-action section for the naming distinction. */
230
+ listUndoLogEntries(): Promise<UndoLogEntry[]>;
231
+ /**
232
+ * Stages a revert of undo-log entry `id` as its own approval-gated `PendingActionPayload` —
233
+ * reusing the exact same staging/approval machinery write_file/run_shell_command already use,
234
+ * rather than a new confirmation concept. Approve/decline it the same way any other staged
235
+ * action resolves: `turn('', { sessionId, approved, pendingActionId })`.
236
+ */
237
+ stageUndoAction(id: string): Promise<{
238
+ status: 'staged';
239
+ pendingActionId: string;
240
+ reason: string;
241
+ } | {
242
+ status: 'error';
243
+ message: string;
244
+ }>;
245
+ /**
246
+ * Ranked search over the per-message index (see appendTranscriptMessage/IndexedMessage), not
247
+ * the whole session transcript — a hit resolves to the one exchange that matched. Deliberately
248
+ * not scoped to a single sessionId: this is a single local install's memory namespace, and
249
+ * "what did I tell you about my dentist appointment" should find it regardless of which
250
+ * session it was said in.
251
+ *
252
+ * `MemoryAdapter.search()` scores every stored key in one pass (facts, reminders, experience
253
+ * data, the message index itself, its counters, ...), so this asks for every entry scoring
254
+ * above 0 rather than a small topK directly, then filters to `transcript-msg:` keys and
255
+ * truncates afterward — otherwise a real match could be pushed out of a small topK by
256
+ * unrelated non-transcript entries that happen to score higher. Read-only and synchronous over
257
+ * already-persisted data: never an LLM call, never a network request, never a mutation. Used
258
+ * by `/search`.
259
+ *
260
+ * `FileSystemAdapter.search()` (packages/runtime) throws if ANY file under the memory namespace
261
+ * fails to parse as JSON — e.g. a transcript file left truncated by a process kill mid-write.
262
+ * One corrupt entry unrelated to this query must not turn `/search` into an uncaught error for
263
+ * the user — degrade to "no results" instead, same fail-open posture as the other two
264
+ * `memory.search()` call sites (backfillMessageIndex, sweepAbandonedPendingActionsOnStartup).
265
+ */
266
+ searchTranscript(query: string, topK?: number): Promise<TranscriptSearchHit[]>;
267
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A real, non-mutating tool call the model made while producing a reply — grounds a reply in something other
3
+ * than the model's own words. write_file is deliberately excluded: until approved, nothing was actually read or
4
+ * changed. `path` holds the file path for read_file/list_directory, the URL for fetch_url, or the query for
5
+ * web_search — same shape, different meaning per tool, to keep this interface minimal.
6
+ *
7
+ * Split into its own file (rather than living on assistant.ts or agent-loop.ts) so both can import it without
8
+ * either owning it — AgentLoop produces AssistantSource entries while executing tool calls, and assistant.ts's
9
+ * public AssistantTurnResult.sources field is typed with it.
10
+ */
11
+ export interface AssistantSource {
12
+ tool: 'read_file' | 'list_directory' | 'web_search' | 'fetch_url';
13
+ path: string;
14
+ }
@@ -0,0 +1,158 @@
1
+ import { RiskState, LayerActivityEvent, AskQuestion } from '@buildaharness/harness';
2
+ import { TokenUsage } from '@buildaharness/runtime';
3
+ import { AnswerClaim } from './answer-claim.js';
4
+ import { TurnIntentClassification } from './turn-intent-classifier.js';
5
+ import { AssistantSource } from './assistant-source.js';
6
+ import { BatchBudgetTrace } from './agent-loop.js';
7
+ import { PlanPosition } from './plan-store.js';
8
+ /**
9
+ * The public result/progress shapes every extracted module (ActionApprovalService,
10
+ * TurnInterpreter, HarnessBridge, ResponseService) builds or consumes — split into its own file
11
+ * so those modules can depend on the shapes without depending on assistant.ts itself (which, in
12
+ * turn, depends on all of them to assemble the sequencer). assistant.ts re-exports these under
13
+ * their original names for full backward compatibility with existing imports
14
+ * (index.ts/cli.ts/cli-session.ts/assistant.test.ts).
15
+ */
16
+ export interface AssistantTrace {
17
+ nodeExecutionOrder: string[];
18
+ verificationHealth: {
19
+ strength: number;
20
+ feasibility: number;
21
+ };
22
+ /** Every one of the 11 harness layers' fired/skipped report for this turn — see LayerActivityEvent. Powers the "Why?" panel's "What I checked" list and the 11-layer status grid. */
23
+ layerActivity: LayerActivityEvent[];
24
+ /**
25
+ * Present only when the batch-research path (batch-list-detector.ts / AgentLoop.runBatchToolLoop)
26
+ * drove this turn — absent otherwise, same "absent when unused" convention as sources/usage.
27
+ */
28
+ batchBudget?: BatchBudgetTrace;
29
+ }
30
+ /**
31
+ * Which code path actually produced this turn's reply — a test/telemetry affordance for
32
+ * plans/chat_ui_browser_e2e_plan.html phase B1 (the browser-e2e lane that unblocks the
33
+ * ASSISTANT_ONE_LOOP default-flip, R5 of the D2 one-loop rewire). The reply text itself is
34
+ * identical across all three by design (INV-19); this field is how a test asserts "the flag
35
+ * changed which path ran" without diffing internal traces.
36
+ *
37
+ * - 'posthoc' — flag OFF (or a trivial / no-tool turn): AgentLoop.runToolLoop finished a
38
+ * draftReply before HarnessBridge constructed HarnessRuntime; the harness run
39
+ * is post-hoc bookkeeping over an already-produced reply.
40
+ * - 'flat-oneloop' — flag ON, non-batch: the flat tool loop ran inside the harness's own
41
+ * driveMainLoop via AgentLoop.createOneLoopProposer.
42
+ * - 'batch-oneloop' — flag ON, batch-research: AgentLoop.createBatchOneLoopProposer drove the
43
+ * probe→calibrate→confirm→resolve→synthesize sequence under driveMainLoop.
44
+ */
45
+ export type ProposerKind = 'posthoc' | 'flat-oneloop' | 'batch-oneloop';
46
+ export interface AssistantTurnResult {
47
+ /**
48
+ * `needs_clarification` (Q2 of plans/ask_question_and_plan_mode_plan.html): a structured
49
+ * ask-question escalation (a populated `EscalationHalt.blocker.questions`, Q0) was staged for
50
+ * resume via AskClarificationService instead of terminating the turn — pass
51
+ * `pendingClarificationId` + a completed `AskResponse` back into
52
+ * `turn(message, { pendingClarificationId, clarificationAnswer })` to resolve it. Only reachable
53
+ * when the effective askMode (Q1's three-tier INV-29 resolution) is enabled; flag-off leaves
54
+ * every escalation on the pre-existing `escalated`/`reply: null` path, byte-identical to today.
55
+ */
56
+ /**
57
+ * `needs_plan_approval` (P2 of plans/ask_question_and_plan_mode_plan.html): a drafted plan
58
+ * (plan mode's P1/P3) was staged for the mandatory whole-plan approval gate instead of
59
+ * continuing to revise — pass `planApprovalId` back into
60
+ * `turn(message, { planApprovalId, planDecision, edits? })` to resolve it. Every drafted plan
61
+ * passes through this exactly once, regardless of risk (no risk-tiered skip — see
62
+ * PlanApprovalService's doc comment).
63
+ */
64
+ status: 'ok' | 'needs_approval' | 'escalated' | 'needs_clarification' | 'needs_plan_approval';
65
+ reply: string | null;
66
+ reason?: string;
67
+ /** See ProposerKind. Always set (defaults to 'posthoc'), on every status. */
68
+ proposerKind?: ProposerKind;
69
+ riskLevel?: TurnIntentClassification['riskLevel'];
70
+ controlState?: {
71
+ riskState: RiskState;
72
+ escalationReason: string | null;
73
+ };
74
+ stepsUsed?: number;
75
+ /** True when the turn was answered by the triviality fast path — no HarnessRuntime.run() this turn. */
76
+ harnessSkipped?: boolean;
77
+ /** Structured harness telemetry for a "Why?" disclosure — the step sequence and verification confidence, not free-text reasoning. */
78
+ trace?: AssistantTrace;
79
+ /** Set only when `needs_approval` was triggered by a `write_file`/`run_shell_command`/`send_email` tool call — pass back into `turn(message, { approved, pendingActionId })` to apply or discard it. */
80
+ pendingActionId?: string;
81
+ /** Which kind of action `pendingActionId` refers to — a write shows path + content preview, a shell command shows the exact command + resolved cwd, an email shows recipient + subject + body, a batch confirmation shows the projected remaining search count, a revert (staged only via /undo-action, never by the model) shows which paths will be restored/removed. */
82
+ pendingActionKind?: 'write' | 'shell' | 'email' | 'batch' | 'revert';
83
+ /** Real read_file/list_directory calls made while producing this reply, in call order. Only set when fileTools is configured and at least one such call happened this turn. */
84
+ sources?: AssistantSource[];
85
+ /**
86
+ * Structured, durable plan progress — present whenever a templated plan (new or
87
+ * resumed) drove this turn's initialTasks, absent otherwise (same "absent when
88
+ * unused" convention as trace/sources). Unlike trace, this can be non-null across
89
+ * many consecutive turns in the same session: the plan persists in `memory` until
90
+ * every task is COMPLETE or the user abandons it. See plan-store.ts.
91
+ */
92
+ planStatus?: {
93
+ templateName: string | null;
94
+ successCriteria: string;
95
+ completionPct: number;
96
+ tasks: {
97
+ id: string;
98
+ description: string;
99
+ status: string;
100
+ }[];
101
+ };
102
+ /**
103
+ * Token usage accumulated across every real LLM call this turn made (can be more than one:
104
+ * decomposition, plan-building, up to maxSteps tool-loop round trips).
105
+ * Absent when the backend/response never reported usage at all (e.g. the claude-cli backend
106
+ * with no usage field) — same "absent when unused" convention as trace/sources. Never set on
107
+ * needs_approval/escalated, matching how trace/sources already behave.
108
+ */
109
+ usage?: TokenUsage;
110
+ /** 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`. */
111
+ contradictionNotice?: string;
112
+ /**
113
+ * An epistemic-honesty signal for replies that went through the harness loop (absent on the
114
+ * triviality fast path, same "absent when unused" convention as trace/sources — a
115
+ * harness-skipped reply has no verification/evidence trail to attach). See answer-claim.ts's
116
+ * doc comment for how each field is derived.
117
+ */
118
+ answerClaim?: AnswerClaim;
119
+ /**
120
+ * Set only on a plan-pacing pause — the "Ready to continue with: ...?"/"All plan steps have
121
+ * run..." text appended to `reply` after the model's own draftReply. A caller that streamed
122
+ * `reply` token-by-token via `onToken` (see TurnOptions.onToken) already showed draftReply
123
+ * live and must print this separately, since draftReply is all `onToken` ever saw — this text
124
+ * is computed after the harness run completes, well after streaming finished.
125
+ */
126
+ pausedNote?: string;
127
+ /** Set only on `status: 'needs_clarification'` — pass back into `turn(message, { pendingClarificationId, clarificationAnswer })` to resolve it. See AskClarificationService. */
128
+ pendingClarificationId?: string;
129
+ /** The batch of questions `pendingClarificationId` refers to — set only on `status: 'needs_clarification'`, absent otherwise (same "absent when unused" convention as trace/sources). */
130
+ questions?: AskQuestion[];
131
+ /** Set only on `status: 'needs_plan_approval'` — pass back into `turn(message, { planApprovalId, planDecision, edits? })` to resolve it. See PlanApprovalService. */
132
+ planApprovalId?: string;
133
+ /**
134
+ * The staged plan `planApprovalId` refers to — set only on `status: 'needs_plan_approval'`,
135
+ * absent otherwise (same "absent when unused" convention as trace/sources). Each task's own
136
+ * `riskLevel` is included so an approval screen can show it prominently even though nothing
137
+ * about risk ever skips this gate (Section 5b-2). `reviewNotes` stays absent until P6's
138
+ * self-verification pass exists.
139
+ */
140
+ planApproval?: {
141
+ templateName: string | null;
142
+ successCriteria: string;
143
+ rationale: string;
144
+ tasks: {
145
+ id: string;
146
+ description: string;
147
+ riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH';
148
+ }[];
149
+ reviewNotes?: string[];
150
+ };
151
+ }
152
+ export interface AssistantProgress {
153
+ stepsUsed: number;
154
+ maxSteps: number;
155
+ currentNode?: string;
156
+ /** 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. */
157
+ planPosition?: PlanPosition;
158
+ }