@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,64 @@
1
+ export interface FactMarkerPatterns {
2
+ factMarkers: RegExp[];
3
+ healthOrDietaryMarkers: RegExp[];
4
+ durableNameOrPreferenceMarkers: RegExp[];
5
+ nonClaimMarkers: RegExp[];
6
+ clauseBoundary: RegExp[];
7
+ }
8
+ /** Matches fact-extraction.ts's FACT_MARKERS/HEALTH_OR_DIETARY_MARKERS/etc. — one compiled pattern per language. */
9
+ export declare function getFactMarkerPatterns(): FactMarkerPatterns;
10
+ /** True if `text` matches any of `patterns` — the "try every language, gate on either matching" rule (Decision 3). */
11
+ export declare function testAny(patterns: RegExp[], text: string): boolean;
12
+ /** Splits `text` on the first pattern (by language) that isn't empty-matching — clause boundaries don't need multi-language OR-ing the way a match test does, since splitting on any one language's boundary set is enough to separate clauses regardless of which language the boundary word came from. Currently just "en"; see splitClausesAcrossLanguages if a future language needs a genuinely different boundary shape combined in the same split. */
13
+ export declare function splitOnAny(patterns: RegExp[], text: string): string[];
14
+ export interface CompiledRiskPattern {
15
+ pattern: RegExp;
16
+ reason: string;
17
+ }
18
+ export interface RiskPatterns {
19
+ highRiskPatterns: CompiledRiskPattern[];
20
+ mediumRiskPatterns: CompiledRiskPattern[];
21
+ reminderPattern: CompiledRiskPattern;
22
+ reminderRecallQuestion: RegExp[];
23
+ bulkReminderReason: string;
24
+ pastTenseQuestion: RegExp[];
25
+ firstPersonPastNarrative: RegExp[];
26
+ reportedThirdPartySpeech: RegExp[];
27
+ riskClauseBoundary: RegExp[];
28
+ }
29
+ /** Matches risk-classifier.ts's HIGH_RISK_PATTERNS/MEDIUM_RISK_PATTERNS/REMINDER_PATTERN/etc. */
30
+ export declare function getRiskPatterns(): RiskPatterns;
31
+ /** Matches trust-tagging.ts's INJECTION_PATTERNS. */
32
+ export declare function getInjectionPatterns(): CompiledRiskPattern[];
33
+ /** Matches contradiction-checker.ts's CODING_FACT_MARKERS. */
34
+ export declare function getCodingFactMarkerPatterns(): RegExp[];
35
+ export interface EnumerationPatterns {
36
+ sequencingMarkers: RegExp[];
37
+ oneCommaListMarker: RegExp[];
38
+ twoCommaListMarker: RegExp[];
39
+ semicolonListMarker: RegExp[];
40
+ numberedListItem: RegExp[];
41
+ factThenSingleReminder: RegExp[];
42
+ remindWord: RegExp[];
43
+ }
44
+ /** Matches decomposition-classifier.ts's SEQUENCING_MARKERS/ONE_COMMA_LIST_MARKER/etc. */
45
+ export declare function getEnumerationPatterns(): EnumerationPatterns;
46
+ export interface TaskCancelPatterns {
47
+ taskCancelVerbs: RegExp[];
48
+ taskReferenceMarker: RegExp[];
49
+ cancelMatchStopwords: ReadonlySet<string>;
50
+ }
51
+ /** Matches plan-store.ts's TASK_CANCEL_VERBS/TASK_REFERENCE_MARKER/CANCEL_MATCH_STOPWORDS. */
52
+ export declare function getTaskCancelPatterns(): TaskCancelPatterns;
53
+ export interface PlanModeCancelPatterns {
54
+ cancelVerbs: RegExp[];
55
+ planningReferenceMarker: RegExp[];
56
+ }
57
+ /** Matches plan-drafting-service.ts's exit-phrase detection ("cancel plan"/"abort planning"/...) — both a cancel-shaped verb AND an explicit reference to planning/drafting must match, same both-markers-required discipline getTaskCancelPatterns uses, so an unrelated "cancel my flight" said mid-draft doesn't spuriously exit plan mode. */
58
+ export declare function getPlanModeCancelPatterns(): PlanModeCancelPatterns;
59
+ /** Matches batch-list-detector.ts's CONNECTOR_WORDS. */
60
+ export declare function getConnectorWords(): ReadonlySet<string>;
61
+ /** Matches tool-yield-classifier.ts's DEAD_END_MARKERS. */
62
+ export declare function getDeadEndMarkers(): RegExp[];
63
+ /** Matches plan-templates/index.ts's TEMPLATE_KEYWORDS. */
64
+ export declare function getTemplateKeywords(): Record<string, string[]>;
@@ -0,0 +1,219 @@
1
+ import { ExperienceStore, StrategyWeightKey, DecompositionEntry, RecoverySequenceEntry, ExperienceStoreData, ExternalContradictionInput } from '@buildaharness/harness';
2
+ import { MemoryAdapter, ReminderStore, ReminderRecord, ILLMClient, TokenUsage } from '@buildaharness/runtime';
3
+ import { UserFact } from './fact-extraction.js';
4
+ import { Corroboration } from './contradiction-checker.js';
5
+ import { StatedFact, FactCategory } from './turn-intent-classifier.js';
6
+ export declare const FACT_CAP = 20;
7
+ export declare const DURABLE_FACTS_KEY = "facts:durable";
8
+ /**
9
+ * Global, never cleared by `/new` — same retention shape as DURABLE_FACTS_KEY (a pending fact
10
+ * shouldn't vanish just because the session changed). Phase 3 of
11
+ * plans/personal_assistant_fact_extraction_llm_confidence_plan.html: a `model_inferred` fact
12
+ * that reached `durable: true, confidence: 'medium'` lands here instead of being silently
13
+ * promoted or silently dropped — see `recordFacts()`'s promotion policy and
14
+ * `confirmPendingFact()`/`rejectPendingFact()` below.
15
+ */
16
+ export declare const PENDING_CONFIRMATION_KEY = "facts:pending-confirmation";
17
+ /**
18
+ * Global, never cleared by `/new`. Holds facts removed from `facts:${sessionId}`/
19
+ * PENDING_CONFIRMATION_KEY either by an entry-time retraction (a later statement contradicts an
20
+ * unconfirmed guess — see `recordFacts()`) or by an explicit `/memory reject`. Rejected facts are
21
+ * remembered, not blacklisted: the entry-time check compares new statements against this pool
22
+ * too, so a matching restatement can re-enter PENDING_CONFIRMATION_KEY tagged
23
+ * `previouslyRejected` instead of being silently re-asked forever or silently refused forever.
24
+ */
25
+ export declare const REJECTED_FACTS_KEY = "facts:rejected";
26
+ /** Bound on how many learned decompositions/recovery sequences `getMemorySummary()` includes — see MemorySummary's doc comment. */
27
+ export declare const MEMORY_SUMMARY_PREVIEW_LIMIT = 20;
28
+ /** A `model_inferred` fact sitting in PENDING_CONFIRMATION_KEY, awaiting `/memory confirm`/`/memory reject`. */
29
+ export interface PendingFact extends UserFact {
30
+ category: FactCategory;
31
+ /** True when this exact fact previously lived in REJECTED_FACTS_KEY and was corroborated back in by a later, differently-phrased restatement — see `recordFacts()`'s corroboration handling. `/memory` surfaces this so the user sees the prior rejection instead of the fact looking brand new. */
32
+ previouslyRejected?: boolean;
33
+ }
34
+ /** An entry in REJECTED_FACTS_KEY — see that constant's doc comment. */
35
+ export interface RejectedFact {
36
+ text: string;
37
+ rejectedAt: string;
38
+ rejectionSource: 'auto_retracted' | 'user_explicit';
39
+ }
40
+ /**
41
+ * Read-only snapshot returned by `getMemorySummary()` — see that method's doc comment.
42
+ * `decompositions`/`recoverySequences` are capped at the 20 most recently learned entries
43
+ * (newest first) so `/memory` stays scannable after months of accumulated learning; `/memory
44
+ * export` (see `exportMemory()`) returns every category unbounded, since a file on disk doesn't
45
+ * have the same terminal-scrollback concern a REPL print does. `pending`/`rejected` are their
46
+ * stores' full, unbounded contents — neither is expected to grow anywhere near
47
+ * MEMORY_SUMMARY_PREVIEW_LIMIT in practice, unlike decompositions/recoverySequences.
48
+ */
49
+ export interface MemorySummary {
50
+ facts: UserFact[];
51
+ reminders: ReminderRecord[];
52
+ pending: PendingFact[];
53
+ experience: {
54
+ strategyWeights: Record<StrategyWeightKey, number>;
55
+ decompositions: DecompositionEntry[];
56
+ recoverySequences: RecoverySequenceEntry[];
57
+ };
58
+ }
59
+ /** Full, unbounded snapshot written by `/memory export` — every ExperienceStore category plus facts/reminders/pending-confirmation, as plain JSON. */
60
+ export interface MemoryExport {
61
+ exportedAt: string;
62
+ facts: UserFact[];
63
+ reminders: ReminderRecord[];
64
+ pending: PendingFact[];
65
+ experience: ExperienceStoreData;
66
+ }
67
+ /** Result of a single fact's promotion-time (`/memory confirm`) or corroboration-driven (medium→high) admission into DURABLE_FACTS_KEY — see `confirmPendingFact()`. */
68
+ export interface PendingConfirmationOutcome {
69
+ fact: UserFact;
70
+ /** Set when the promotion-time check (checkForContradictions against current Knowledge) found a conflict — advisory only, matching every other contradiction check in this codebase: the promotion still succeeds regardless. */
71
+ conflictNotice?: string;
72
+ }
73
+ /** `recordFacts()`'s return — see its doc comment's "Ordering constraint" note on why callers must read this before building any contradiction notice for the turn. */
74
+ export interface RecordFactsResult {
75
+ contradictions: ExternalContradictionInput[];
76
+ corroborations: Corroboration[];
77
+ }
78
+ /**
79
+ * The exact lexical+LLM merge `recordFacts()` uses to decide what to write to the fact stores,
80
+ * exposed (Phase 4 of plans/personal_assistant_fact_extraction_llm_confidence_plan.html) so a
81
+ * caller can seed `HarnessRunParams.currentTurnFacts` (harness-bridge.ts's `factExtractor`) with
82
+ * the identical merged, deduplicated list — instead of harness-bridge.ts recomputing only the free
83
+ * lexical half and never seeing an LLM-caught fact at all, which left a same-turn LLM-caught fact
84
+ * invisible to contradiction detection. Two independent calls with the same arguments (one here to
85
+ * build a harness run's currentTurnFacts before the run, one inside `recordFacts()` itself after
86
+ * it) each stamp their own `extractedAt` — harmless, since neither call's result is compared
87
+ * against the other's by identity.
88
+ */
89
+ export declare function buildTurnFacts(sessionId: string, userMessage: string, statedFacts: StatedFact[]): UserFact[];
90
+ /**
91
+ * Owns fact/reminder/experience reads that feed each turn's system prompt, fact capture, and the
92
+ * `/memory`/`/memory export` read-only snapshots — the "what has this assistant learned" surface
93
+ * of PersonalAssistant, split out in Phase 4d of the architecture remediation plan. `llmClient`/
94
+ * `model` were added in Phase 3 of the fact-extraction-confidence plan — `recordFacts()`'s
95
+ * entry-time consistency check and the promotion-time check in `confirmPendingFact()` both run
96
+ * the same `checkForContradictions` the harness's own per-turn contradiction hook already uses,
97
+ * rather than building a second mechanism.
98
+ */
99
+ export declare class MemoryService {
100
+ private readonly memory;
101
+ private readonly reminderStore;
102
+ private readonly experienceStore;
103
+ private readonly llmClient;
104
+ private readonly model;
105
+ /** Resolves to `config.activeProject` if the user set one via `/project <name>`, else the workspace root — read through a getter (same convention as `model` above) so a mid-session `/project`/`/config set activeProject` change takes effect on the very next turn. See UserFact.project's doc comment for how this is used. */
106
+ private readonly currentProject;
107
+ constructor(memory: MemoryAdapter, reminderStore: ReminderStore, experienceStore: ExperienceStore, llmClient: ILLMClient, model: () => string | undefined,
108
+ /** Resolves to `config.activeProject` if the user set one via `/project <name>`, else the workspace root — read through a getter (same convention as `model` above) so a mid-session `/project`/`/config set activeProject` change takes effect on the very next turn. See UserFact.project's doc comment for how this is used. */
109
+ currentProject?: () => string);
110
+ /**
111
+ * Durable + session facts for `sessionId`, plus the ready-to-splice system-prompt block — see
112
+ * runTurn's former factsBlock. `facts` is the full inventory, unfiltered by project — `/memory`
113
+ * (getMemorySummary) needs to show and let the user forget a fact regardless of which project is
114
+ * currently active. `factsBlock` — what the model actually sees this turn — filters to
115
+ * global-or-current-project only, so an unrelated project's facts don't leak into context (see
116
+ * UserFact.project's doc comment).
117
+ */
118
+ loadFacts(sessionId: string): Promise<{
119
+ facts: UserFact[];
120
+ factsBlock: string;
121
+ }>;
122
+ /**
123
+ * `/memory forget <n>` — removes the nth entry (1-based in `/memory`'s "Facts I know" display,
124
+ * 0-based here) from whichever store(s) it actually lives in. `index` is over the same merged,
125
+ * durable-first ordering `loadFacts()`/`getMemorySummary()` already produce, so the number a user
126
+ * sees in `/memory` is the number they pass here — no separate "durable index" vs "session index"
127
+ * to track. A fact that's both durable and session-scoped under an identical restated text (see
128
+ * mergeFacts's dedup) is removed from both stores by `sameFact` identity, not just the copy that
129
+ * happened to win the display dedup. Returns undefined for an out-of-range index (the caller's
130
+ * `/memory` view is stale — nothing to forget).
131
+ */
132
+ forgetFact(index: number, sessionId: string): Promise<UserFact | undefined>;
133
+ /**
134
+ * reminderStore is cross-session durable (clearSession() never touches it, same tier as
135
+ * DURABLE_FACTS_KEY) but, unlike facts, was never actually surfaced into context: a plain
136
+ * conversational question about a previously-created reminder ("did I mention X earlier?") got
137
+ * no grounding unless the model happened to call list_reminders itself — found via live
138
+ * testing (a fresh /new session flatly denied any record of a reminder created in the prior
139
+ * session, and separately claimed "I don't have access to other conversations" in the same
140
+ * reply that a durable *fact* from that same prior session correctly informed). Only undone
141
+ * reminders: a completed one is no longer something the user would expect the assistant to
142
+ * "remember" as pending.
143
+ */
144
+ loadActiveReminders(): Promise<{
145
+ activeReminders: ReminderRecord[];
146
+ remindersBlock: string;
147
+ }>;
148
+ /**
149
+ * Captures this turn's durable/session facts into the session's fact store. A no-op when
150
+ * neither pass finds anything. Facts that clear `shouldAutoPromote()` (see that function's doc
151
+ * comment for the current three-way policy) are ALSO appended to DURABLE_FACTS_KEY, a store
152
+ * clearSession() never touches, so they survive /new instead of vanishing with the rest of the
153
+ * session's facts. A `model_inferred` fact at `durable: true, confidence: 'medium'` is queued
154
+ * to PENDING_CONFIRMATION_KEY instead (Phase 3) — never auto-promoted, never dropped.
155
+ *
156
+ * Phase 2: the free lexical pass (`extractFactsFromTurn`) and `classifyTurnIntent`'s
157
+ * `statesDurableFacts` (the `StatedFact[]` list this method's `statedFacts` param carries) are
158
+ * **both always considered**, merged via `mergeTurnFacts()`. The lexical pass keeps running
159
+ * unconditionally so a classifier outage (an empty `statedFacts` list, per
160
+ * `failSafeClassification`) still degrades to "regex only," never to "no facts at all."
161
+ *
162
+ * Phase 3: every write this function performs passes through the entry-time consistency check
163
+ * first — this turn's new facts are checked, in one batched `checkForContradictions` call, both
164
+ * against Knowledge (durable + session facts already in the Knowledge tier — see
165
+ * `tierForFact`/`isKnowledgeTier`) and against the session's current medium/low-confidence
166
+ * `model_inferred` facts (the "uncertain pool"), plus REJECTED_FACTS_KEY (so a restated,
167
+ * previously-rejected guess is recognized rather than treated as brand new). A contradiction
168
+ * against the uncertain pool is a retraction: the retracted fact is removed from the session
169
+ * store and PENDING_CONFIRMATION_KEY and recorded into REJECTED_FACTS_KEY instead. A
170
+ * corroboration against the uncertain pool upgrades that fact's confidence in place
171
+ * (low→medium queues it to PENDING_CONFIRMATION_KEY; medium→high promotes it to
172
+ * DURABLE_FACTS_KEY) — corroboration is deliberately the one read-modify-write path in this
173
+ * function; every other write is append-only. A corroboration against REJECTED_FACTS_KEY
174
+ * re-queues the fact to PENDING_CONFIRMATION_KEY tagged `previouslyRejected`.
175
+ *
176
+ * **Ordering constraint on callers**: this must run, and its `contradictions` must be folded
177
+ * into whatever contradiction notice the turn returns, BEFORE that notice is finalized — see
178
+ * response-service.ts's three result-builders and assistant-session.ts's
179
+ * `dedupedContradictionNotice`. Left uncalled, this phase's entry-time findings would be
180
+ * computed but never surfaced in the turn's own reply.
181
+ */
182
+ recordFacts(sessionId: string, userMessage: string, statedFacts: StatedFact[], onUsage?: (usage: TokenUsage) => void): Promise<RecordFactsResult>;
183
+ /**
184
+ * `/memory confirm <n>` — promotes the nth (1-based in `/memory`'s display, 0-based here) entry
185
+ * in PENDING_CONFIRMATION_KEY to DURABLE_FACTS_KEY. Runs the same promotion-time check as
186
+ * corroboration's medium→high path: entering DURABLE_FACTS_KEY is itself a store-write, checked
187
+ * against current Knowledge at the moment of promotion rather than deferred to the next turn's
188
+ * re-seed. A conflict is advisory only — the confirm still succeeds regardless, matching every
189
+ * other contradiction check in this codebase (never gates belief admission); the conflict is
190
+ * returned as `conflictNotice` for the caller to surface. Returns undefined for an out-of-range
191
+ * index (the caller's `/memory` view is stale — nothing to confirm).
192
+ */
193
+ confirmPendingFact(index: number, onUsage?: (usage: TokenUsage) => void): Promise<PendingConfirmationOutcome | undefined>;
194
+ /** `/memory reject <n>` — removes the nth pending entry and records it into REJECTED_FACTS_KEY with `rejectionSource: 'user_explicit'`. Returns undefined for an out-of-range index. */
195
+ rejectPendingFact(index: number): Promise<PendingFact | undefined>;
196
+ /** `/memory confirm <category>` — bulk-confirms every pending entry in one category, useful once a topic accumulates several related guesses the user would naturally want to resolve together. Runs the promotion-time check per fact (still one call per fact — matches confirmPendingFact's own guarantee, not batched across facts since each is an independent store-write with its own conflict outcome). */
197
+ confirmPendingCategory(category: FactCategory, onUsage?: (usage: TokenUsage) => void): Promise<PendingConfirmationOutcome[]>;
198
+ /** `/memory reject <category>` — bulk-rejects every pending entry in one category. */
199
+ rejectPendingCategory(category: FactCategory): Promise<PendingFact[]>;
200
+ private rejectFacts;
201
+ private promoteConfirmedFact;
202
+ /**
203
+ * Read-only snapshot of what this session/assistant has learned: durable facts extracted
204
+ * from the user's own messages, reminders created so far, pending-confirmation guesses, and the
205
+ * real content (not just counts) of the learning-layer `ExperienceStore` — strategy weights in
206
+ * full, and the 20 most recently learned decompositions/recovery sequences (see
207
+ * MEMORY_SUMMARY_PREVIEW_LIMIT). Use `exportMemory()` for the full, unbounded contents. Used by
208
+ * `/memory`.
209
+ */
210
+ getMemorySummary(sessionId: string): Promise<MemorySummary>;
211
+ /**
212
+ * Full, unbounded snapshot of everything learned so far — every ExperienceStore category
213
+ * (not just the 20-entry preview `getMemorySummary()` bounds for terminal display) plus
214
+ * facts/reminders/pending-confirmation, as plain JSON. Read-only: this adds no corresponding
215
+ * import path, so a user cannot hand-edit the result and load it back in. Used by `/memory
216
+ * export`.
217
+ */
218
+ exportMemory(sessionId: string): Promise<MemoryExport>;
219
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Static, approximate per-model pricing — a fallback used only by the proxy backend's /cost
3
+ * display. The proxy (packages/proxy/src/forward.ts) is a thin pass-through to Anthropic/
4
+ * OpenAI and never computes or returns a dollar cost itself, only the raw token counts each
5
+ * provider's response already includes. This table is NOT billing-accurate: it doesn't account
6
+ * for prompt-caching discounts, batch pricing, or any rate change since it was last updated —
7
+ * update the numbers below if Anthropic's published pricing changes. The Claude CLI backend
8
+ * needs no such table; its costUsd comes from Claude's own real accounting (see
9
+ * claude-cli-prompt.ts's parseClaudeCliOutput).
10
+ */
11
+ export interface ModelPriceUsd {
12
+ inputPerMTok: number;
13
+ outputPerMTok: number;
14
+ }
15
+ /** Returns undefined (rather than a misleading number) when the model is unset or not a recognized Claude tier — callers must not silently treat "no estimate" as "free". */
16
+ export declare function estimateCostUsd(model: string | undefined, usage: {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ }): number | undefined;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Node-level network containment for an approved run_shell_command execution — Decision 6 of
3
+ * plans/lexical_functions_hardening_plan.html (Phase 4 step 2). This is the network-reachability
4
+ * half of that decision; the env-stripping half already existed in shell-executor.ts's
5
+ * allowlistedEnv(). Deliberately not a real OS sandbox (see the plan's Decision 6 for why that
6
+ * tradeoff was chosen): a minimal loopback-only forward proxy that relays a CONNECT tunnel
7
+ * (HTTPS) or a plain-HTTP proxy request only when its target host matches an entry in the
8
+ * caller-supplied allowlist (exact match or subdomain), and otherwise closes the connection with
9
+ * a 403. The spawned subprocess's HTTP(S)_PROXY env vars are forced to point here, so a tool that
10
+ * honors those (curl, wget, most language HTTP clients) can only reach an allowlisted host. This
11
+ * does NOT stop a tool that opens raw sockets and ignores proxy env vars entirely — that
12
+ * limitation is Decision 6's explicitly accepted tradeoff for a dependency-free, Node-only
13
+ * implementation identical across CLI and desktop.
14
+ */
15
+ export interface NetworkContainmentProxy {
16
+ port: number;
17
+ close: () => Promise<void>;
18
+ }
19
+ export declare function getNetworkContainmentProxy(allowlist: readonly string[]): Promise<NetworkContainmentProxy>;
20
+ /** Test-only: drops all cached proxies so a test's allowlist doesn't leak into another test's expectations. */
21
+ export declare function resetNetworkContainmentProxiesForTests(): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { AssistantConfig, ConfigStore } from './config.js';
2
+ /**
3
+ * JSON-file-backed ConfigStore for the CLI, at a single `config.json` path (typically
4
+ * `<dataDir>/config.json`). Deliberately not exported from this package's index — only cli.ts
5
+ * imports it, same reasoning as node-fs-backend.ts — so a browser build never pulls in
6
+ * node:fs/promises.
7
+ *
8
+ * save() merges the patch onto whatever's already persisted (never a blind overwrite of
9
+ * unrelated keys) and writes through a temp-file-then-rename so a crash mid-write can never
10
+ * leave a half-written config.json behind — the same failure mode file-tools.ts's
11
+ * .pending-actions staging already avoids the same way. Setting a key to `undefined` in a
12
+ * patch removes it from the persisted file (JSON.stringify drops undefined-valued
13
+ * properties), which is what /config reset relies on.
14
+ *
15
+ * Concurrent save() calls on the same instance are serialized through an internal queue —
16
+ * cli.ts's readline 'line' handler doesn't wait for one command to finish before dispatching
17
+ * the next (e.g. piped stdin with two /config set lines back to back), so without this a
18
+ * second save()'s load-modify-write could race the first's: reading stale state before the
19
+ * first's write lands and silently clobbering it, or (with a timestamp-only temp filename)
20
+ * colliding on the exact same temp path within the same millisecond.
21
+ */
22
+ export declare class NodeConfigStore implements ConfigStore {
23
+ private readonly path;
24
+ private queue;
25
+ constructor(path: string);
26
+ load(): Promise<Partial<AssistantConfig>>;
27
+ save(patch: Partial<AssistantConfig>): Promise<void>;
28
+ private writePatch;
29
+ }
@@ -0,0 +1,26 @@
1
+ import { LayerActivityEvent } from '@buildaharness/harness';
2
+ export declare function nodeDisplayName(node: string | undefined): string | undefined;
3
+ /**
4
+ * The 11 harness layer slugs, in the fixed order CLAUDE.md's architecture table lists them —
5
+ * used by both the CLI's `/layers` and chat-ui's layer grid to render a stable 2-3 letter
6
+ * code per layer (Phase 3.3 of the harness layer activation plan). Matches the `layer` field
7
+ * on the `layer_activity` TraceEvent (see trace-events.ts) and HarnessRunOptions'
8
+ * LayerActivityEvent (see @buildaharness/harness).
9
+ */
10
+ export declare const LAYER_ORDER: readonly ["world_model", "evidence_reasoning", "hypothesis", "contradiction", "diagnostics", "control_state", "planning", "execution", "verification", "recovery", "reviewer_pass"];
11
+ export type LayerSlug = (typeof LAYER_ORDER)[number];
12
+ export declare const LAYER_DISPLAY_NAME: Record<LayerSlug, string>;
13
+ export declare const LAYER_SHORT_CODE: Record<LayerSlug, string>;
14
+ export declare function nodeToLayer(node: string | undefined): LayerSlug | undefined;
15
+ export interface WhyChainItem {
16
+ layer: LayerSlug;
17
+ reason: string;
18
+ }
19
+ /**
20
+ * Collapses a turn's raw layerActivity (one event per layer per main-loop iteration — see
21
+ * harness-runtime.ts's onLayerActivity doc comment) down to just the layers that fired, in the
22
+ * order they fired, merging consecutive re-fires of the same layer (back-to-back loop
23
+ * iterations) into a single link. Shared by the CLI's `/why` and chat-ui's "Why?" panel so both
24
+ * surfaces summarize a turn identically instead of drifting apart.
25
+ */
26
+ export declare function buildWhyChain(layerActivity: LayerActivityEvent[]): WhyChainItem[];
@@ -0,0 +1,8 @@
1
+ import { FsBackend } from '@buildaharness/runtime';
2
+ /**
3
+ * node:fs/promises-backed FsBackend for the CLI. Deliberately not exported from
4
+ * this package's index — only cli.ts imports it, so a browser build (chat-ui)
5
+ * that only ever imports the package root never pulls a Node builtin into its
6
+ * bundle.
7
+ */
8
+ export declare function createNodeFsBackend(): FsBackend;
@@ -0,0 +1,22 @@
1
+ export type NonInteractiveApprovalMode = 'decline' | 'require-tty';
2
+ /**
3
+ * Pure logic backing cli.ts's ASSISTANT_NON_INTERACTIVE_APPROVAL handling — split out so it's
4
+ * unit-testable in isolation, the same way error-classifier.ts and node-display-names.ts are
5
+ * split out of cli.ts for the same reason (cli.ts runs `main()` at import time, so it can't be
6
+ * imported directly by a test).
7
+ *
8
+ * Turns the piped/scripted-stdin approval-gate behavior into an explicit, documented choice
9
+ * instead of an incidental side effect of readline hitting closed stdin (see cli.ts's askYesNo,
10
+ * whose fail-closed catch stays the unset-env-var default and is unaffected by this — nothing
11
+ * changes for an interactive TTY session either way). 'decline': every approval gate
12
+ * auto-declines immediately, without ever touching stdin — the right choice for a scripted/CI
13
+ * caller that expects every HIGH-risk/staged action to be rejected outright, no exceptions.
14
+ * 'require-tty': cli.ts fails fast at startup with a clear, actionable error if stdin isn't a
15
+ * real TTY, instead of running an entire session that can only surface the problem deep in, at
16
+ * the first approval prompt, as a generic "[could not read a response]". An unrecognized value is
17
+ * ignored (falls back to unset) with a startup warning, so a typo can't silently produce
18
+ * different behavior than intended — mirrors ASSISTANT_LLM_BACKEND's own typo-tolerance in
19
+ * cli-config.ts, but warns here since a silently-ignored approval-mode typo is safety-relevant in
20
+ * a way a silently-ignored backend typo (which still resolves to a working default) isn't.
21
+ */
22
+ export declare function resolveNonInteractiveApprovalMode(env: NodeJS.ProcessEnv): NonInteractiveApprovalMode | undefined;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure logic backing PersonalAssistant's ASSISTANT_ONE_LOOP handling — split out so it's
3
+ * unit-testable in isolation, same pattern as control-plane-flag.ts (removed in
4
+ * personal-assistant: Phase 4b/4c) and non-interactive-mode.ts's resolveNonInteractiveApprovalMode:
5
+ * a module-level default resolved once from process.env, threaded as an injectable constructor
6
+ * option so tests never touch real process.env.
7
+ *
8
+ * Backs plans/harness_d2_one_loop_rewire_plan.html's R2 phase: the harness-driven proposer that
9
+ * lets HarnessRuntime's driveMainLoop actually call AgentLoop's tool-calling machinery once per
10
+ * main-loop iteration, instead of receiving an already-finished draftReply after the fact. R2
11
+ * landed it default OFF; R3/R4 built the rest of the wiring behind it; R5 gathered the rollout
12
+ * evidence (Rule 2/Rule 6 differential clean, CLI live-verification both states, the chat-ui
13
+ * flag OFF/ON parity matrix green under real headless Chromium) and flipped the default to
14
+ * 'enabled' on 2026-09-06. The flag itself is kept for one more window as an escape hatch before
15
+ * being removed (R5's final step), mirroring ASSISTANT_CONTROL_PLANE's own shape.
16
+ *
17
+ * 'enabled' (the default): HarnessBridge.run() swaps in a real harness-driven proposer (built
18
+ * from AgentLoop.createHarnessProposer) as the 'default' toolExecutor, when the caller supplies
19
+ * one — the harness's driveMainLoop drives the real tool calls one iteration at a time.
20
+ * 'disabled': the pre-rewire behavior — toolExecutors.default always resolves to
21
+ * `() => draftReply` (the tool loop runs to completion first, the harness sees the finished
22
+ * reply). An unrecognized value is ignored (falls back to the default) with a startup warning,
23
+ * same typo-tolerance-with-a-warning convention resolveControlPlaneMode/
24
+ * resolveNonInteractiveApprovalMode used, since a silently-misread flag here is safety-relevant.
25
+ */
26
+ export type OneLoopMode = 'enabled' | 'disabled';
27
+ export declare const DEFAULT_ONE_LOOP_MODE: OneLoopMode;
28
+ /**
29
+ * Value-level resolver, shared by every surface: `resolveOneLoopMode` (CLI, reads
30
+ * `process.env.ASSISTANT_ONE_LOOP`) and chat-ui's `envOverridesFromImportMetaEnv` (browser build,
31
+ * reads Vite's `import.meta.env.VITE_ASSISTANT_ONE_LOOP`, which has no `process` to hand a
32
+ * `NodeJS.ProcessEnv` to). An unset or empty value falls back to the default silently; an
33
+ * unrecognized non-empty value falls back with a startup warning naming `varName`, the same
34
+ * typo-tolerance-with-a-warning convention resolveControlPlaneMode/resolveNonInteractiveApprovalMode
35
+ * use, since a silently-misread flag here is safety-relevant.
36
+ */
37
+ export declare function normalizeOneLoopMode(raw: string | undefined, varName?: string): OneLoopMode;
38
+ export declare function resolveOneLoopMode(env: NodeJS.ProcessEnv): OneLoopMode;
@@ -0,0 +1,62 @@
1
+ import { AssistantSession } from './assistant-session.js';
2
+ import { PlanService } from './plan-service.js';
3
+ import { PlanRecord } from './plan-store.js';
4
+ import { AssistantTurnResult } from './assistant-types.js';
5
+ import { TraceEvent } from './trace-events.js';
6
+ /**
7
+ * `'approve_trusted'` (plan mode's P10) is structurally its own decision, not a modifier on
8
+ * `'approve'`/`'approve_with_edits'` — it carries no `edits` of its own and, like plain
9
+ * `'approve'`, activates the plan as staged, just with `PlanRecord.trustApprovedSteps` also set
10
+ * (see resolvePendingPlanApproval below and INV-36).
11
+ */
12
+ export type PlanDecision = 'approve' | 'approve_trusted' | 'approve_with_edits' | 'decline';
13
+ export interface PlanApprovalEdits {
14
+ cancelTaskIds?: string[];
15
+ editedTasks?: {
16
+ id: string;
17
+ description: string;
18
+ }[];
19
+ }
20
+ /**
21
+ * `PlanApprovalService.resolvePendingPlanApproval`'s result: either a terminal
22
+ * `AssistantTurnResult` (stale/missing staged plan, no decision supplied, or a failed
23
+ * approve/approve-with-edits — fail closed, `mode` left exactly as staged) or `{ fallThrough:
24
+ * true }`, meaning the plan record has already been updated (activated, or discarded) and
25
+ * `assistant.ts`'s `runTurn` should keep going with the *same originating message* through the
26
+ * ordinary pipeline below, per this phase's own scope: approve lets that pipeline pick up the
27
+ * now-`active` plan exactly the way a freshly template-matched plan already does today
28
+ * (`TurnInterpreter.resolveTasks` -> `loadActivePlan` -> `initialTasks`, regardless of what the
29
+ * message itself says, short of a cancel/abandon match) — no new execution-driving code needed,
30
+ * just reusing what already runs a plan's first task the moment it becomes `active`. Decline
31
+ * falls through the same way, but with no plan active at all.
32
+ */
33
+ export type PlanApprovalOutcome = {
34
+ fallThrough: true;
35
+ } | AssistantTurnResult;
36
+ /**
37
+ * Owns plan mode's P2 mandatory whole-plan approval gate — mirrors ActionApprovalService's
38
+ * "resolved by ID, never re-derived" staging pattern (T4 of the file-tools plan), the same model
39
+ * AskClarificationService (Q2) already follows. Every drafted plan passes through here exactly
40
+ * once, regardless of risk (Section 5b-2: no risk-tiered skip) — `PlanDraftingService` calls
41
+ * `stageAndRespond` the moment a revision's `readyForApproval` is true, and
42
+ * `assistant.ts`'s `runTurn` calls `resolvePendingPlanApproval` whenever a turn carries a
43
+ * `planApprovalId`, before `planMode.active` is even checked (mirrors where `pendingActionId`/
44
+ * `pendingClarificationId` are checked).
45
+ */
46
+ export declare class PlanApprovalService {
47
+ private readonly planService;
48
+ private readonly session;
49
+ private readonly onTrace;
50
+ constructor(planService: PlanService, session: AssistantSession, onTrace: ((event: TraceEvent) => void) | undefined);
51
+ /** Stages `plan` for approval and returns the `needs_plan_approval` result — the PlanDraftingService counterpart to AskClarificationService.stageAndRespond. */
52
+ stageAndRespond(sessionId: string, transcriptKey: string, plan: PlanRecord, userMessage: string): Promise<AssistantTurnResult>;
53
+ /**
54
+ * Resolves a staged plan by ID — validates it's still exactly the plan that was staged (a
55
+ * stale/unknown `planApprovalId` resolves to a no-op `ok`, same as
56
+ * AskClarificationService.resolvePendingClarification's own stale-ID handling), then applies
57
+ * the caller's decision. Fail-closed (Protected Invariants) on every failure path: no decision,
58
+ * or a throwing edit/activation step, leaves `mode: 'awaiting_approval'` untouched and returns a
59
+ * new `needs_plan_approval` result rather than silently advancing or discarding the plan.
60
+ */
61
+ resolvePendingPlanApproval(sessionId: string, planApprovalId: string, decision: PlanDecision | undefined, edits: PlanApprovalEdits | undefined): Promise<PlanApprovalOutcome>;
62
+ }
@@ -0,0 +1,32 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ import { DecomposedTaskSpec } from './decomposition-classifier.js';
3
+ import { PlanTemplate } from './plan-templates/index.js';
4
+ export interface Plan {
5
+ templateName: string;
6
+ successCriteria: string;
7
+ tasks: DecomposedTaskSpec[];
8
+ /**
9
+ * Why this approach, not just what the success criteria are — optional for now since neither
10
+ * buildPlanFromTemplate's template-personalization call nor any other current producer of a
11
+ * Plan asks the LLM for one; plan-store.ts's createPlanRecord defaults it to '' when absent.
12
+ * A future from-scratch drafting path (plan mode's P3) is expected to populate it.
13
+ */
14
+ rationale?: string;
15
+ }
16
+ /**
17
+ * Spends one real LLM call personalizing `template`'s task skeleton to `message` —
18
+ * only called once classifyTurnIntent has already named a matchedPlanTemplate, so an
19
+ * ordinary turn never pays for this. Same "malformed/incomplete JSON is the expected
20
+ * failure mode, not the edge case" fallback classifyTurnIntent itself uses: any parse
21
+ * failure or a response with fewer than 2 usable tasks returns null, meaning "fall back to
22
+ * the caller's own ad hoc decomposition for this turn" rather than throwing.
23
+ *
24
+ * Each output task's `riskLevel` is attached from the matching skeleton task's own
25
+ * `risk_level` (curated by whoever wrote the template's JSON) by id, not asked of the LLM —
26
+ * the template already has this, more accurately than an LLM re-deriving it from a
27
+ * personalized one-line description would, and it costs zero extra output tokens. Falls back
28
+ * to 'LOW' only for the pathological case of a task id the LLM invented that doesn't match any
29
+ * skeleton task (the id/depends_on-must-match-skeleton instruction above means this should not
30
+ * happen in practice).
31
+ */
32
+ export declare function buildPlanFromTemplate(llmClient: ILLMClient, message: string, template: PlanTemplate, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<Plan | null>;