@wrongstack/core 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/agent-bridge-6KPqsFx6.d.ts +33 -0
  2. package/dist/compactor-B4mQZXf2.d.ts +17 -0
  3. package/dist/config-BU9f_5yH.d.ts +193 -0
  4. package/dist/{provider-txgB0Oq9.d.ts → context-BmM2xGUZ.d.ts} +532 -472
  5. package/dist/coordination/index.d.ts +694 -0
  6. package/dist/coordination/index.js +1995 -0
  7. package/dist/coordination/index.js.map +1 -0
  8. package/dist/defaults/index.d.ts +34 -2309
  9. package/dist/defaults/index.js +3893 -3803
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/events-BMNaEFZl.d.ts +218 -0
  12. package/dist/execution/index.d.ts +260 -0
  13. package/dist/execution/index.js +1625 -0
  14. package/dist/execution/index.js.map +1 -0
  15. package/dist/index.d.ts +47 -10
  16. package/dist/index.js +6617 -6093
  17. package/dist/index.js.map +1 -1
  18. package/dist/infrastructure/index.d.ts +10 -0
  19. package/dist/infrastructure/index.js +575 -0
  20. package/dist/infrastructure/index.js.map +1 -0
  21. package/dist/input-reader-E-ffP2ee.d.ts +12 -0
  22. package/dist/kernel/index.d.ts +15 -4
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/logger-BH6AE0W9.d.ts +24 -0
  25. package/dist/logger-BMQgxvdy.d.ts +12 -0
  26. package/dist/mcp-servers-Dzgg4x1w.d.ts +100 -0
  27. package/dist/memory-CEXuo7sz.d.ts +16 -0
  28. package/dist/mode-CV077NjV.d.ts +27 -0
  29. package/dist/models/index.d.ts +60 -0
  30. package/dist/models/index.js +621 -0
  31. package/dist/models/index.js.map +1 -0
  32. package/dist/models-registry-DqzwpBQy.d.ts +46 -0
  33. package/dist/models-registry-Y2xbog0E.d.ts +95 -0
  34. package/dist/multi-agent-fmkRHtof.d.ts +283 -0
  35. package/dist/observability/index.d.ts +353 -0
  36. package/dist/observability/index.js +691 -0
  37. package/dist/observability/index.js.map +1 -0
  38. package/dist/observability-BhnVLBLS.d.ts +67 -0
  39. package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
  40. package/dist/path-resolver-DBjaoXFq.d.ts +54 -0
  41. package/dist/plugin-DJk6LL8B.d.ts +434 -0
  42. package/dist/renderer-rk_1Swwc.d.ts +158 -0
  43. package/dist/sdd/index.d.ts +206 -0
  44. package/dist/sdd/index.js +864 -0
  45. package/dist/sdd/index.js.map +1 -0
  46. package/dist/secret-scrubber-CicHLN4G.d.ts +31 -0
  47. package/dist/secret-scrubber-DF88luOe.d.ts +54 -0
  48. package/dist/secret-vault-DoISxaKO.d.ts +19 -0
  49. package/dist/security/index.d.ts +30 -0
  50. package/dist/security/index.js +524 -0
  51. package/dist/security/index.js.map +1 -0
  52. package/dist/selector-BbJqiEP4.d.ts +51 -0
  53. package/dist/session-reader-Drq8RvJu.d.ts +150 -0
  54. package/dist/skill-DhfSizKv.d.ts +72 -0
  55. package/dist/storage/index.d.ts +382 -0
  56. package/dist/storage/index.js +1530 -0
  57. package/dist/storage/index.js.map +1 -0
  58. package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-BC_8ypCG.d.ts} +1 -1
  59. package/dist/task-graph-BITvWt4t.d.ts +160 -0
  60. package/dist/tool-executor-CpuJPYm9.d.ts +97 -0
  61. package/dist/types/index.d.ts +26 -4
  62. package/dist/types/index.js +1787 -4
  63. package/dist/types/index.js.map +1 -1
  64. package/dist/utils/index.d.ts +49 -2
  65. package/dist/utils/index.js +100 -2
  66. package/dist/utils/index.js.map +1 -1
  67. package/package.json +34 -2
  68. package/dist/mode-Pjt5vMS6.d.ts +0 -815
  69. package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
@@ -0,0 +1,51 @@
1
+ import { M as Message } from './context-BmM2xGUZ.js';
2
+
3
+ /**
4
+ * Result of LLM-driven message importance analysis.
5
+ * The selector marks each message range with an importance tier,
6
+ * and optionally provides a natural-language summary for collapsed ranges.
7
+ */
8
+ interface SelectorResult {
9
+ /**
10
+ * Ordered list of kept message ranges. Each entry describes a
11
+ * message range that should be preserved verbatim in the context.
12
+ */
13
+ kept: Array<{
14
+ from: number;
15
+ to: number;
16
+ importance: 'critical' | 'high' | 'medium';
17
+ }>;
18
+ /**
19
+ * Collapsed ranges — either replaced by the compactor or omitted.
20
+ * Each entry may carry a summary text produced by the LLM.
21
+ */
22
+ collapsed: Array<{
23
+ from: number;
24
+ to: number;
25
+ summary?: string;
26
+ }>;
27
+ /**
28
+ * Raw reasoning from the selector LLM (for debugging / audit).
29
+ */
30
+ reasoning: string;
31
+ }
32
+ /**
33
+ * Message selector that uses an LLM to decide which message ranges
34
+ * to keep vs collapse/summarize. The selector runs as a separate API
35
+ * call before compaction, making it more surgical than fixed-window
36
+ * or rules-based approaches.
37
+ */
38
+ interface MessageSelector {
39
+ /**
40
+ * Analyze `messages` and return a structured plan for what to keep
41
+ * vs collapse. May modify the messages array in-place if needed,
42
+ * or return a plan that the caller (Compactor) executes.
43
+ *
44
+ * @param messages Current message history (may be modified in-place)
45
+ * @param maxToKeep Token budget — selector should aim to keep total
46
+ * retained message content under this threshold
47
+ */
48
+ select(messages: Message[], maxToKeep: number): Promise<SelectorResult>;
49
+ }
50
+
51
+ export type { MessageSelector as M, SelectorResult as S };
@@ -0,0 +1,150 @@
1
+ import { b as ContentBlock, m as SessionEvent, n as SessionMetadata, o as SessionStore } from './context-BmM2xGUZ.js';
2
+
3
+ type AttachmentKind = 'text' | 'image' | 'file';
4
+ interface AttachmentMeta {
5
+ /** Display label for the placeholder e.g. "123 lines" or "PNG 412 KB". */
6
+ label?: string;
7
+ /** Original filename if known. */
8
+ filename?: string;
9
+ /** MIME type if known. Required for images. */
10
+ mediaType?: string;
11
+ }
12
+ interface Attachment {
13
+ readonly id: string;
14
+ readonly kind: AttachmentKind;
15
+ readonly meta: AttachmentMeta;
16
+ /** In-memory payload. For images this is base64; for text/file it's the raw text. */
17
+ readonly data?: string;
18
+ /** Disk location if spooled. Mutually exclusive with `data` for large payloads. */
19
+ readonly path?: string;
20
+ readonly bytes: number;
21
+ readonly createdAt: string;
22
+ }
23
+ interface AttachmentRef {
24
+ readonly id: string;
25
+ readonly kind: AttachmentKind;
26
+ /** Index for display, e.g. `#1`. Stable for the lifetime of a session. */
27
+ readonly seq: number;
28
+ readonly meta: AttachmentMeta;
29
+ }
30
+ interface AddAttachmentInput {
31
+ kind: AttachmentKind;
32
+ data: string;
33
+ meta?: AttachmentMeta;
34
+ }
35
+ /**
36
+ * Session-scoped store for content that is too big to inline in display
37
+ * but must be sent to the model as a real ContentBlock. The input layer
38
+ * (CLI/TUI) puts pasted text, dropped files, and pasted images here, gets
39
+ * back a stable AttachmentRef, and shows a placeholder like `[pasted #1]`
40
+ * to the user. At submit time, `expand()` swaps placeholders for the real
41
+ * payload as ContentBlock[].
42
+ */
43
+ interface AttachmentStore {
44
+ add(input: AddAttachmentInput): Promise<AttachmentRef>;
45
+ get(id: string): Promise<Attachment | undefined>;
46
+ list(): AttachmentRef[];
47
+ /**
48
+ * Replace all known placeholder tokens in `text` (e.g. `[pasted #1]`,
49
+ * `[image #2]`) with the corresponding ContentBlock(s) and return the
50
+ * mixed array. Unknown placeholders are left as plain text.
51
+ */
52
+ expand(text: string): Promise<ContentBlock[]>;
53
+ clear(): Promise<void>;
54
+ }
55
+
56
+ /**
57
+ * L2-A: SessionReader — query, replay, search, export over a `SessionStore`.
58
+ *
59
+ * Keeps a clean read-only interface (no `append`, no `delete`) so analytics
60
+ * code can be wired against a store without granting it the writer surface.
61
+ */
62
+ type SessionEventType = SessionEvent['type'];
63
+ interface SessionQuery {
64
+ /** Filter by start timestamp (ISO). Sessions started before this are excluded. */
65
+ since?: string;
66
+ /** Filter by start timestamp (ISO). Sessions started after this are excluded. */
67
+ until?: string;
68
+ /** Substring match against title (case-insensitive). */
69
+ titleContains?: string;
70
+ /** Filter by provider id. */
71
+ provider?: string;
72
+ /** Filter by model id. */
73
+ model?: string;
74
+ /** Minimum total tokens (input+output) to keep. */
75
+ minTokens?: number;
76
+ /** Limit result count. Defaults to no limit. */
77
+ limit?: number;
78
+ }
79
+ interface SessionSearchHit {
80
+ sessionId: string;
81
+ eventIndex: number;
82
+ ts: string;
83
+ type: SessionEventType;
84
+ /** Short snippet of the matched text — null for events without text content. */
85
+ snippet: string | null;
86
+ }
87
+ interface SessionSearchQuery {
88
+ /** Plain text or regex pattern. */
89
+ query: string;
90
+ /** Treat `query` as a regex. Defaults to false (literal substring). */
91
+ regex?: boolean;
92
+ /** Case-insensitive match. Defaults to true. */
93
+ caseInsensitive?: boolean;
94
+ /** Limit only to these event types. Defaults to all event types. */
95
+ types?: SessionEventType[];
96
+ /** Limit hit count. Defaults to 100. */
97
+ limit?: number;
98
+ }
99
+ interface SessionExportOptions {
100
+ /** "markdown" produces a human-readable chat log; "json" passes through raw events. */
101
+ format: 'markdown' | 'json' | 'text';
102
+ /** Include tool_use/tool_result blocks. Defaults to true. */
103
+ includeTools?: boolean;
104
+ /** Include system/diagnostic events (errors, compaction). Defaults to true. */
105
+ includeDiagnostics?: boolean;
106
+ }
107
+ interface SessionSummaryLite {
108
+ id: string;
109
+ title: string;
110
+ startedAt: string;
111
+ endedAt?: string;
112
+ provider: string;
113
+ model: string;
114
+ tokenTotal: number;
115
+ }
116
+ interface SessionReader {
117
+ /** List sessions matching the query. Uses the underlying store's summary cache. */
118
+ query(q?: SessionQuery): Promise<SessionSummaryLite[]>;
119
+ /** Yield events for `sessionId` in chronological order. */
120
+ replay(sessionId: string): AsyncIterable<SessionEvent>;
121
+ /** Full-text/regex search across one or all sessions. */
122
+ search(q: SessionSearchQuery, sessionId?: string): Promise<SessionSearchHit[]>;
123
+ /** Render a session for human or downstream-tool consumption. */
124
+ export(sessionId: string, opts: SessionExportOptions): Promise<string>;
125
+ /** Read the metadata header without loading the full event stream. */
126
+ metadata(sessionId: string): Promise<SessionMetadata>;
127
+ }
128
+ interface DefaultSessionReaderOptions {
129
+ store: SessionStore;
130
+ }
131
+
132
+ /**
133
+ * L2-A: read-only view over a `SessionStore` with query, replay, search,
134
+ * and export helpers. Implemented on top of the public `SessionStore`
135
+ * surface so any concrete store can be inspected without re-implementation.
136
+ *
137
+ * The heavy operations re-parse the JSONL stream on every call — fine for
138
+ * /resume and one-off analytics. Wrap with a memoizing decorator if needed.
139
+ */
140
+ declare class DefaultSessionReader implements SessionReader {
141
+ private readonly store;
142
+ constructor(opts: DefaultSessionReaderOptions);
143
+ query(q?: SessionQuery): Promise<SessionSummaryLite[]>;
144
+ replay(sessionId: string): AsyncIterable<SessionEvent>;
145
+ search(q: SessionSearchQuery, sessionId?: string): Promise<SessionSearchHit[]>;
146
+ export(sessionId: string, opts: SessionExportOptions): Promise<string>;
147
+ metadata(sessionId: string): Promise<SessionMetadata>;
148
+ }
149
+
150
+ export { type AddAttachmentInput as A, DefaultSessionReader as D, type Attachment as a, type AttachmentKind as b, type AttachmentMeta as c, type AttachmentRef as d, type AttachmentStore as e };
@@ -0,0 +1,72 @@
1
+ import { j as Response, a0 as Context, h as ProviderError } from './context-BmM2xGUZ.js';
2
+
3
+ type RecoveryDecision = {
4
+ /**
5
+ * Recovery mutated state or waited for capacity and the agent should
6
+ * rebuild the provider request and try the turn again.
7
+ */
8
+ action: 'retry';
9
+ reason: string;
10
+ model?: string;
11
+ } | {
12
+ /**
13
+ * Recovery produced a substitute provider response that should be
14
+ * processed exactly like a normal model response.
15
+ */
16
+ action: 'continue';
17
+ response: Response;
18
+ reason?: string;
19
+ } | {
20
+ /** Recovery inspected the error and decided the agent must fail. */
21
+ action: 'fail';
22
+ reason: string;
23
+ error?: unknown;
24
+ };
25
+ interface ErrorHandler {
26
+ /**
27
+ * Attempt to recover from an unretried provider/tool boundary error.
28
+ *
29
+ * `null` means "no strategy matched". Non-null decisions are explicit:
30
+ * retry the current turn, continue with a substitute response, or fail
31
+ * deliberately. Callers should not infer control flow from truthiness.
32
+ */
33
+ recover(err: unknown, ctx: Context): Promise<RecoveryDecision | null>;
34
+ classify(err: unknown): {
35
+ kind: 'rate_limit' | 'overloaded' | 'server' | 'client' | 'network' | 'abort' | 'context_overflow' | 'unknown';
36
+ retryable: boolean;
37
+ };
38
+ }
39
+
40
+ interface RetryPolicy {
41
+ shouldRetry(err: ProviderError | Error, attempt: number): boolean;
42
+ delayMs(attempt: number): number;
43
+ maxAttempts(err: ProviderError | Error): number;
44
+ }
45
+
46
+ interface SkillManifest {
47
+ name: string;
48
+ description: string;
49
+ version?: string;
50
+ path: string;
51
+ source: 'project' | 'user' | 'bundled';
52
+ }
53
+ /** Parsed skill entry for structured rendering in system prompt. */
54
+ interface SkillEntry {
55
+ name: string;
56
+ /** "Use when..." trigger condition — one-liner */
57
+ trigger: string;
58
+ /** Comma-separated scope items */
59
+ scope: string[];
60
+ source: SkillManifest['source'];
61
+ path: string;
62
+ }
63
+ interface SkillLoader {
64
+ list(): Promise<SkillManifest[]>;
65
+ /** Structured entries with trigger/scope for system prompt rendering. */
66
+ listEntries(): Promise<SkillEntry[]>;
67
+ find(name: string): Promise<SkillManifest | undefined>;
68
+ manifestText(): Promise<string>;
69
+ readBody(name: string): Promise<string>;
70
+ }
71
+
72
+ export type { ErrorHandler as E, RecoveryDecision as R, SkillEntry as S, SkillLoader as a, SkillManifest as b, RetryPolicy as c };
@@ -0,0 +1,382 @@
1
+ import { E as EventBus } from '../events-BMNaEFZl.js';
2
+ import { o as SessionStore, n as SessionMetadata, q as SessionWriter, k as ResumedSession, S as SessionData, p as SessionSummary, b as ContentBlock, m as SessionEvent } from '../context-BmM2xGUZ.js';
3
+ import { e as AttachmentStore, A as AddAttachmentInput, d as AttachmentRef, a as Attachment } from '../session-reader-Drq8RvJu.js';
4
+ export { D as DefaultSessionReader } from '../session-reader-Drq8RvJu.js';
5
+ import { b as MemoryStore, a as MemoryScope } from '../memory-CEXuo7sz.js';
6
+ import { a as WstackPaths } from '../wstack-paths-BGu2INTm.js';
7
+ import { b as ConfigStore, C as Config, a as ConfigLoader } from '../config-BU9f_5yH.js';
8
+ import { S as SecretVault } from '../secret-vault-DoISxaKO.js';
9
+ import '../models-registry-Y2xbog0E.js';
10
+
11
+ interface SessionStoreOptions {
12
+ dir: string;
13
+ /** Optional EventBus for emitting session diagnostics. */
14
+ events?: EventBus;
15
+ }
16
+ declare class DefaultSessionStore implements SessionStore {
17
+ private readonly dir;
18
+ private readonly events?;
19
+ constructor(opts: SessionStoreOptions);
20
+ create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter>;
21
+ resume(id: string): Promise<ResumedSession>;
22
+ load(id: string): Promise<SessionData>;
23
+ list(limit?: number): Promise<SessionSummary[]>;
24
+ private summaryFor;
25
+ delete(id: string): Promise<void>;
26
+ private summarize;
27
+ private metaFromEvents;
28
+ private replay;
29
+ }
30
+
31
+ /**
32
+ * The persisted form of a single queued user message. The TUI's
33
+ * in-memory QueueItem has a render id; that's pure UI bookkeeping, so
34
+ * we drop it when serializing — fresh ids are assigned on rehydrate.
35
+ */
36
+ interface PersistedQueueItem {
37
+ displayText: string;
38
+ blocks: ContentBlock[];
39
+ }
40
+ /**
41
+ * Side-file storage for a session's pending input queue. Lives at
42
+ * `<sessionDir>/queue.json` next to the attachment spool. Reads are
43
+ * tolerant (missing/malformed file → empty array); writes are atomic
44
+ * (tmp + rename) so a crash mid-write can never leave a partial file
45
+ * the next launch would choke on.
46
+ *
47
+ * The contract is "snapshot replacement": every mutation hands the
48
+ * full queue and we rewrite the file. The queue is small (rarely more
49
+ * than a handful of messages), so this is cheaper than delta logging
50
+ * and avoids the replay complexity.
51
+ */
52
+ declare class QueueStore {
53
+ private readonly file;
54
+ constructor(opts: {
55
+ dir: string;
56
+ });
57
+ write(items: PersistedQueueItem[]): Promise<void>;
58
+ read(): Promise<PersistedQueueItem[]>;
59
+ clear(): Promise<void>;
60
+ }
61
+
62
+ interface AttachmentStoreOptions {
63
+ /**
64
+ * Directory for spooling payloads larger than `spoolThresholdBytes`.
65
+ * When omitted, all payloads stay in memory.
66
+ */
67
+ spoolDir?: string;
68
+ spoolThresholdBytes?: number;
69
+ }
70
+ /**
71
+ * In-memory attachment store with optional disk spool. Placeholder syntax
72
+ * is `[<kind> #<seq>]` where kind is `pasted` / `image` / `file`. Unknown
73
+ * placeholders are passed through as-is so users can write that literal
74
+ * text without losing it.
75
+ */
76
+ declare class DefaultAttachmentStore implements AttachmentStore {
77
+ private readonly items;
78
+ private readonly refs;
79
+ private nextSeq;
80
+ private readonly spoolDir;
81
+ private readonly spoolThreshold;
82
+ constructor(opts?: AttachmentStoreOptions);
83
+ add(input: AddAttachmentInput): Promise<AttachmentRef>;
84
+ get(id: string): Promise<Attachment | undefined>;
85
+ list(): AttachmentRef[];
86
+ expand(text: string): Promise<ContentBlock[]>;
87
+ clear(): Promise<void>;
88
+ private toBlock;
89
+ }
90
+
91
+ interface MemoryStoreOptions {
92
+ paths: WstackPaths;
93
+ }
94
+ /**
95
+ * Three scopes:
96
+ * project-agents → <project>/.wrongstack/AGENTS.md (committed)
97
+ * project-memory → ~/.wrongstack/projects/<hash>/memory.md (per-project agent notes)
98
+ * user-memory → ~/.wrongstack/memory.md (global personal memory)
99
+ */
100
+ declare class DefaultMemoryStore implements MemoryStore {
101
+ private readonly files;
102
+ /**
103
+ * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
104
+ * `clear` are read-modify-write against a single file; without a lock,
105
+ * two concurrent calls on the same scope can read the same baseline and
106
+ * the later write silently drops the earlier entry. We chain each
107
+ * mutation onto the prior promise for the same scope so they run in
108
+ * issue order. Different scopes still proceed in parallel.
109
+ *
110
+ * The chain tracks only the last pending write. If a write fails, its
111
+ * error is caught and swallowed (line 43) so the chain stays alive for
112
+ * subsequent calls. A crash between atomicWrite() and backup copy leaves
113
+ * the file at its new content with no backup — acceptable for an optional
114
+ * backup whose worst case is losing a memory consolidation pass.
115
+ */
116
+ private readonly writeChain;
117
+ constructor(opts: MemoryStoreOptions);
118
+ private runSerialized;
119
+ readAll(): Promise<string>;
120
+ read(scope: MemoryScope): Promise<string>;
121
+ remember(text: string, scope?: MemoryScope): Promise<void>;
122
+ forget(query: string, scope?: MemoryScope): Promise<number>;
123
+ private forgetUnsafe;
124
+ consolidate(scope: MemoryScope): Promise<void>;
125
+ private consolidateUnsafe;
126
+ clear(scope?: MemoryScope): Promise<void>;
127
+ }
128
+
129
+ /**
130
+ * Reference implementation of `ConfigStore`. Stores a single frozen Config
131
+ * and notifies watchers synchronously on every update. Updates use a deep
132
+ * clone so callers can mutate their `partial` argument freely without
133
+ * tainting state.
134
+ *
135
+ * For the CLI: instantiate once at boot, pass the store (not the Config)
136
+ * to subsystems that care about runtime changes (provider switching,
137
+ * extension reload).
138
+ */
139
+ declare class DefaultConfigStore implements ConfigStore {
140
+ private current;
141
+ private watchers;
142
+ constructor(initial: Config);
143
+ get(): Readonly<Config>;
144
+ getSection<K extends keyof Config>(key: K): Readonly<Config[K]>;
145
+ getExtension(pluginName: string): Readonly<Record<string, unknown>>;
146
+ update(partial: Partial<Config>): Readonly<Config>;
147
+ watch(cb: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void;
148
+ }
149
+
150
+ /**
151
+ * A single config source. Higher priority wins in merges.
152
+ * Sources are applied in priority order (lowest first), so a source
153
+ * with priority=10 overrides one with priority=1.
154
+ */
155
+ interface ConfigSource {
156
+ /** Unique name for debugging and error messages. */
157
+ name: string;
158
+ /** Lower numbers merge first, higher numbers override lower. Default: 50. */
159
+ priority?: number;
160
+ /**
161
+ * Read the raw config patch. Return an empty object if unavailable.
162
+ * Errors are surfaced but do not abort loading — the source is skipped.
163
+ */
164
+ read(): Promise<Partial<Config>>;
165
+ }
166
+ interface ConfigLoaderOptions {
167
+ paths: WstackPaths;
168
+ strict?: boolean;
169
+ vault?: SecretVault;
170
+ /** Extra sources merged after the built-in layers. */
171
+ sources?: ConfigSource[];
172
+ }
173
+ declare class DefaultConfigLoader implements ConfigLoader {
174
+ private readonly paths;
175
+ private readonly strict;
176
+ private readonly vault;
177
+ private readonly extraSources;
178
+ constructor(opts: ConfigLoaderOptions);
179
+ load(opts?: {
180
+ cliFlags?: Partial<Config>;
181
+ cwd?: string;
182
+ }): Promise<Config>;
183
+ private readJson;
184
+ private validateBehavior;
185
+ private validateIdentity;
186
+ }
187
+
188
+ /**
189
+ * L2-D: Config version migration framework. Pure functions, decoupled
190
+ * from disk/CLI — caller passes a parsed JSON object and gets back the
191
+ * up-to-date `Config` shape (or a structured error explaining why
192
+ * migration failed).
193
+ *
194
+ * Migrations are registered as `{ from, to, migrate }` triples and run
195
+ * sequentially. Each migration is independently testable. Adding a new
196
+ * version means appending one migration; existing user configs are
197
+ * upgraded in place at load time.
198
+ */
199
+ interface MigrationContext {
200
+ /**
201
+ * Original on-disk version of the input. Migrations may use this to
202
+ * decide between in-place patches and rewrites.
203
+ */
204
+ fromVersion: number;
205
+ /**
206
+ * Set when the migration writes back to disk. Callers persist the
207
+ * migrated config when this is true so the user doesn't see the same
208
+ * migration banner on every boot.
209
+ */
210
+ shouldPersist: boolean;
211
+ }
212
+ interface ConfigMigration {
213
+ /** Version of the input this migration accepts. */
214
+ from: number;
215
+ /** Version of the output it produces. */
216
+ to: number;
217
+ /** Pure transform — no I/O. */
218
+ migrate(input: Record<string, unknown>, ctx: MigrationContext): Record<string, unknown>;
219
+ /** Optional human-readable description for migration logs / banners. */
220
+ describe?: string;
221
+ }
222
+ interface MigrationResult {
223
+ /** Final config (still typed as `unknown`-keyed — caller validates). */
224
+ config: Record<string, unknown>;
225
+ /** Ordered list of `from→to` versions that ran. */
226
+ applied: string[];
227
+ /** True when at least one migration produced changes worth persisting. */
228
+ shouldPersist: boolean;
229
+ }
230
+ declare class ConfigMigrationError extends Error {
231
+ readonly fromVersion: number;
232
+ readonly targetVersion: number;
233
+ readonly missingStep: number | null;
234
+ constructor(opts: {
235
+ message: string;
236
+ fromVersion: number;
237
+ targetVersion: number;
238
+ missingStep: number | null;
239
+ });
240
+ }
241
+ /**
242
+ * Run registered migrations until the input reaches `targetVersion`.
243
+ *
244
+ * Resolution rules:
245
+ * 1. If `input.version === targetVersion`, no migrations run; `shouldPersist`
246
+ * is false.
247
+ * 2. Otherwise walk the migration chain from `input.version` upward,
248
+ * picking the migration whose `from` matches the current version.
249
+ * 3. Stop when `current.version === targetVersion`.
250
+ * 4. If no migration matches at some point, throw `ConfigMigrationError`
251
+ * with the missing step recorded for diagnostics.
252
+ *
253
+ * Migrations may be downward (e.g. for staged rollouts), but `targetVersion`
254
+ * must be reachable strictly via the registered chain — there's no implicit
255
+ * "skip" or transitive resolution.
256
+ */
257
+ declare function runConfigMigrations(input: Record<string, unknown>, targetVersion: number, migrations: readonly ConfigMigration[]): MigrationResult;
258
+ /**
259
+ * Default empty migration registry. Real migrations are appended as new
260
+ * Config versions are introduced. Example (when v2 lands):
261
+ *
262
+ * export const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [
263
+ * {
264
+ * from: 1, to: 2, describe: 'rename `apiKey` → `auth.apiKey`',
265
+ * migrate(cfg) {
266
+ * const apiKey = cfg.apiKey;
267
+ * delete cfg.apiKey;
268
+ * return { ...cfg, auth: { ...(cfg.auth ?? {}), apiKey } };
269
+ * },
270
+ * },
271
+ * ];
272
+ */
273
+ declare const DEFAULT_CONFIG_MIGRATIONS: readonly ConfigMigration[];
274
+
275
+ /**
276
+ * Per-project lockfile used for crash detection. The CLI writes one of
277
+ * these alongside the session JSONLs (`<projectSessions>/active.json`)
278
+ * when an interactive run starts, and deletes it on clean exit. If we
279
+ * find one on the next launch whose owning PID is dead (or whose host
280
+ * doesn't match), we know the previous run was killed mid-flight and
281
+ * the session it was writing to is a recovery candidate.
282
+ *
283
+ * The lockfile is intentionally per-project (already isolated by
284
+ * `wpaths.projectSessions`), so two TUIs in two different repos do not
285
+ * fight each other.
286
+ */
287
+ interface RecoveryLockOptions {
288
+ /** Directory the lockfile lives in. Usually `wpaths.projectSessions`. */
289
+ dir: string;
290
+ /** This process's PID. Default: `process.pid`. */
291
+ pid?: number;
292
+ /** Hostname recorded for the lock. Default: `os.hostname()`. */
293
+ hostname?: string;
294
+ /** Locks older than this are considered orphaned (disk wiped, etc.). Default 24h. */
295
+ maxAgeMs?: number;
296
+ /** Used to check whether the abandoned session was actually closed cleanly. */
297
+ sessionStore?: SessionStore;
298
+ /**
299
+ * Override the PID-liveness probe. Default: `process.kill(pid, 0)` —
300
+ * succeeds (or throws EPERM) when the PID is alive, throws ESRCH when
301
+ * it is gone. Tests inject a deterministic stub.
302
+ */
303
+ isPidAlive?: (pid: number) => boolean;
304
+ }
305
+ interface AbandonedSession {
306
+ sessionId: string;
307
+ pid: number;
308
+ startedAt: string;
309
+ /** Lockfile age in ms at the time of the check. */
310
+ ageMs: number;
311
+ /** Number of messages already on disk for this session. */
312
+ messageCount: number;
313
+ }
314
+ declare class RecoveryLock {
315
+ private readonly file;
316
+ private readonly pid;
317
+ private readonly hostname;
318
+ private readonly maxAgeMs;
319
+ private readonly sessionStore?;
320
+ private readonly probe;
321
+ constructor(opts: RecoveryLockOptions);
322
+ /**
323
+ * Examine the lockfile and decide whether it represents an abandoned
324
+ * session. Returns `null` if the file is missing, points to a live
325
+ * instance, references a clean-closed session, is too old, or is
326
+ * malformed. Otherwise returns enough detail to prompt the user.
327
+ *
328
+ * Important: this is a read-only check. We never delete an active
329
+ * lock from here — if another wstack instance is alive, the caller
330
+ * should bail or run with a fresh session instead.
331
+ */
332
+ checkAbandoned(): Promise<AbandonedSession | null>;
333
+ /**
334
+ * Claim the lock for the given session. Overwrites any existing lock
335
+ * — the caller should have already handled abandonment (via
336
+ * `checkAbandoned`) before calling this.
337
+ */
338
+ write(sessionId: string): Promise<void>;
339
+ /**
340
+ * Release the lock. Idempotent — silently succeeds if the file is
341
+ * already gone (e.g. someone else cleared it, or the directory was
342
+ * wiped).
343
+ */
344
+ clear(): Promise<void>;
345
+ private readLock;
346
+ }
347
+
348
+ interface QueryFilter {
349
+ eventTypes?: string[];
350
+ toolNames?: string[];
351
+ timeRange?: {
352
+ start: string;
353
+ end: string;
354
+ };
355
+ }
356
+ interface ModeChange {
357
+ ts: string;
358
+ from: string;
359
+ to: string;
360
+ }
361
+ interface TaskSummary {
362
+ taskId: string;
363
+ title: string;
364
+ status: string;
365
+ createdAt: string;
366
+ completedAt?: string;
367
+ }
368
+ interface SessionAnalysis {
369
+ sessionId: string;
370
+ totalDuration: number;
371
+ toolUsageCount: Record<string, number>;
372
+ errorCount: number;
373
+ modeChanges: ModeChange[];
374
+ tasks: TaskSummary[];
375
+ }
376
+ declare class SessionAnalyzer {
377
+ analyze(events: SessionEvent[]): SessionAnalysis;
378
+ query(events: SessionEvent[], filter: QueryFilter): SessionEvent[];
379
+ private calcDuration;
380
+ }
381
+
382
+ export { type AbandonedSession, type AttachmentStoreOptions, type ConfigLoaderOptions, type ConfigMigration, ConfigMigrationError, type ConfigSource, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultSessionStore, type MemoryStoreOptions, type MigrationContext, type MigrationResult, type PersistedQueueItem, QueueStore, RecoveryLock, type RecoveryLockOptions, SessionAnalyzer, type SessionStoreOptions, runConfigMigrations };