@buildaharness/aielia 0.3.4 → 0.3.5
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 +63 -20
- package/dist/PlanGraphPane.d.ts +19 -0
- package/dist/_memory-core-generated.d.ts +1764 -0
- package/dist/agent-loop.d.ts +23 -8
- package/dist/ambiguity-guard-flag.d.ts +12 -0
- package/dist/ambiguity-guard.d.ts +20 -0
- package/dist/answer-claim.d.ts +13 -1
- package/dist/ask-clarification-service.d.ts +12 -1
- package/dist/ask-response-format.d.ts +6 -0
- package/dist/assistant-session.d.ts +12 -1
- package/dist/assistant-source.d.ts +7 -0
- package/dist/assistant-types.d.ts +17 -1
- package/dist/assistant.d.ts +128 -6
- package/dist/benchmark-injected-failure.d.ts +6 -0
- package/dist/bin-CjKpxDxl.js +4836 -0
- package/dist/bin-CjKpxDxl.js.map +1 -0
- package/dist/cli-session.d.ts +14 -1
- package/dist/cli.d.ts +17 -2
- package/dist/cli.js +1 -1
- package/dist/code-plan-templates.d.ts +21 -0
- package/dist/config.d.ts +29 -0
- package/dist/constraint-check.d.ts +35 -0
- package/dist/contradiction-checker.d.ts +7 -0
- package/dist/decomposed-answer.d.ts +13 -0
- package/dist/decomposition-classifier.d.ts +2 -0
- package/dist/doctor-checks.d.ts +3 -0
- package/dist/episodic-digest.d.ts +89 -0
- package/dist/fact-extraction.d.ts +39 -0
- package/dist/file-tools-mcp-server.mjs +138 -12
- package/dist/file-tools.d.ts +4 -0
- package/dist/first-run.d.ts +0 -2
- package/dist/goal-graph-store.d.ts +18 -18
- package/dist/grounding-check.d.ts +33 -0
- package/dist/harness-bridge.d.ts +75 -2
- package/dist/harness-token-budget.d.ts +9 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +165 -94
- package/dist/index.js.map +1 -1
- package/dist/layer-policy-flag.d.ts +13 -0
- package/dist/layer-policy-wiring.d.ts +77 -0
- package/dist/layer-settings.d.ts +49 -0
- package/dist/layer-telemetry.d.ts +2 -0
- package/dist/lexical/lexical-mode.d.ts +29 -0
- package/dist/memory-consolidation.d.ts +80 -0
- package/dist/memory-governance.d.ts +30 -0
- package/dist/memory-reviewer.d.ts +114 -0
- package/dist/memory-service.d.ts +348 -9
- package/dist/model-json.d.ts +17 -0
- package/dist/next-step-context.d.ts +51 -0
- package/dist/next-step-proposer.d.ts +3 -2
- package/dist/plan-graph-flag.d.ts +20 -0
- package/dist/plan-question.d.ts +60 -0
- package/dist/plan-service.d.ts +6 -2
- package/dist/plan-step-prompt.d.ts +15 -0
- package/dist/plan-store.d.ts +7 -1
- package/dist/plan-viz/normalize.d.ts +14 -0
- package/dist/plan-viz/plan-detail-lines.d.ts +3 -0
- package/dist/plan-viz/plan-layout.d.ts +48 -0
- package/dist/plan-viz/plan-nav.d.ts +29 -0
- package/dist/plan-viz/plan-raster.d.ts +36 -0
- package/dist/plan-viz/plan-routes.d.ts +6 -0
- package/dist/plan-viz/plan-snapshot.d.ts +73 -0
- package/dist/plan-viz/plan-viewport.d.ts +29 -0
- package/dist/plan-viz/plan-width.d.ts +15 -0
- package/dist/plan-viz/testkit/display-width.d.ts +12 -0
- package/dist/plan-viz/testkit/generators.d.ts +61 -0
- package/dist/plan-viz/testkit/grid-oracles.d.ts +38 -0
- package/dist/plan-viz/testkit/nav-oracles.d.ts +79 -0
- package/dist/plan-viz/testkit/types.d.ts +8 -0
- package/dist/plan-viz/types.d.ts +34 -0
- package/dist/{provider-setup-CPbB5y_V.js → provider-setup-LFsaTkpf.js} +9308 -5510
- package/dist/provider-setup-LFsaTkpf.js.map +1 -0
- package/dist/recall-tool.d.ts +43 -0
- package/dist/recovery-note.d.ts +22 -0
- package/dist/response-service.d.ts +11 -1
- package/dist/review-checker.d.ts +10 -4
- package/dist/reviewer-revision.d.ts +23 -0
- package/dist/risk-classifier.d.ts +7 -0
- package/dist/scripted-llm-client.d.ts +26 -1
- package/dist/semantic-compaction.d.ts +15 -0
- package/dist/semantic-hypotheses.d.ts +40 -0
- package/dist/source-reliability.d.ts +71 -0
- package/dist/task-completion-check.d.ts +24 -0
- package/dist/tool-control-plane.d.ts +33 -1
- package/dist/tool-effect-class.d.ts +9 -0
- package/dist/transcript-compaction.d.ts +9 -0
- package/dist/{tui-app-DeyZ4Pr9.js → tui-app-BLR-PRFp.js} +321 -7
- package/dist/tui-app-BLR-PRFp.js.map +1 -0
- package/dist/tui-app.d.ts +38 -0
- package/dist/turn-intent-classifier.d.ts +75 -16
- package/dist/turn-interpreter.d.ts +25 -3
- package/dist/version.d.ts +1 -1
- package/package.json +6 -6
- package/dist/bin-CdVTAe7j.js +0 -2209
- package/dist/bin-CdVTAe7j.js.map +0 -1
- package/dist/provider-setup-CPbB5y_V.js.map +0 -1
- package/dist/tui-app-DeyZ4Pr9.js.map +0 -1
package/dist/memory-service.d.ts
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import { ExperienceStore, StrategyWeightKey, DecompositionEntry, RecoverySequenceEntry, ExperienceStoreData, ExternalContradictionInput } from '@buildaharness/harness';
|
|
2
|
-
import { MemoryAdapter, ReminderStore, ReminderRecord, ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { MemoryAdapter, ReminderStore, ReminderRecord, ILLMClient, TokenUsage, ChatMessage } from '@buildaharness/runtime';
|
|
3
3
|
import { UserFact } from './fact-extraction.js';
|
|
4
4
|
import { Corroboration } from './contradiction-checker.js';
|
|
5
|
+
import { DigestStore, SessionDigest } from './episodic-digest.js';
|
|
5
6
|
import { StatedFact, FactCategory } from './turn-intent-classifier.js';
|
|
7
|
+
import { MemoryWriteMode, CrossTurnWriter } from './memory-governance.js';
|
|
8
|
+
import { ConsolidationProposal } from './memory-consolidation.js';
|
|
9
|
+
import { VerifiedReviewOp } from './memory-reviewer.js';
|
|
6
10
|
export declare const FACT_CAP = 20;
|
|
7
|
-
|
|
11
|
+
/** Default character budget for the rendered facts block under `AUDIT_MEMORY_BUDGETED_RENDER` (M0 measured ~195 tokens ≈ 800 chars for 20 facts; this leaves room for roughly 5x that). */
|
|
12
|
+
export declare const DEFAULT_MEMORY_BUDGET_CHARS: number;
|
|
13
|
+
/** Append-only store of entries a keyed fact replaced (M1); the M2 audit log will subsume it. */
|
|
14
|
+
export declare const RETIRED_FACTS_KEY: string;
|
|
15
|
+
/** M2: append-only change log with pre-images. Global, never cleared by `/new` (same convention as DURABLE_FACTS_KEY). */
|
|
16
|
+
export declare const AUDIT_LOG_KEY: string;
|
|
17
|
+
/** M2/M5: highest audit `seq` already consolidated; the audit log never rotates away an entry newer than this. Absent until M5 writes it. */
|
|
18
|
+
export declare const CONSOLIDATION_STATE_KEY: string;
|
|
19
|
+
/** M2: how many audit entries rotation keeps (plus any newer than the consolidation watermark). */
|
|
20
|
+
export declare const AUDIT_LOG_KEEP: number;
|
|
21
|
+
/**
|
|
22
|
+
* M6: `/memory off` — when `{ off: true }`, nothing writes memory for this install (facts, pending
|
|
23
|
+
* queue, and every later writer: M3 digests, M4 reviewer, M5 consolidation must check
|
|
24
|
+
* `MemoryService.isMemoryOff()` first). Reads, forget, reject and undo still work: switching writes
|
|
25
|
+
* off never traps the user's existing data. Global, never cleared by `/new`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MEMORY_OFF_KEY: string;
|
|
28
|
+
/** M5: staged proposals awaiting the user (`/memory consolidate`). Global, never cleared by `/new`. */
|
|
29
|
+
export declare const CONSOLIDATION_PROPOSALS_KEY: string;
|
|
30
|
+
/** M5: entries the user accepted an archive proposal for. Recoverable (`/memory archive restore`), never deleted by this phase. */
|
|
31
|
+
export declare const ARCHIVED_FACTS_KEY: string;
|
|
32
|
+
export type AuditOp = 'add' | 'replace' | 'retire' | 'remove' | 'confirm' | 'reject' | 'undo' | 'archive' | 'restore';
|
|
33
|
+
export interface AuditEntry {
|
|
34
|
+
seq: number;
|
|
35
|
+
at: string;
|
|
36
|
+
op: AuditOp;
|
|
37
|
+
/** `text|extractedAt` of the fact the entry concerns (M1's id convention). */
|
|
38
|
+
factId: string;
|
|
39
|
+
/** Pre-image (redacted text only — the gate redacts before anything reaches the log). */
|
|
40
|
+
before?: UserFact;
|
|
41
|
+
after?: UserFact;
|
|
42
|
+
/** Which store the change was made to. */
|
|
43
|
+
store: 'durable' | 'pending' | 'rejected';
|
|
44
|
+
writer: string;
|
|
45
|
+
turn: string;
|
|
46
|
+
/** Set on an `undo` entry: the seq it reverted. */
|
|
47
|
+
undoes?: number;
|
|
48
|
+
/** M6: the user erased this fact from history (`/memory archive forget`); before/after were removed and the entry can no longer be undone. */
|
|
49
|
+
erased?: boolean;
|
|
50
|
+
/** M5: entries applied together (one consolidation proposal) share a group; undoing one undoes the group. */
|
|
51
|
+
group?: string;
|
|
52
|
+
/** M5: position the pre-image held in the durable list, so undo restores order exactly. */
|
|
53
|
+
index?: number;
|
|
54
|
+
}
|
|
55
|
+
export declare const DURABLE_FACTS_KEY: string;
|
|
8
56
|
/**
|
|
9
57
|
* Global, never cleared by `/new` — same retention shape as DURABLE_FACTS_KEY (a pending fact
|
|
10
58
|
* shouldn't vanish just because the session changed). Phase 3 of
|
|
@@ -13,7 +61,7 @@ export declare const DURABLE_FACTS_KEY = "facts:durable";
|
|
|
13
61
|
* promoted or silently dropped — see `recordFacts()`'s promotion policy and
|
|
14
62
|
* `confirmPendingFact()`/`rejectPendingFact()` below.
|
|
15
63
|
*/
|
|
16
|
-
export declare const PENDING_CONFIRMATION_KEY
|
|
64
|
+
export declare const PENDING_CONFIRMATION_KEY: string;
|
|
17
65
|
/**
|
|
18
66
|
* Global, never cleared by `/new`. Holds facts removed from `facts:${sessionId}`/
|
|
19
67
|
* PENDING_CONFIRMATION_KEY either by an entry-time retraction (a later statement contradicts an
|
|
@@ -22,7 +70,7 @@ export declare const PENDING_CONFIRMATION_KEY = "facts:pending-confirmation";
|
|
|
22
70
|
* too, so a matching restatement can re-enter PENDING_CONFIRMATION_KEY tagged
|
|
23
71
|
* `previouslyRejected` instead of being silently re-asked forever or silently refused forever.
|
|
24
72
|
*/
|
|
25
|
-
export declare const REJECTED_FACTS_KEY
|
|
73
|
+
export declare const REJECTED_FACTS_KEY: string;
|
|
26
74
|
/** Bound on how many learned decompositions/recovery sequences `getMemorySummary()` includes — see MemorySummary's doc comment. */
|
|
27
75
|
export declare const MEMORY_SUMMARY_PREVIEW_LIMIT = 20;
|
|
28
76
|
/** A `model_inferred` fact sitting in PENDING_CONFIRMATION_KEY, awaiting `/memory confirm`/`/memory reject`. */
|
|
@@ -30,6 +78,22 @@ export interface PendingFact extends UserFact {
|
|
|
30
78
|
category: FactCategory;
|
|
31
79
|
/** 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
80
|
previouslyRejected?: boolean;
|
|
81
|
+
/** M4: `retire` marks a reviewer proposal to retire an existing durable fact (`retireTargetId` = its `text|extractedAt`) rather than add one; confirming it retires, never adds. */
|
|
82
|
+
proposedOp?: 'retire';
|
|
83
|
+
retireTargetId?: string;
|
|
84
|
+
/** M4: who staged this entry and what the semantic verifier said, shown in `/memory`. */
|
|
85
|
+
stagedBy?: 'reviewer';
|
|
86
|
+
verification?: 'supported' | 'unsupported' | 'not_checked';
|
|
87
|
+
}
|
|
88
|
+
/** What `stageReviewerOps()` did with a batch, for tests and `/memory` diagnostics. */
|
|
89
|
+
export interface StageReviewerResult {
|
|
90
|
+
staged: number;
|
|
91
|
+
/** Unsupported by the verifier, kept session-scoped only. */
|
|
92
|
+
sessionScoped: number;
|
|
93
|
+
/** Refused by a plain-code gate (retire of a user_asserted fact, unknown target) or dropped by the write gate. */
|
|
94
|
+
refused: number;
|
|
95
|
+
/** The signal fired before the write: nothing was written. */
|
|
96
|
+
aborted: boolean;
|
|
33
97
|
}
|
|
34
98
|
/** An entry in REJECTED_FACTS_KEY — see that constant's doc comment. */
|
|
35
99
|
export interface RejectedFact {
|
|
@@ -63,7 +127,60 @@ export interface MemoryExport {
|
|
|
63
127
|
reminders: ReminderRecord[];
|
|
64
128
|
pending: PendingFact[];
|
|
65
129
|
experience: ExperienceStoreData;
|
|
130
|
+
/** M3: every stored episodic digest, unbounded and regardless of retention. */
|
|
131
|
+
digests?: SessionDigest[];
|
|
132
|
+
/** M6: facts a keyed update replaced (and, after M5, archived) — covered by export so nothing the assistant holds about the user is missing from it. */
|
|
133
|
+
retired?: UserFact[];
|
|
134
|
+
/** M6: the audit log (redacted text only, by the M2 gate). Empty when the audit-log flag is off. */
|
|
135
|
+
audit?: AuditEntry[];
|
|
136
|
+
/** M6: governance state at export time. */
|
|
137
|
+
governance?: {
|
|
138
|
+
mode: MemoryWriteMode;
|
|
139
|
+
off: boolean;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** M6: what the facts block of the most recent turn actually contained ("Why?" panel, `/why`). */
|
|
143
|
+
export interface MemoryInjection {
|
|
144
|
+
/** Facts placed in the prompt, in render order. */
|
|
145
|
+
facts: {
|
|
146
|
+
text: string;
|
|
147
|
+
unconfirmed: boolean;
|
|
148
|
+
}[];
|
|
149
|
+
/** In-scope live facts that did NOT make it into the prompt (budget or the legacy 20-fact cap). */
|
|
150
|
+
notShown: number;
|
|
66
151
|
}
|
|
152
|
+
/** M6: store-health snapshot for `/doctor` and the memory panel. Read-only; never touches usage counters. */
|
|
153
|
+
export interface MemoryStatus {
|
|
154
|
+
mode: MemoryWriteMode;
|
|
155
|
+
off: boolean;
|
|
156
|
+
budgetedRender: boolean;
|
|
157
|
+
budgetChars: number;
|
|
158
|
+
/** Characters the in-scope live facts would need if all were rendered. */
|
|
159
|
+
storeChars: number;
|
|
160
|
+
liveFacts: number;
|
|
161
|
+
pending: number;
|
|
162
|
+
flaggedPending: number;
|
|
163
|
+
retired: number;
|
|
164
|
+
auditEnabled: boolean;
|
|
165
|
+
auditEntries: number;
|
|
166
|
+
/** Highest audit seq already consolidated (M5 writes `memory:consolidation-state`); undefined until then. */
|
|
167
|
+
lastConsolidatedSeq?: number;
|
|
168
|
+
lastConsolidationAt?: string;
|
|
169
|
+
lastInjection?: MemoryInjection;
|
|
170
|
+
}
|
|
171
|
+
/** M6 extension point for M5: what a consolidation run reports back to `/memory consolidate`. */
|
|
172
|
+
export type ConsolidationOutcome = {
|
|
173
|
+
status: 'done';
|
|
174
|
+
message: string;
|
|
175
|
+
} | {
|
|
176
|
+
status: 'nothing_to_do';
|
|
177
|
+
message: string;
|
|
178
|
+
};
|
|
179
|
+
/** M5 registers one of these via `MemoryService.registerConsolidator()`; it proposes through `submit` so governance, the gate and the audit log apply. */
|
|
180
|
+
export type MemoryConsolidator = (ctx: {
|
|
181
|
+
submit: MemoryService['submitCandidate'];
|
|
182
|
+
sessionId: string;
|
|
183
|
+
}) => Promise<ConsolidationOutcome>;
|
|
67
184
|
/** Result of a single fact's promotion-time (`/memory confirm`) or corroboration-driven (medium→high) admission into DURABLE_FACTS_KEY — see `confirmPendingFact()`. */
|
|
68
185
|
export interface PendingConfirmationOutcome {
|
|
69
186
|
fact: UserFact;
|
|
@@ -75,6 +192,47 @@ export interface RecordFactsResult {
|
|
|
75
192
|
contradictions: ExternalContradictionInput[];
|
|
76
193
|
corroborations: Corroboration[];
|
|
77
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* `AUDIT_MEMORY_BUDGETED_RENDER` gate (M1 of the agent memory framework plan). Gates budgeted/
|
|
197
|
+
* priority rendering, keyed supersession and usage fields. `=0`/`off` restores `slice(-FACT_CAP)`
|
|
198
|
+
* and append-only writes. Default **ON** (M7 corrections scenario, 2 of 2 seeds: lexicalOff FAIL, memoryM12On PASS);
|
|
199
|
+
* `0`/`false`/`off`/`no`/`disabled` restores the legacy path. Read fresh each call; this is the one read point.
|
|
200
|
+
*/
|
|
201
|
+
export declare function memoryBudgetedRenderEnabled(env?: Record<string, string | undefined>): boolean;
|
|
202
|
+
/** `AUDIT_MEMORY_WRITE_GATE`: semantic secret redaction + injection judgement before promotion, failing closed. Default OFF until the real-model pilot (plan defaults rule); `1/true/on/yes/enabled` enables. */
|
|
203
|
+
export declare function memoryWriteGateEnabled(env?: Record<string, string | undefined>): boolean;
|
|
204
|
+
/** `AUDIT_MEMORY_AUDIT_LOG`: append-only `memory:audit`. Default OFF until the pilot; `=0` restores the unlogged path. */
|
|
205
|
+
export declare function memoryAuditLogEnabled(env?: Record<string, string | undefined>): boolean;
|
|
206
|
+
/** Outcome of `admitCandidate()` for one candidate. `session` = kept session-scoped only, never durable or pending. */
|
|
207
|
+
export interface AdmitDecision {
|
|
208
|
+
action: 'admit' | 'session' | 'flag' | 'drop';
|
|
209
|
+
fact: UserFact;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The single judgement step every writer's candidate passes through before it can be promoted (M2).
|
|
213
|
+
* Pure: the judgements were produced by the same LLM call that produced the fact. Always strips the
|
|
214
|
+
* transient `judgement`. With the gate off the candidate is returned unchanged (`admit`). With it on,
|
|
215
|
+
* for a `model_inferred` candidate: a missing judgement fails closed (`session`, durable bit cleared);
|
|
216
|
+
* a secret is replaced by the model's secret-free text, or dropped when the claim is the secret; an
|
|
217
|
+
* instruction-shaped claim is `flag`ged for the pending queue. A non-`user` origin never promotes.
|
|
218
|
+
*/
|
|
219
|
+
export declare function admitCandidate(candidate: UserFact, gateOn: boolean): AdmitDecision;
|
|
220
|
+
/** Removes verbatim copies of the injected memory block from text bound for candidate extraction (feedback-loop prevention). Structural: it removes the exact block this service rendered, not a pattern. */
|
|
221
|
+
export declare function excludeInjectedBlock(text: string, injectedBlock: string): string;
|
|
222
|
+
export interface RenderedFacts {
|
|
223
|
+
block: string;
|
|
224
|
+
/** Facts actually placed in the block, in render order. */
|
|
225
|
+
shown: UserFact[];
|
|
226
|
+
/** In-scope live facts that did not fit the budget. */
|
|
227
|
+
droppedCount: number;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Renders facts by priority and character budget, not by position: identity/preference first, then
|
|
231
|
+
* semantic, then episodic/session facts; within a tier newest statement first, then most recently
|
|
232
|
+
* injected. A fact is dropped only when the budget is spent, and the drop is counted. No
|
|
233
|
+
* query-relevance ranking (D2: no lexical scoring). Pure.
|
|
234
|
+
*/
|
|
235
|
+
export declare function renderFactsBlock(inScope: UserFact[], budgetChars: number): RenderedFacts;
|
|
78
236
|
/**
|
|
79
237
|
* `AUDIT_MODEL_INFERRED_FACTS` gate — feature-value audit (Phase C3 of the internal plan).
|
|
80
238
|
* Default **ON**: `classifyTurnIntent`'s `statesDurableFacts` (source `model_inferred`) reach
|
|
@@ -96,7 +254,7 @@ export declare function modelInferredFactsEnabled(env?: Record<string, string |
|
|
|
96
254
|
* it) each stamp their own `extractedAt` — harmless, since neither call's result is compared
|
|
97
255
|
* against the other's by identity.
|
|
98
256
|
*/
|
|
99
|
-
export declare function buildTurnFacts(sessionId: string, userMessage: string, statedFacts: StatedFact[]): UserFact[];
|
|
257
|
+
export declare function buildTurnFacts(sessionId: string, userMessage: string, statedFacts: StatedFact[], policyEnabled?: boolean, now?: () => string): UserFact[];
|
|
100
258
|
/**
|
|
101
259
|
* Owns fact/reminder/experience reads that feed each turn's system prompt, fact capture, and the
|
|
102
260
|
* `/memory`/`/memory export` read-only snapshots — the "what has this assistant learned" surface
|
|
@@ -114,9 +272,99 @@ export declare class MemoryService {
|
|
|
114
272
|
private readonly model;
|
|
115
273
|
/** 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. */
|
|
116
274
|
private readonly currentProject;
|
|
275
|
+
/** M1: character budget for the facts block under `AUDIT_MEMORY_BUDGETED_RENDER` (config `memoryBudgetChars`). */
|
|
276
|
+
private readonly memoryBudgetChars;
|
|
277
|
+
/** M6: governance mode (config `memoryWriteMode`), read through a getter like the others so a live change applies on the next write. Resolved here and nowhere else. */
|
|
278
|
+
private readonly writeMode;
|
|
279
|
+
/** Time source for every timestamp this service writes (extractedAt of stated facts, audit `at`, retiredAt, usage flush). Injectable so the cross-runtime conformance fixtures are byte-reproducible; default is wall-clock, i.e. no behaviour change. */
|
|
280
|
+
private readonly clock;
|
|
117
281
|
constructor(memory: MemoryAdapter, reminderStore: ReminderStore, experienceStore: ExperienceStore, llmClient: ILLMClient, model: () => string | undefined,
|
|
118
282
|
/** 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. */
|
|
119
|
-
currentProject?: () => string
|
|
283
|
+
currentProject?: () => string,
|
|
284
|
+
/** M1: character budget for the facts block under `AUDIT_MEMORY_BUDGETED_RENDER` (config `memoryBudgetChars`). */
|
|
285
|
+
memoryBudgetChars?: () => number,
|
|
286
|
+
/** M6: governance mode (config `memoryWriteMode`), read through a getter like the others so a live change applies on the next write. Resolved here and nowhere else. */
|
|
287
|
+
writeMode?: () => MemoryWriteMode,
|
|
288
|
+
/** Time source for every timestamp this service writes (extractedAt of stated facts, audit `at`, retiredAt, usage flush). Injectable so the cross-runtime conformance fixtures are byte-reproducible; default is wall-clock, i.e. no behaviour change. */
|
|
289
|
+
clock?: () => string);
|
|
290
|
+
private consolidator?;
|
|
291
|
+
private lastInjection?;
|
|
292
|
+
/** `/memory off` state — M3/M4/M5 writers must call this before any write or LLM spend. */
|
|
293
|
+
isMemoryOff(): Promise<boolean>;
|
|
294
|
+
setMemoryOff(off: boolean): Promise<void>;
|
|
295
|
+
/** M5 extension point (not built here): until a consolidator is registered `/memory consolidate` says so instead of pretending. */
|
|
296
|
+
registerConsolidator(fn: MemoryConsolidator | undefined): void;
|
|
297
|
+
consolidate(sessionId: string): Promise<ConsolidationOutcome | {
|
|
298
|
+
status: 'unavailable' | 'blocked';
|
|
299
|
+
message: string;
|
|
300
|
+
}>;
|
|
301
|
+
/**
|
|
302
|
+
* M6 extension point for M3 (digest), M4 (reviewer) and M5 (consolidation) writers: the ONE way a
|
|
303
|
+
* cross-turn candidate enters memory. It applies, in order: `/memory off`, the M2 gate
|
|
304
|
+
* (`admitCandidate`: redaction, injection flag, fail-closed judgement, non-user origin), then the
|
|
305
|
+
* governance route for `memoryWriteMode` (`resolveWriteRoute`), and writes through the same
|
|
306
|
+
* single durable commit point / pending queue / audit log as every other writer. Add-only: removal
|
|
307
|
+
* and retirement stay with the user or a staged proposal.
|
|
308
|
+
*/
|
|
309
|
+
submitCandidate(writer: CrossTurnWriter, candidate: UserFact, sessionId: string, opts?: {
|
|
310
|
+
pendingExtras?: Partial<PendingFact>;
|
|
311
|
+
forceGate?: boolean;
|
|
312
|
+
}): Promise<{
|
|
313
|
+
route: 'durable' | 'pending' | 'session' | 'dropped' | 'blocked';
|
|
314
|
+
fact?: UserFact;
|
|
315
|
+
}>;
|
|
316
|
+
/** Facts rendered since the last flush, keyed by `text|extractedAt`; written lazily so loadFacts() stays read-only (M1). */
|
|
317
|
+
private pendingInjections;
|
|
318
|
+
/** Number of in-scope facts the last budgeted render could not show — `/memory` says "N facts not shown this turn". */
|
|
319
|
+
lastDroppedCount: number;
|
|
320
|
+
/** The facts block most recently rendered into a prompt; excluded verbatim from candidate extraction (M2 feedback-loop prevention). */
|
|
321
|
+
private lastInjectedBlock;
|
|
322
|
+
private digestStoreInstance?;
|
|
323
|
+
/** Incremented whenever recordFacts() or stageReviewerOps() changes any memory store; the M4 trigger compares it before and after a turn. */
|
|
324
|
+
writeCount: number;
|
|
325
|
+
/** The facts block most recently injected into a prompt (what the M4 reviewer must strip from its input). */
|
|
326
|
+
getInjectedBlock(): string;
|
|
327
|
+
/**
|
|
328
|
+
* Keys of the live durable facts, with their text, for the classifier prompt (M1 key stability:
|
|
329
|
+
* the model reuses a stored key for a new value of the same attribute instead of inventing a
|
|
330
|
+
* variant that would miss supersession). Newest first, capped so the prompt stays small. Empty
|
|
331
|
+
* when keyed supersession is off.
|
|
332
|
+
*/
|
|
333
|
+
getKnownFactKeys(limit?: number): Promise<{
|
|
334
|
+
key: string;
|
|
335
|
+
text: string;
|
|
336
|
+
}[]>;
|
|
337
|
+
/** Durable facts in stored order — the list a reviewer `retire` op's `targetId` indexes into. */
|
|
338
|
+
getDurableFacts(): Promise<UserFact[]>;
|
|
339
|
+
/**
|
|
340
|
+
* M4: stages the post-turn reviewer's operations. Per decision D1 nothing here reaches durable memory: surviving ops
|
|
341
|
+
* wait in the pending queue with their evidence (shown by `/memory`). Plain-code gates, no language judgement:
|
|
342
|
+
* a `retire` of a `user_asserted` fact (or of an unknown target) is refused; every op passes `admitCandidate()`;
|
|
343
|
+
* an op the verifier called unsupported is kept session-scoped only. All writes happen after the abort check, in one
|
|
344
|
+
* batch per store, so an abort leaves no partial write. `durableSnapshot` is the list the reviewer's `targetId`s index.
|
|
345
|
+
*/
|
|
346
|
+
stageReviewerOps(sessionId: string, ops: readonly VerifiedReviewOp[], durableSnapshot: readonly UserFact[], signal?: AbortSignal): Promise<StageReviewerResult>;
|
|
347
|
+
/**
|
|
348
|
+
* M2: the ONLY place `DURABLE_FACTS_KEY` is written. Every add, replace, retire, remove, confirm
|
|
349
|
+
* and usage-flush goes through here, so a new writer cannot skip the audit log. A structural test
|
|
350
|
+
* (memory-write-gate.test.ts) asserts this is the only call in the file that writes the durable key.
|
|
351
|
+
*/
|
|
352
|
+
private commitDurable;
|
|
353
|
+
private appendAudit;
|
|
354
|
+
/** `/memory history` — newest last; the last `limit` entries. */
|
|
355
|
+
getAuditLog(limit?: number): Promise<AuditEntry[]>;
|
|
356
|
+
/**
|
|
357
|
+
* `/memory undo <seq>` — restores the pre-image of one audit entry exactly (and removes what that
|
|
358
|
+
* entry added), then appends an `undo` entry. An entry can be undone once. Returns a message for
|
|
359
|
+
* the caller, or undefined when the seq is unknown.
|
|
360
|
+
*/
|
|
361
|
+
undoAudit(seq: number, sessionId?: string): Promise<{
|
|
362
|
+
ok: boolean;
|
|
363
|
+
message: string;
|
|
364
|
+
}>;
|
|
365
|
+
/** Undo of the change that created a side store must leave it absent again, not an empty array (exact restoration). */
|
|
366
|
+
private setOrClear;
|
|
367
|
+
private undoEntry;
|
|
120
368
|
/**
|
|
121
369
|
* Durable + session facts for `sessionId`, plus the ready-to-splice system-prompt block — see
|
|
122
370
|
* runTurn's former factsBlock. `facts` is the full inventory, unfiltered by project — `/memory`
|
|
@@ -125,9 +373,15 @@ export declare class MemoryService {
|
|
|
125
373
|
* global-or-current-project only, so an unrelated project's facts don't leak into context (see
|
|
126
374
|
* UserFact.project's doc comment).
|
|
127
375
|
*/
|
|
128
|
-
loadFacts(sessionId: string
|
|
376
|
+
loadFacts(sessionId: string, opts?: {
|
|
377
|
+
record?: boolean;
|
|
378
|
+
}): Promise<{
|
|
129
379
|
facts: UserFact[];
|
|
130
380
|
factsBlock: string;
|
|
381
|
+
knownFactKeys: {
|
|
382
|
+
key: string;
|
|
383
|
+
text: string;
|
|
384
|
+
}[];
|
|
131
385
|
}>;
|
|
132
386
|
/**
|
|
133
387
|
* `/memory forget <n>` — removes the nth entry (1-based in `/memory`'s "Facts I know" display,
|
|
@@ -139,7 +393,7 @@ export declare class MemoryService {
|
|
|
139
393
|
* happened to win the display dedup. Returns undefined for an out-of-range index (the caller's
|
|
140
394
|
* `/memory` view is stale — nothing to forget).
|
|
141
395
|
*/
|
|
142
|
-
forgetFact(index: number, sessionId: string): Promise<UserFact | undefined>;
|
|
396
|
+
forgetFact(index: number, sessionId: string, erase?: boolean): Promise<UserFact | undefined>;
|
|
143
397
|
/**
|
|
144
398
|
* reminderStore is cross-session durable (clearSession() never touches it, same tier as
|
|
145
399
|
* DURABLE_FACTS_KEY) but, unlike facts, was never actually surfaced into context: a plain
|
|
@@ -157,7 +411,7 @@ export declare class MemoryService {
|
|
|
157
411
|
}>;
|
|
158
412
|
/**
|
|
159
413
|
* Captures this turn's durable/session facts into the session's fact store. A no-op when
|
|
160
|
-
* neither pass finds anything. Facts
|
|
414
|
+
* neither pass finds anything. Facts routed to durable by `resolveWriteRoute()` (memory-governance.ts; see its doc
|
|
161
415
|
* comment for the current three-way policy) are ALSO appended to DURABLE_FACTS_KEY, a store
|
|
162
416
|
* clearSession() never touches, so they survive /new instead of vanishing with the rest of the
|
|
163
417
|
* session's facts. A `model_inferred` fact at `durable: true, confidence: 'medium'` is queued
|
|
@@ -190,6 +444,49 @@ export declare class MemoryService {
|
|
|
190
444
|
* computed but never surfaced in the turn's own reply.
|
|
191
445
|
*/
|
|
192
446
|
recordFacts(sessionId: string, userMessage: string, statedFacts: StatedFact[], onUsage?: (usage: TokenUsage) => void): Promise<RecordFactsResult>;
|
|
447
|
+
private consolidating;
|
|
448
|
+
/**
|
|
449
|
+
* M5: proposes consolidation and archive changes; applies NOTHING (D1: staged). Work is for the
|
|
450
|
+
* stores as the previous session left them: callers run it at session start or `/new`, or on the
|
|
451
|
+
* manual `/memory consolidate` (`manual` forces the model pass).
|
|
452
|
+
*
|
|
453
|
+
* Cost gate, deterministic and before any model call: the model pass runs only when the audit log
|
|
454
|
+
* has entries newer than `memory:consolidation-state.lastSeq` or the curated (durable) store is
|
|
455
|
+
* over the character budget, and there are at least two live facts; otherwise zero model calls.
|
|
456
|
+
* Archive proposals use no model: only M1's usage timestamps and the retention window. A failed
|
|
457
|
+
* or unusable model call changes nothing (the watermark does not move, so the next run retries).
|
|
458
|
+
* Requires the audit log: a change that cannot be undone is never proposed.
|
|
459
|
+
*/
|
|
460
|
+
runConsolidation(opts?: {
|
|
461
|
+
manual?: boolean;
|
|
462
|
+
onUsage?: (usage: TokenUsage) => void;
|
|
463
|
+
}): Promise<{
|
|
464
|
+
status: 'disabled' | 'needs_audit_log' | 'busy' | 'skipped' | 'ran' | 'failed';
|
|
465
|
+
proposed: number;
|
|
466
|
+
modelCalls: number;
|
|
467
|
+
projectedChars?: number;
|
|
468
|
+
usedChars?: number;
|
|
469
|
+
}>;
|
|
470
|
+
getConsolidationProposals(): Promise<ConsolidationProposal[]>;
|
|
471
|
+
/** `/memory archive` — what staged forgetting has set aside. Nothing in it is deleted. */
|
|
472
|
+
getArchivedFacts(): Promise<UserFact[]>;
|
|
473
|
+
/** `/memory consolidate dismiss <n>`: drops the proposal and remembers its signature so it is not raised again. */
|
|
474
|
+
dismissProposal(index: number): Promise<ConsolidationProposal | undefined>;
|
|
475
|
+
/**
|
|
476
|
+
* `/memory consolidate accept <n>`: the user's explicit yes. Applies one staged proposal to the
|
|
477
|
+
* durable store through `commitDurable` as one audit group (`/memory undo <seq>` reverts all of it,
|
|
478
|
+
* restoring the store and its order exactly). Merged/superseded originals go to the retired store,
|
|
479
|
+
* archived ones to the archive store; nothing is deleted. Refuses without the audit log, and treats
|
|
480
|
+
* a proposal whose facts changed since it was staged as stale.
|
|
481
|
+
*/
|
|
482
|
+
acceptProposal(index: number, sessionId?: string): Promise<{
|
|
483
|
+
ok: boolean;
|
|
484
|
+
message: string;
|
|
485
|
+
}>;
|
|
486
|
+
/** `/memory archive restore <n>`: puts an archived entry back among the live facts (audited; undoable). */
|
|
487
|
+
restoreArchivedFact(listIndex: number, sessionId?: string): Promise<UserFact | undefined>;
|
|
488
|
+
/** Writes the batched `injectedCount`/`lastInjectedAt` updates collected by budgeted renders, to whichever store(s) hold each fact. A no-op (no writes) when nothing was rendered. */
|
|
489
|
+
private flushInjectionUsage;
|
|
193
490
|
/**
|
|
194
491
|
* `/memory confirm <n>` — promotes the nth (1-based in `/memory`'s display, 0-based here) entry
|
|
195
492
|
* in PENDING_CONFIRMATION_KEY to DURABLE_FACTS_KEY. Runs the same promotion-time check as
|
|
@@ -226,4 +523,46 @@ export declare class MemoryService {
|
|
|
226
523
|
* export`.
|
|
227
524
|
*/
|
|
228
525
|
exportMemory(sessionId: string): Promise<MemoryExport>;
|
|
526
|
+
/** What the most recent turn's facts block contained (undefined before the first turn). Set only by the turn path, never by read-only views. */
|
|
527
|
+
getLastInjection(): MemoryInjection | undefined;
|
|
528
|
+
/** `/memory archive`: entries a keyed update replaced, newest first. M5 EXTENSION POINT: its archive store joins here (and in `exportMemory`) when it exists. */
|
|
529
|
+
listArchive(): Promise<UserFact[]>;
|
|
530
|
+
/**
|
|
531
|
+
* `/memory archive forget <n>`: permanently erases one archived entry (index over `listArchive()`),
|
|
532
|
+
* including its pre-images in the audit log (those entries stay, marked `erased`, and can no longer
|
|
533
|
+
* be undone). The only path that edits the audit log other than appending, and only on an explicit user request.
|
|
534
|
+
*/
|
|
535
|
+
forgetArchived(index: number): Promise<UserFact | undefined>;
|
|
536
|
+
/** Strips one fact's pre/post-images from the audit log (entries stay, marked `erased`, and can no longer be undone) and unlinks `supersedes` references to it. */
|
|
537
|
+
private eraseFromAuditLog;
|
|
538
|
+
/** Read-only health snapshot (`/doctor`, memory panel). Never touches usage counters or the last-injection record. */
|
|
539
|
+
getMemoryStatus(sessionId: string): Promise<MemoryStatus>;
|
|
540
|
+
/** M3 (D4): `/memory forget digest <id>` or, with no id, every digest. Returns how many were removed. */
|
|
541
|
+
forgetDigests(id?: string): Promise<number>;
|
|
542
|
+
/** M3: read-only digest reader over the same store the recall tool uses. */
|
|
543
|
+
get digests(): DigestStore;
|
|
544
|
+
/** Token usage of the most recent digest/flush calls made by this service (cost per session edge). */
|
|
545
|
+
digestUsage: {
|
|
546
|
+
inputTokens: number;
|
|
547
|
+
outputTokens: number;
|
|
548
|
+
costUsd: number;
|
|
549
|
+
};
|
|
550
|
+
private trackDigestUsage;
|
|
551
|
+
/**
|
|
552
|
+
* M3 session-edge digest (`/new`, exit). One bounded call, fail-open: any failure leaves the previous digest (if any)
|
|
553
|
+
* untouched. Skipped when the conversation has no user message. Output is judged and redacted by `admitCandidate`
|
|
554
|
+
* (see episodic-digest.ts) and stored at `episodic:<id>`; it never touches durable facts. Callers gate on
|
|
555
|
+
* `episodicDigestEnabled()`; never call from inside a turn.
|
|
556
|
+
*/
|
|
557
|
+
writeSessionDigest(sessionId: string, transcript: ChatMessage[], onUsage?: (usage: TokenUsage) => void): Promise<SessionDigest | null>;
|
|
558
|
+
/** M3: ends the conversation's digest identity so the next conversation under the same session id gets a new digest. Call after `writeSessionDigest`. */
|
|
559
|
+
endDigestConversation(sessionId: string): Promise<void>;
|
|
560
|
+
/**
|
|
561
|
+
* M3 pre-compaction flush: called with exactly the messages compaction is about to drop. One call extracts candidate
|
|
562
|
+
* facts and a digest delta (folded into the conversation's digest). Candidates are judged by `admitCandidate` and go
|
|
563
|
+
* to the pending queue only, never durable. Fail-open: returns 0 on any failure and compaction proceeds regardless.
|
|
564
|
+
*/
|
|
565
|
+
flushBeforeCompaction(sessionId: string, older: ChatMessage[], onUsage?: (usage: TokenUsage) => void): Promise<number>;
|
|
566
|
+
/** Judges flush candidates (gate forced on: a missing judgement drops the candidate) and appends the survivors to the pending queue. Exact-text duplicates of anything pending, durable or rejected are skipped. */
|
|
567
|
+
private queueFlushCandidates;
|
|
229
568
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant parsing of JSON a model was asked to return.
|
|
3
|
+
*
|
|
4
|
+
* A backend that can't enforce a schema (claude-cli in particular) sometimes wraps the JSON in a code
|
|
5
|
+
* fence, puts a stray tag or a sentence of reasoning in front of it, or trails prose after it. A strict
|
|
6
|
+
* `JSON.parse` then throws, and every classifier in this package fails open — silently losing the
|
|
7
|
+
* check. This is the one place that recovers the JSON from that, so no call site needs its own copy.
|
|
8
|
+
*
|
|
9
|
+
* It is a strict superset of `JSON.parse`: text that already parses parses to exactly the same value,
|
|
10
|
+
* and only text that would have thrown is looked at further.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Parses `text` as JSON, or, failing that, the first (outermost, leftmost) JSON object/array embedded in
|
|
14
|
+
* it. Throws a `SyntaxError` when there is none — the same failure a plain `JSON.parse` gives, so a
|
|
15
|
+
* caller's existing `catch` keeps working.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseModelJson(text: string): unknown;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ChatMessage } from '@buildaharness/runtime';
|
|
2
|
+
import { GoalGraphRecord } from './goal-graph-store.js';
|
|
3
|
+
import { AssistantSource } from './assistant-source.js';
|
|
4
|
+
export interface NextStepConversationTurn {
|
|
5
|
+
role: 'user' | 'assistant';
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface NextStepGoalOverview {
|
|
9
|
+
goal: string;
|
|
10
|
+
status: string;
|
|
11
|
+
/** True for the thread the suggestion is about (the finished thread, or the active one at turn end). */
|
|
12
|
+
focus: boolean;
|
|
13
|
+
tasks: {
|
|
14
|
+
description: string;
|
|
15
|
+
status: string;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Next steps already proposed for this goal when it finished (the high/medium-confidence ones the
|
|
19
|
+
* thread persisted), for goals other than the focus one. They are how an open item of an earlier
|
|
20
|
+
* goal survives after that goal has left the conversation window. Absent when there are none.
|
|
21
|
+
*/
|
|
22
|
+
openSuggestions?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface NextStepContext {
|
|
25
|
+
/** Earlier user/assistant messages, oldest first, excluding the exchange the suggestion is about. */
|
|
26
|
+
conversation?: NextStepConversationTurn[];
|
|
27
|
+
/** Every goal thread the session tracks, unfinished ones first. */
|
|
28
|
+
goals?: NextStepGoalOverview[];
|
|
29
|
+
/** Files read / searches made while producing this turn's reply. */
|
|
30
|
+
stepsThisTurn?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The last few user/assistant messages, oldest first. `currentExchange`, when given, drops the
|
|
34
|
+
* trailing user+assistant pair that is the exchange being suggested for (the proposer already gets
|
|
35
|
+
* it in full), so it is not sent twice. Tool messages and empty messages are skipped.
|
|
36
|
+
*/
|
|
37
|
+
export declare function summarizeConversation(transcript: ChatMessage[], currentExchange?: {
|
|
38
|
+
userMessage: string;
|
|
39
|
+
}): NextStepConversationTurn[];
|
|
40
|
+
/** Every goal thread with its tasks, the focus thread and unfinished threads first. Abandoned threads are left out. */
|
|
41
|
+
export declare function summarizeGoalGraph(record: GoalGraphRecord | undefined, focusThreadId?: string): NextStepGoalOverview[];
|
|
42
|
+
export declare function summarizeSteps(sources: AssistantSource[] | undefined): string[];
|
|
43
|
+
export declare function buildNextStepContext(parts: {
|
|
44
|
+
transcript?: ChatMessage[];
|
|
45
|
+
currentExchange?: {
|
|
46
|
+
userMessage: string;
|
|
47
|
+
};
|
|
48
|
+
goalGraph?: GoalGraphRecord;
|
|
49
|
+
focusThreadId?: string;
|
|
50
|
+
sources?: AssistantSource[];
|
|
51
|
+
}): NextStepContext;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
2
|
import { GoalThread } from './goal-graph-store.js';
|
|
3
3
|
import { GoalGraphSuggestMode } from './goal-graph-suggest-flag.js';
|
|
4
|
+
import { NextStepContext } from './next-step-context.js';
|
|
4
5
|
export type SuggestionConfidence = 'high' | 'medium' | 'low';
|
|
5
6
|
/** Mirrors `shouldAutoPromote()`'s three-way policy *shape* (memory-service.ts) — not its bands, since Q10 found no numeric threshold to reuse: `high` auto-promotes, `medium` queues for explicit confirmation, `low` stays advisory-only and is never persisted past the turn that produced it. */
|
|
6
7
|
export type SuggestionPromotion = 'auto' | 'pending_confirm' | 'session_only';
|
|
@@ -35,7 +36,7 @@ export declare function classifySuggestionPromotion(confidence: SuggestionConfid
|
|
|
35
36
|
* `thread` hasn't actually reached `DONE` yet, since a suggestion only makes sense once the goal
|
|
36
37
|
* it follows from is finished.
|
|
37
38
|
*/
|
|
38
|
-
export declare function proposeNextSteps(thread: GoalThread, llmClient: ILLMClient, suggestMode: GoalGraphSuggestMode, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<SuggestedNextStepNode[]>;
|
|
39
|
+
export declare function proposeNextSteps(thread: GoalThread, llmClient: ILLMClient, suggestMode: GoalGraphSuggestMode, model?: string, onUsage?: (usage: TokenUsage) => void, bigPicture?: NextStepContext): Promise<SuggestedNextStepNode[]>;
|
|
39
40
|
/**
|
|
40
41
|
* The turn-end entry point: after a full (non-trivial) turn completes, propose up to three
|
|
41
42
|
* concrete next steps for the user to pick from — shown under the reply, like Claude Code's prompt
|
|
@@ -49,4 +50,4 @@ export declare function proposeNextSteps(thread: GoalThread, llmClient: ILLMClie
|
|
|
49
50
|
export declare function proposeTurnNextSteps(turn: {
|
|
50
51
|
userMessage: string;
|
|
51
52
|
reply: string;
|
|
52
|
-
}, llmClient: ILLMClient, suggestMode: GoalGraphSuggestMode, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<NextStepSuggestion[]>;
|
|
53
|
+
}, llmClient: ILLMClient, suggestMode: GoalGraphSuggestMode, model?: string, onUsage?: (usage: TokenUsage) => void, bigPicture?: NextStepContext): Promise<NextStepSuggestion[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic backing the plan-graph visualization (read-only plan view on the CLI and desktop/web
|
|
3
|
+
* surfaces) — see plans/plan_visualization_plan.html. Same pattern as goal-graph-flag.ts.
|
|
4
|
+
*
|
|
5
|
+
* Presentation only: the flag never changes a plan, the harness or any persisted state, so it is
|
|
6
|
+
* deliberately NOT in LAYER_SETTINGS. 'disabled' (the default) leaves every surface byte-identical
|
|
7
|
+
* to the behaviour before the feature existed (INV-43). `AssistantConfig.planGraphMode` stays out of
|
|
8
|
+
* DEFAULT_CONFIG (undefined = "the package owns the default"), so consumers go through
|
|
9
|
+
* `isPlanGraphEnabled` rather than comparing against a possibly-undefined value.
|
|
10
|
+
*/
|
|
11
|
+
export type PlanGraphMode = 'enabled' | 'disabled';
|
|
12
|
+
export declare const DEFAULT_PLAN_GRAPH_MODE: PlanGraphMode;
|
|
13
|
+
export declare function isPlanGraphEnabled(mode: PlanGraphMode | undefined): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Shared by cli-config.ts (via `resolvePlanGraphMode`) and chat-ui's browser-config.ts (Vite's
|
|
16
|
+
* `import.meta.env.VITE_ASSISTANT_PLAN_GRAPH`, which has no `process`). An unset or empty value
|
|
17
|
+
* falls back silently; an unrecognized value falls back with a warning naming `varName`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizePlanGraphMode(raw: string | undefined, varName?: string): PlanGraphMode;
|
|
20
|
+
export declare function resolvePlanGraphMode(env: NodeJS.ProcessEnv): PlanGraphMode;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PlanRecord, PlanTaskRecord } from './plan-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* How a message that only asks about the active plan (the classifier's `isPlanQuestion`) is handled:
|
|
4
|
+
* - `stuck` (default): answered from the plan's recorded state ONLY while the plan is stuck on a failed
|
|
5
|
+
* task — exactly where re-driving the plan is known to go wrong. A healthy plan behaves as it always
|
|
6
|
+
* has, so this default changes nothing for it.
|
|
7
|
+
* - `always`: any plan question is answered from the recorded state, healthy plan or not.
|
|
8
|
+
* - `off`: an active plan drives every non-trivial turn (the pre-routing behaviour).
|
|
9
|
+
* `AUDIT_PLAN_QUESTION_ROUTING`: `always`/`on`/`1`/`true` → always; `0`/`false`/`off`/`no`/`disabled` → off;
|
|
10
|
+
* empty or `stuck` → stuck. Read at exactly one site: assistant.ts, where the turn is routed.
|
|
11
|
+
*/
|
|
12
|
+
export type PlanQuestionRouting = 'off' | 'stuck' | 'always';
|
|
13
|
+
export declare function planQuestionRoutingMode(env?: Record<string, string | undefined>): PlanQuestionRouting;
|
|
14
|
+
/** A plan is stuck when a step failed and was not skipped: the harness cannot advance past it on its own. */
|
|
15
|
+
export declare function isPlanStuck(plan: PlanRecord): boolean;
|
|
16
|
+
export declare function shouldRoutePlanQuestion(input: {
|
|
17
|
+
mode: PlanQuestionRouting;
|
|
18
|
+
plan: PlanRecord | null;
|
|
19
|
+
isPlanQuestion: boolean | undefined;
|
|
20
|
+
}): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* `AUDIT_STUCK_PLAN_RESUME` (default ON): a plan stuck on a failed step is resumed only by a message that says to carry it
|
|
23
|
+
* on (the classifier's `continuesPlan`). Any other message is answered on its own terms — the plan is not handed to the
|
|
24
|
+
* harness, so the failed step is not silently retried — and the reply ends by asking whether to retry it. `0`/`false`/
|
|
25
|
+
* `off`/`no`/`disabled` restores the old behaviour (any non-trivial message on a stuck plan retries the step).
|
|
26
|
+
*/
|
|
27
|
+
export declare function stuckPlanResumeEnabled(env?: Record<string, string | undefined>): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True when the turn should set the stuck plan aside: the plan is stuck, the message is not a question about it (that is
|
|
30
|
+
* routed separately) and the classifier said explicitly that it does not continue it. A missing `continuesPlan` is not
|
|
31
|
+
* "no" — it keeps today's behaviour.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shouldSetAsideStuckPlan(input: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
plan: PlanRecord | null;
|
|
36
|
+
isPlanQuestion: boolean | undefined;
|
|
37
|
+
continuesPlan: boolean | undefined;
|
|
38
|
+
}): boolean;
|
|
39
|
+
/** Prompt addendum for a turn that set a stuck plan aside: answer the message, then ask about the failed step. */
|
|
40
|
+
export declare function renderStuckPlanNudge(plan: PlanRecord): string;
|
|
41
|
+
/**
|
|
42
|
+
* The plan's tasks as the harness should see them for a turn that moves the plan forward. A step that
|
|
43
|
+
* failed is put back to pending so it gets another attempt (left as FAILED it would strand the run on
|
|
44
|
+
* every later turn), and its description carries why the last attempt was rejected so the retry can
|
|
45
|
+
* address that instead of repeating it. Only the harness's copy changes: the stored plan keeps its
|
|
46
|
+
* original description, and its status is rewritten from the run's outcome afterwards.
|
|
47
|
+
*/
|
|
48
|
+
export declare function planTasksForRun(tasks: PlanTaskRecord[]): PlanTaskRecord[];
|
|
49
|
+
/**
|
|
50
|
+
* What to add to the reply of a turn that ran an approved plan and stopped because a step was not
|
|
51
|
+
* accepted as done. Without it the reply is just the last step that DID complete, and the user only finds
|
|
52
|
+
* out the plan stalled by asking. Empty when nothing was rejected this turn.
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderPlanStopNote(plan: PlanRecord, taskNotes: Record<string, string> | undefined): string;
|
|
55
|
+
/**
|
|
56
|
+
* The plan's real state, rendered for the prompt of a turn that only asks about it. Grounds the answer
|
|
57
|
+
* in what the task graph actually recorded (a task is "done" only if it completed; a failed one carries
|
|
58
|
+
* the reason it was not accepted) so the reply can't drift into describing the plan as it was drafted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function renderPlanStateBlock(plan: PlanRecord): string;
|