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