@wrongstack/core 0.1.8 → 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.
- package/dist/agent-bridge-6KPqsFx6.d.ts +33 -0
- package/dist/compactor-B4mQZXf2.d.ts +17 -0
- package/dist/config-BU9f_5yH.d.ts +193 -0
- package/dist/{provider-txgB0Oq9.d.ts → context-BmM2xGUZ.d.ts} +532 -472
- package/dist/coordination/index.d.ts +694 -0
- package/dist/coordination/index.js +1995 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/defaults/index.d.ts +34 -2206
- package/dist/defaults/index.js +4116 -3790
- package/dist/defaults/index.js.map +1 -1
- package/dist/events-BMNaEFZl.d.ts +218 -0
- package/dist/execution/index.d.ts +260 -0
- package/dist/execution/index.js +1625 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index.d.ts +50 -12
- package/dist/index.js +6669 -5909
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +10 -0
- package/dist/infrastructure/index.js +575 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/input-reader-E-ffP2ee.d.ts +12 -0
- package/dist/kernel/index.d.ts +15 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-BH6AE0W9.d.ts +24 -0
- package/dist/logger-BMQgxvdy.d.ts +12 -0
- package/dist/mcp-servers-Dzgg4x1w.d.ts +100 -0
- package/dist/memory-CEXuo7sz.d.ts +16 -0
- package/dist/mode-CV077NjV.d.ts +27 -0
- package/dist/models/index.d.ts +60 -0
- package/dist/models/index.js +621 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models-registry-DqzwpBQy.d.ts +46 -0
- package/dist/models-registry-Y2xbog0E.d.ts +95 -0
- package/dist/multi-agent-fmkRHtof.d.ts +283 -0
- package/dist/observability/index.d.ts +353 -0
- package/dist/observability/index.js +691 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability-BhnVLBLS.d.ts +67 -0
- package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
- package/dist/path-resolver-DBjaoXFq.d.ts +54 -0
- package/dist/plugin-DJk6LL8B.d.ts +434 -0
- package/dist/renderer-rk_1Swwc.d.ts +158 -0
- package/dist/sdd/index.d.ts +206 -0
- package/dist/sdd/index.js +864 -0
- package/dist/sdd/index.js.map +1 -0
- package/dist/secret-scrubber-CicHLN4G.d.ts +31 -0
- package/dist/secret-scrubber-DF88luOe.d.ts +54 -0
- package/dist/secret-vault-DoISxaKO.d.ts +19 -0
- package/dist/security/index.d.ts +30 -0
- package/dist/security/index.js +524 -0
- package/dist/security/index.js.map +1 -0
- package/dist/selector-BbJqiEP4.d.ts +51 -0
- package/dist/session-reader-Drq8RvJu.d.ts +150 -0
- package/dist/skill-DhfSizKv.d.ts +72 -0
- package/dist/storage/index.d.ts +382 -0
- package/dist/storage/index.js +1530 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-BC_8ypCG.d.ts} +1 -1
- package/dist/task-graph-BITvWt4t.d.ts +160 -0
- package/dist/tool-executor-CpuJPYm9.d.ts +97 -0
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.js +1787 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +49 -2
- package/dist/utils/index.js +100 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +34 -2
- package/skills/audit-log/SKILL.md +67 -0
- package/skills/bug-hunter/SKILL.md +87 -0
- package/skills/refactor-planner/SKILL.md +94 -0
- package/skills/security-scanner/SKILL.md +117 -0
- package/dist/mode-Pjt5vMS6.d.ts +0 -815
- 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 };
|