ctxfile 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +164 -0
- package/behaviors/canonical.md +54 -0
- package/behaviors/render/agents-md/SECTION.md +58 -0
- package/behaviors/render/claude-code/SKILL.md +59 -0
- package/behaviors/render/codex/instructions.md +58 -0
- package/behaviors/render/cursor/ctxfile.mdc +59 -0
- package/behaviors/render/generic/system-prompt.md +58 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4181 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +3142 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +942 -0
- package/dist/index.d.ts +942 -0
- package/dist/index.js +3028 -0
- package/dist/index.js.map +1 -0
- package/manifest.json +36 -0
- package/package.json +73 -0
- package/ui-dist/assets/archivo-var-latin-BEIDiHaE.woff2 +0 -0
- package/ui-dist/assets/doto-var-latin-Y-BORX9o.woff2 +0 -0
- package/ui-dist/assets/index-CAydkgHR.css +1 -0
- package/ui-dist/assets/index-Do7dvee8.js +19 -0
- package/ui-dist/assets/plex-mono-400-latin-BJoXLJYV.woff2 +0 -0
- package/ui-dist/assets/plex-mono-500-latin-C820gu2e.woff2 +0 -0
- package/ui-dist/assets/plex-mono-600-latin-DpGnXj3s.woff2 +0 -0
- package/ui-dist/index.html +18 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
interface ConnectorStatus {
|
|
5
|
+
name: string;
|
|
6
|
+
status: "ok" | "skipped" | "error";
|
|
7
|
+
error?: string;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
}
|
|
10
|
+
interface KeyFile {
|
|
11
|
+
path: string;
|
|
12
|
+
tokens: number;
|
|
13
|
+
truncated: boolean;
|
|
14
|
+
redactions: number;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
interface GitCommit {
|
|
18
|
+
hash: string;
|
|
19
|
+
date: string;
|
|
20
|
+
message: string;
|
|
21
|
+
author: string;
|
|
22
|
+
}
|
|
23
|
+
interface GitState {
|
|
24
|
+
branch: string;
|
|
25
|
+
staged: string[];
|
|
26
|
+
modified: string[];
|
|
27
|
+
untracked: string[];
|
|
28
|
+
ahead: number;
|
|
29
|
+
behind: number;
|
|
30
|
+
commits: GitCommit[];
|
|
31
|
+
diffSummary: string;
|
|
32
|
+
}
|
|
33
|
+
interface NotionPage {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
lastEditedTime: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}
|
|
39
|
+
interface SessionDigest {
|
|
40
|
+
/** Originating tool, e.g. "claude-code", "cursor", "codex", "opencode",
|
|
41
|
+
"gemini-cli", "aider", "openclaw". Open set: new session connectors
|
|
42
|
+
add sources without a core type change. */
|
|
43
|
+
source: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
startedAt: string | null;
|
|
46
|
+
lastActiveAt: string | null;
|
|
47
|
+
turnCount: number;
|
|
48
|
+
/** Redacted, token-capped rolling summary of the conversation's text turns. */
|
|
49
|
+
digest: string;
|
|
50
|
+
}
|
|
51
|
+
interface ContextMeta {
|
|
52
|
+
name: "ctxfile";
|
|
53
|
+
version: string;
|
|
54
|
+
generatedAt: string;
|
|
55
|
+
root: string;
|
|
56
|
+
tokenBudget: number;
|
|
57
|
+
tokensUsed: number;
|
|
58
|
+
connectors: ConnectorStatus[];
|
|
59
|
+
}
|
|
60
|
+
interface ContextObject {
|
|
61
|
+
meta: ContextMeta;
|
|
62
|
+
plan: string | null;
|
|
63
|
+
keyFiles: KeyFile[];
|
|
64
|
+
gitState: GitState | null;
|
|
65
|
+
notionPages: NotionPage[];
|
|
66
|
+
/** Recent agent-session digests (populated by Pro session connectors). */
|
|
67
|
+
sessions?: SessionDigest[];
|
|
68
|
+
sessionSummary: string | null;
|
|
69
|
+
}
|
|
70
|
+
type ContextScope = "full" | "plan" | "files" | "git";
|
|
71
|
+
/** Progress events emitted while a snapshot builds (consumed by the local UI's SSE stream). */
|
|
72
|
+
type BuildEvent = {
|
|
73
|
+
type: "connector:start";
|
|
74
|
+
name: string;
|
|
75
|
+
} | {
|
|
76
|
+
type: "connector:done";
|
|
77
|
+
connector: ConnectorStatus;
|
|
78
|
+
} | {
|
|
79
|
+
type: "tokens";
|
|
80
|
+
tokensUsed: number;
|
|
81
|
+
tokenBudget: number;
|
|
82
|
+
} | {
|
|
83
|
+
type: "done";
|
|
84
|
+
generatedAt: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* `ctxfile export`: the ContextObject as a static, self-describing artifact
|
|
89
|
+
* (".ctxfile convention") that cloud agents read from the repo or a CI
|
|
90
|
+
* artifact. The envelope keys are snake_case and frozen by the public schema;
|
|
91
|
+
* the embedded context keeps the camelCase shape `get_context` already serves.
|
|
92
|
+
*/
|
|
93
|
+
declare const EXPORT_SCHEMA_VERSION = "1";
|
|
94
|
+
declare const EXPORT_PROFILES: readonly ["repo-safe", "full", "custom"];
|
|
95
|
+
type ExportProfile = (typeof EXPORT_PROFILES)[number];
|
|
96
|
+
declare const EXPORT_SECTIONS: readonly ["plan", "gitState", "keyFiles", "keyFileContent", "notionPages", "sessions", "sessionSummary"];
|
|
97
|
+
type ExportSection = (typeof EXPORT_SECTIONS)[number];
|
|
98
|
+
interface ExportedKeyFile {
|
|
99
|
+
path: string;
|
|
100
|
+
tokens: number;
|
|
101
|
+
truncated: boolean;
|
|
102
|
+
redactions: number;
|
|
103
|
+
/** Present only when the "keyFileContent" section is included. */
|
|
104
|
+
content?: string;
|
|
105
|
+
}
|
|
106
|
+
interface ExportedContext {
|
|
107
|
+
meta: ContextMeta;
|
|
108
|
+
plan: string | null;
|
|
109
|
+
keyFiles: ExportedKeyFile[];
|
|
110
|
+
gitState: GitState | null;
|
|
111
|
+
notionPages: NotionPage[];
|
|
112
|
+
sessions?: SessionDigest[];
|
|
113
|
+
sessionSummary: string | null;
|
|
114
|
+
}
|
|
115
|
+
interface ExportEnvelope {
|
|
116
|
+
ctxfile_schema: typeof EXPORT_SCHEMA_VERSION;
|
|
117
|
+
ctxfile_version: string;
|
|
118
|
+
profile: ExportProfile;
|
|
119
|
+
/** When this artifact was written (drift detection, together with git_sha). */
|
|
120
|
+
generated_at: string;
|
|
121
|
+
/** When the underlying snapshot was built. */
|
|
122
|
+
snapshot_generated_at: string;
|
|
123
|
+
git_sha: string | null;
|
|
124
|
+
/** Basename only; the absolute local path never leaves the machine. */
|
|
125
|
+
root_name: string;
|
|
126
|
+
/** Sections actually present, so a found file explains itself. */
|
|
127
|
+
sections: ExportSection[];
|
|
128
|
+
context: ExportedContext;
|
|
129
|
+
}
|
|
130
|
+
interface BuildExportOptions {
|
|
131
|
+
profile: ExportProfile;
|
|
132
|
+
/** Section allowlist for the "custom" profile (falls back to repo-safe). */
|
|
133
|
+
customSections?: ExportSection[] | null;
|
|
134
|
+
/** Clock override for tests. */
|
|
135
|
+
now?: () => Date;
|
|
136
|
+
}
|
|
137
|
+
declare function buildExportEnvelope(ctx: ContextObject, options: BuildExportOptions): ExportEnvelope;
|
|
138
|
+
/** Human/agent-readable render of the same artifact (context.md). */
|
|
139
|
+
declare function renderExportMarkdown(envelope: ExportEnvelope): string;
|
|
140
|
+
|
|
141
|
+
/** Scopes for the HTTP door: what a bearer token may do on a connection. */
|
|
142
|
+
declare const SERVE_SCOPES: readonly ["read:context", "write:sessions"];
|
|
143
|
+
type ServeScope = (typeof SERVE_SCOPES)[number];
|
|
144
|
+
interface ConsultProviderSpec {
|
|
145
|
+
type: "anthropic" | "openai-compatible" | "ollama";
|
|
146
|
+
model?: string;
|
|
147
|
+
baseUrl?: string;
|
|
148
|
+
/** Name of the env var holding the API key — never the key itself. */
|
|
149
|
+
apiKeyEnv?: string;
|
|
150
|
+
}
|
|
151
|
+
interface ServeTokenSpec {
|
|
152
|
+
name: string;
|
|
153
|
+
/** Name of the env var holding the bearer token — never the token itself. */
|
|
154
|
+
tokenEnv: string;
|
|
155
|
+
scopes: ServeScope[];
|
|
156
|
+
}
|
|
157
|
+
interface ResolvedConfig {
|
|
158
|
+
root: string;
|
|
159
|
+
tokenBudget: number;
|
|
160
|
+
maxFileTokens: number;
|
|
161
|
+
cacheDir: string;
|
|
162
|
+
cacheMaxAgeMs: number;
|
|
163
|
+
include: string[];
|
|
164
|
+
exclude: string[];
|
|
165
|
+
notion: {
|
|
166
|
+
token: string | null;
|
|
167
|
+
pageIds: string[];
|
|
168
|
+
};
|
|
169
|
+
ollama: {
|
|
170
|
+
baseUrl: string;
|
|
171
|
+
model: string | null;
|
|
172
|
+
summarize: boolean;
|
|
173
|
+
};
|
|
174
|
+
voice: {
|
|
175
|
+
whisperPath: string | null;
|
|
176
|
+
modelPath: string | null;
|
|
177
|
+
audioDir: string | null;
|
|
178
|
+
};
|
|
179
|
+
consult: {
|
|
180
|
+
providers: ConsultProviderSpec[];
|
|
181
|
+
};
|
|
182
|
+
telemetry: {
|
|
183
|
+
enabled: boolean;
|
|
184
|
+
endpoint: string;
|
|
185
|
+
};
|
|
186
|
+
export: {
|
|
187
|
+
profile: ExportProfile;
|
|
188
|
+
include: ExportSection[] | null;
|
|
189
|
+
};
|
|
190
|
+
behavior: {
|
|
191
|
+
debounceMinutes: number;
|
|
192
|
+
};
|
|
193
|
+
/** The HTTP door (`ctxfile serve`): loopback default, bearer tokens by env var. */
|
|
194
|
+
serve: {
|
|
195
|
+
port: number;
|
|
196
|
+
host: string;
|
|
197
|
+
tokens: ServeTokenSpec[];
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
interface LoadConfigOptions {
|
|
201
|
+
root?: string;
|
|
202
|
+
configPath?: string;
|
|
203
|
+
env?: NodeJS.ProcessEnv;
|
|
204
|
+
}
|
|
205
|
+
declare function loadConfig(opts?: LoadConfigOptions): ResolvedConfig;
|
|
206
|
+
|
|
207
|
+
declare function estimateTokens(text: string): number;
|
|
208
|
+
declare function truncateToTokens(text: string, maxTokens: number): {
|
|
209
|
+
text: string;
|
|
210
|
+
truncated: boolean;
|
|
211
|
+
};
|
|
212
|
+
declare class TokenBudget {
|
|
213
|
+
private readonly total;
|
|
214
|
+
private consumed;
|
|
215
|
+
constructor(total: number);
|
|
216
|
+
used(): number;
|
|
217
|
+
remaining(): number;
|
|
218
|
+
take(tokens: number): boolean;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface SnapshotInput {
|
|
222
|
+
config: ResolvedConfig;
|
|
223
|
+
budget: TokenBudget;
|
|
224
|
+
}
|
|
225
|
+
interface Connector {
|
|
226
|
+
name: string;
|
|
227
|
+
isEnabled(config: ResolvedConfig): boolean;
|
|
228
|
+
snapshot(input: SnapshotInput): Promise<Partial<ContextObject>>;
|
|
229
|
+
}
|
|
230
|
+
interface Summarizer {
|
|
231
|
+
name: string;
|
|
232
|
+
isEnabled(config: ResolvedConfig): boolean;
|
|
233
|
+
summarize(ctx: ContextObject, config: ResolvedConfig): Promise<string | null>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
declare const fileConnector: Connector;
|
|
237
|
+
|
|
238
|
+
declare const gitConnector: Connector;
|
|
239
|
+
|
|
240
|
+
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
241
|
+
interface NotionConnectorOptions {
|
|
242
|
+
fetchImpl?: FetchLike;
|
|
243
|
+
/** Minimum ms between requests. Notion allows ~3 req/s per connection. */
|
|
244
|
+
minIntervalMs?: number;
|
|
245
|
+
}
|
|
246
|
+
declare function createNotionConnector(options?: NotionConnectorOptions): Connector;
|
|
247
|
+
|
|
248
|
+
interface OllamaSummarizerOptions {
|
|
249
|
+
fetchImpl?: FetchLike;
|
|
250
|
+
healthTimeoutMs?: number;
|
|
251
|
+
}
|
|
252
|
+
declare function createOllamaSummarizer(options?: OllamaSummarizerOptions): Summarizer;
|
|
253
|
+
|
|
254
|
+
declare function buildContext(config: ResolvedConfig, connectors: Connector[], summarizer: Summarizer | null, scope?: ContextScope, onEvent?: (event: BuildEvent) => void): Promise<ContextObject>;
|
|
255
|
+
declare function filterScope(ctx: ContextObject, scope: ContextScope): ContextObject;
|
|
256
|
+
|
|
257
|
+
declare class SnapshotCache {
|
|
258
|
+
private readonly db;
|
|
259
|
+
constructor(dbPath: string);
|
|
260
|
+
save(root: string, ctx: ContextObject, configFingerprint?: string, agentId?: string): void;
|
|
261
|
+
latest(root: string, maxAgeMs: number, configFingerprint?: string, agentId?: string): ContextObject | null;
|
|
262
|
+
/** Newest-first snapshot summaries for the UI freshness timeline. Corrupt rows are skipped. */
|
|
263
|
+
recent(root: string, limit?: number, agentId?: string): {
|
|
264
|
+
createdAt: number;
|
|
265
|
+
tokensUsed: number;
|
|
266
|
+
}[];
|
|
267
|
+
close(): void;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Agent-assisted session ingest: the inverse of the session parsers. Instead
|
|
272
|
+
* of ctxfile reverse-engineering a harness's storage, the client surface's
|
|
273
|
+
* own agent formats its session per this published schema and calls
|
|
274
|
+
* `ingest_context` (bulk/agent door) or `save_session` (conversational door).
|
|
275
|
+
* The prompt is the adapter. Parsers stay the invisible default (Pro); ingest
|
|
276
|
+
* is the universal floor for every harness, including ones not written yet.
|
|
277
|
+
*
|
|
278
|
+
* Schema v2 adds threads (durable identities that outlive any one provider's
|
|
279
|
+
* chat history), session lineage via `continues_from`, and the handoff
|
|
280
|
+
* package: when `handoff: true`, validation enforces everything a cold
|
|
281
|
+
* takeover needs, so any agent on any harness produces the same artifact.
|
|
282
|
+
*
|
|
283
|
+
* This is a write path fed by LLM output, so provenance is non-negotiable:
|
|
284
|
+
* every record is stamped `reported_by: "agent"` plus the door it came
|
|
285
|
+
* through, labeled as agent-reported wherever it surfaces, reviewable and
|
|
286
|
+
* deletable via `ctxfile ingest`.
|
|
287
|
+
*/
|
|
288
|
+
declare const INGEST_SCHEMA_VERSION = "2";
|
|
289
|
+
declare const ingestSourceSchema: z.ZodObject<{
|
|
290
|
+
harness: z.ZodString;
|
|
291
|
+
harness_version: z.ZodOptional<z.ZodString>;
|
|
292
|
+
}, z.core.$strict>;
|
|
293
|
+
declare const ingestSessionSchema: z.ZodObject<{
|
|
294
|
+
session_id: z.ZodOptional<z.ZodString>;
|
|
295
|
+
started_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
296
|
+
ended_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
297
|
+
summary: z.ZodString;
|
|
298
|
+
key_decisions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
299
|
+
files_touched: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
300
|
+
open_items: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
301
|
+
thread: z.ZodOptional<z.ZodString>;
|
|
302
|
+
continues_from: z.ZodOptional<z.ZodString>;
|
|
303
|
+
handoff: z.ZodOptional<z.ZodBoolean>;
|
|
304
|
+
state: z.ZodOptional<z.ZodString>;
|
|
305
|
+
gotchas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
306
|
+
artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
307
|
+
ref: z.ZodString;
|
|
308
|
+
role: z.ZodString;
|
|
309
|
+
}, z.core.$strict>>>;
|
|
310
|
+
suggested_first_prompt: z.ZodOptional<z.ZodString>;
|
|
311
|
+
trigger: z.ZodOptional<z.ZodEnum<{
|
|
312
|
+
auto: "auto";
|
|
313
|
+
manual: "manual";
|
|
314
|
+
}>>;
|
|
315
|
+
}, z.core.$strict>;
|
|
316
|
+
declare const ingestInputSchema: z.ZodObject<{
|
|
317
|
+
ctxfile_ingest_schema: z.ZodEnum<{
|
|
318
|
+
1: "1";
|
|
319
|
+
2: "2";
|
|
320
|
+
}>;
|
|
321
|
+
source: z.ZodObject<{
|
|
322
|
+
harness: z.ZodString;
|
|
323
|
+
harness_version: z.ZodOptional<z.ZodString>;
|
|
324
|
+
}, z.core.$strict>;
|
|
325
|
+
session: z.ZodObject<{
|
|
326
|
+
session_id: z.ZodOptional<z.ZodString>;
|
|
327
|
+
started_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
328
|
+
ended_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
329
|
+
summary: z.ZodString;
|
|
330
|
+
key_decisions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
331
|
+
files_touched: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
332
|
+
open_items: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
333
|
+
thread: z.ZodOptional<z.ZodString>;
|
|
334
|
+
continues_from: z.ZodOptional<z.ZodString>;
|
|
335
|
+
handoff: z.ZodOptional<z.ZodBoolean>;
|
|
336
|
+
state: z.ZodOptional<z.ZodString>;
|
|
337
|
+
gotchas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
338
|
+
artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
339
|
+
ref: z.ZodString;
|
|
340
|
+
role: z.ZodString;
|
|
341
|
+
}, z.core.$strict>>>;
|
|
342
|
+
suggested_first_prompt: z.ZodOptional<z.ZodString>;
|
|
343
|
+
trigger: z.ZodOptional<z.ZodEnum<{
|
|
344
|
+
auto: "auto";
|
|
345
|
+
manual: "manual";
|
|
346
|
+
}>>;
|
|
347
|
+
}, z.core.$strict>;
|
|
348
|
+
}, z.core.$strict>;
|
|
349
|
+
type IngestInput = z.infer<typeof ingestInputSchema>;
|
|
350
|
+
/** save_session takes the session fields directly (no envelope); the harness
|
|
351
|
+
is inferred from the connected client unless declared explicitly. */
|
|
352
|
+
declare const saveSessionSchema: z.ZodObject<{
|
|
353
|
+
session_id: z.ZodOptional<z.ZodString>;
|
|
354
|
+
started_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
355
|
+
ended_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
356
|
+
summary: z.ZodString;
|
|
357
|
+
key_decisions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
358
|
+
files_touched: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
359
|
+
open_items: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
360
|
+
thread: z.ZodOptional<z.ZodString>;
|
|
361
|
+
continues_from: z.ZodOptional<z.ZodString>;
|
|
362
|
+
handoff: z.ZodOptional<z.ZodBoolean>;
|
|
363
|
+
state: z.ZodOptional<z.ZodString>;
|
|
364
|
+
gotchas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
365
|
+
artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
366
|
+
ref: z.ZodString;
|
|
367
|
+
role: z.ZodString;
|
|
368
|
+
}, z.core.$strict>>>;
|
|
369
|
+
suggested_first_prompt: z.ZodOptional<z.ZodString>;
|
|
370
|
+
trigger: z.ZodOptional<z.ZodEnum<{
|
|
371
|
+
auto: "auto";
|
|
372
|
+
manual: "manual";
|
|
373
|
+
}>>;
|
|
374
|
+
harness: z.ZodOptional<z.ZodString>;
|
|
375
|
+
}, z.core.$strict>;
|
|
376
|
+
type SaveSessionInput = z.infer<typeof saveSessionSchema>;
|
|
377
|
+
/** Which tool a record arrived through; part of its provenance. */
|
|
378
|
+
type IngestDoor = "ingest_context" | "save_session";
|
|
379
|
+
interface IngestArtifact {
|
|
380
|
+
ref: string;
|
|
381
|
+
role: string;
|
|
382
|
+
}
|
|
383
|
+
/** One stored, provenance-stamped ingest record. */
|
|
384
|
+
interface IngestedSession {
|
|
385
|
+
id: number;
|
|
386
|
+
root: string;
|
|
387
|
+
harness: string;
|
|
388
|
+
harnessVersion: string | null;
|
|
389
|
+
sessionId: string;
|
|
390
|
+
reportedBy: "agent";
|
|
391
|
+
door: IngestDoor;
|
|
392
|
+
trigger: "auto" | "manual";
|
|
393
|
+
startedAt: string | null;
|
|
394
|
+
endedAt: string | null;
|
|
395
|
+
summary: string;
|
|
396
|
+
keyDecisions: string[];
|
|
397
|
+
filesTouched: string[];
|
|
398
|
+
openItems: string[];
|
|
399
|
+
threadId: number | null;
|
|
400
|
+
threadTitle: string | null;
|
|
401
|
+
continuesFrom: string | null;
|
|
402
|
+
handoff: boolean;
|
|
403
|
+
state: string | null;
|
|
404
|
+
gotchas: string[];
|
|
405
|
+
artifacts: IngestArtifact[];
|
|
406
|
+
suggestedFirstPrompt: string | null;
|
|
407
|
+
ingestedAt: string;
|
|
408
|
+
updatedAt: string;
|
|
409
|
+
revision: number;
|
|
410
|
+
}
|
|
411
|
+
/** Turns a validation failure into the actionable message agents self-correct from. */
|
|
412
|
+
declare function formatIngestErrors(error: z.ZodError, toolName?: string): string;
|
|
413
|
+
/** Stable identity when the harness has no native session id. */
|
|
414
|
+
declare function ingestSessionId(input: IngestInput): string;
|
|
415
|
+
/** Renders an ingest record as a SessionDigest so it rides the normal
|
|
416
|
+
sessions surface, loudly labeled as agent-reported. */
|
|
417
|
+
declare function ingestToSessionDigest(record: IngestedSession, maxTokens?: number): SessionDigest;
|
|
418
|
+
/** Parser wins: an ingest record whose sessionId matches a parser-provided
|
|
419
|
+
session is linked (dropped here), never duplicated. */
|
|
420
|
+
declare function mergeIngestedSessions(parserSessions: SessionDigest[] | undefined, ingested: IngestedSession[]): SessionDigest[] | undefined;
|
|
421
|
+
interface ThreadSummary {
|
|
422
|
+
id: number;
|
|
423
|
+
title: string;
|
|
424
|
+
status: string;
|
|
425
|
+
tags: string[];
|
|
426
|
+
/** Private threads are excluded from behavior-layer auto-capture (§4.1). */
|
|
427
|
+
private: boolean;
|
|
428
|
+
createdAt: string;
|
|
429
|
+
lastActiveAt: string;
|
|
430
|
+
sessionCount: number;
|
|
431
|
+
lastHarness: string | null;
|
|
432
|
+
}
|
|
433
|
+
type ThreadResolution = {
|
|
434
|
+
kind: "resolved";
|
|
435
|
+
thread: ThreadSummary;
|
|
436
|
+
assumed: boolean;
|
|
437
|
+
} | {
|
|
438
|
+
kind: "ambiguous";
|
|
439
|
+
candidates: ThreadSummary[];
|
|
440
|
+
} | {
|
|
441
|
+
kind: "none";
|
|
442
|
+
};
|
|
443
|
+
/** Deterministic fuzzy score in [0, 1]: exact 1, containment 0.85, tag hit
|
|
444
|
+
0.8, else token overlap scaled into (0.3, 0.8]. */
|
|
445
|
+
declare function scoreThreadMatch(query: string, thread: Pick<ThreadSummary, "title" | "tags">): number;
|
|
446
|
+
/** The "you know what I mean" rules: named thread fuzzy-matches; no name
|
|
447
|
+
defaults to the most recently active (and the caller says so); a genuine
|
|
448
|
+
tie returns the shortlist to ask with. */
|
|
449
|
+
declare function resolveThread(query: string | undefined, threads: ThreadSummary[]): ThreadResolution;
|
|
450
|
+
/** Renders continue_thread's merged history: chronological order, per-entry
|
|
451
|
+
provenance labels, newest-detailed/oldest-summarized token budgeting.
|
|
452
|
+
Shared by the local server and the relay's Standard-mode vault serving. */
|
|
453
|
+
declare function renderThreadResume(thread: ThreadSummary, sessions: IngestedSession[], assumed: boolean): string;
|
|
454
|
+
/** Maps an MCP client's advertised name to a harness id, so save_session
|
|
455
|
+
callers never have to know our enum. Unknown clients become custom:<name>. */
|
|
456
|
+
declare function inferHarnessFromClientName(clientName: string | undefined): string;
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The Sync mailbox protocol, client side (M2). Push/pull of encrypted blobs
|
|
460
|
+
* with last-write-wins per blob and tombstones; no CRDTs, because a vault is
|
|
461
|
+
* single-user. The relay implements RelayStore (M3: Fly.io/R2; tests: an
|
|
462
|
+
* in-memory stub) and only ever sees opaque ids, version numbers, and
|
|
463
|
+
* ciphertext. Both sides converge because versions are the source records'
|
|
464
|
+
* own updated-at clocks and apply() is idempotent.
|
|
465
|
+
*/
|
|
466
|
+
/** What the relay sees per blob: opaque id, version, deletion flag. */
|
|
467
|
+
interface SyncBlobMeta {
|
|
468
|
+
id: string;
|
|
469
|
+
version: number;
|
|
470
|
+
deleted: boolean;
|
|
471
|
+
}
|
|
472
|
+
/** The relay contract. M3 implements this over HTTP + R2; the same shape
|
|
473
|
+
self-hosts as the Team hub's storage. */
|
|
474
|
+
interface RelayStore {
|
|
475
|
+
list(): Promise<SyncBlobMeta[]>;
|
|
476
|
+
get(id: string): Promise<{
|
|
477
|
+
meta: SyncBlobMeta;
|
|
478
|
+
data: Uint8Array;
|
|
479
|
+
} | null>;
|
|
480
|
+
put(id: string, meta: SyncBlobMeta, data: Uint8Array): Promise<void>;
|
|
481
|
+
}
|
|
482
|
+
/** One syncable record, plaintext side. The payload carries its own natural
|
|
483
|
+
identity, so tombstones and cross-device applies never depend on ids the
|
|
484
|
+
relay can read. */
|
|
485
|
+
interface SyncEntry {
|
|
486
|
+
naturalId: string;
|
|
487
|
+
/** LWW clock: the record's own updated-at, in ms. */
|
|
488
|
+
version: number;
|
|
489
|
+
deleted: boolean;
|
|
490
|
+
payload: Uint8Array;
|
|
491
|
+
}
|
|
492
|
+
interface LocalBlobSource {
|
|
493
|
+
snapshot(): Promise<SyncEntry[]>;
|
|
494
|
+
/** Applies remote entries with its own LWW check; returns how many changed local state. */
|
|
495
|
+
apply(entries: SyncEntry[]): Promise<number>;
|
|
496
|
+
}
|
|
497
|
+
interface SyncResult {
|
|
498
|
+
pushed: number;
|
|
499
|
+
applied: number;
|
|
500
|
+
}
|
|
501
|
+
declare class SyncClient {
|
|
502
|
+
private readonly local;
|
|
503
|
+
private readonly relay;
|
|
504
|
+
private readonly key;
|
|
505
|
+
constructor(local: LocalBlobSource, relay: RelayStore, key: Uint8Array);
|
|
506
|
+
private opaqueIds;
|
|
507
|
+
/** Uploads every local blob that is newer than (or absent from) the relay. */
|
|
508
|
+
push(): Promise<number>;
|
|
509
|
+
/** Downloads and applies every remote blob newer than local state. */
|
|
510
|
+
pull(): Promise<number>;
|
|
511
|
+
/** Pull first (so local LWW sees the freshest remote), then push. */
|
|
512
|
+
sync(): Promise<SyncResult>;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
interface IngestResult {
|
|
516
|
+
id: number;
|
|
517
|
+
sessionId: string;
|
|
518
|
+
revision: number;
|
|
519
|
+
action: "created" | "updated";
|
|
520
|
+
threadId: number | null;
|
|
521
|
+
threadTitle: string | null;
|
|
522
|
+
}
|
|
523
|
+
declare class IngestStore {
|
|
524
|
+
private readonly db;
|
|
525
|
+
constructor(dbPath: string);
|
|
526
|
+
/** Idempotent v2 migration: column-presence checks, never version guesses,
|
|
527
|
+
so a v1 database upgrades in place and a v2 one is untouched. */
|
|
528
|
+
private migrate;
|
|
529
|
+
/** Case-insensitive find-or-create; titles are redacted like all content.
|
|
530
|
+
Re-using a tombstoned (deleted) title resurrects that row — clearing
|
|
531
|
+
`deleted` — rather than bumping its clock while it stays deleted (which
|
|
532
|
+
would attach a session to an invisible thread and re-push a tombstone with
|
|
533
|
+
a newer clock). A unique (root, lower(title)) index makes one row per
|
|
534
|
+
title the only possibility, so resurrection is the correct move. */
|
|
535
|
+
ensureThread(root: string, title: string, now?: number): {
|
|
536
|
+
id: number;
|
|
537
|
+
title: string;
|
|
538
|
+
created: boolean;
|
|
539
|
+
};
|
|
540
|
+
/** The thread of a prior session, so `continues_from` carries lineage even
|
|
541
|
+
when the reporting agent never names the thread. */
|
|
542
|
+
private threadOfSession;
|
|
543
|
+
private threadTitle;
|
|
544
|
+
/** Upsert on (root, harness, session_id): re-ingest updates with history. */
|
|
545
|
+
ingest(root: string, input: IngestInput, now?: number, door?: IngestDoor): IngestResult;
|
|
546
|
+
/** Newest-first records for this project. */
|
|
547
|
+
list(root: string, limit?: number): IngestedSession[];
|
|
548
|
+
/** All threads for this project, most recently active first. */
|
|
549
|
+
listThreads(root: string): ThreadSummary[];
|
|
550
|
+
/** A thread's sessions in chronological (oldest-first) order. */
|
|
551
|
+
threadSessions(root: string, threadId: number): IngestedSession[];
|
|
552
|
+
/** The newest auto checkpoint on a thread, for the behavior-layer debounce
|
|
553
|
+
(§4.2: reject unchanged checkpoints inside the window). */
|
|
554
|
+
latestAutoForThread(root: string, threadTitle: string): IngestedSession | null;
|
|
555
|
+
/** Marks a thread private (excluded from auto-capture) or public again. */
|
|
556
|
+
setThreadPrivate(root: string, threadId: number, isPrivate: boolean, now?: number): boolean;
|
|
557
|
+
/** Whether the (case-insensitive) titled thread is private; false when the
|
|
558
|
+
thread does not exist yet. */
|
|
559
|
+
threadIsPrivate(root: string, title: string): boolean;
|
|
560
|
+
/** Deletes one record by numeric id (as shown by `ctxfile ingest list`).
|
|
561
|
+
A tombstone, not a hard delete, so the deletion syncs like any write. */
|
|
562
|
+
remove(root: string, id: number, now?: number): boolean;
|
|
563
|
+
/** Everything syncable for this root, tombstones included. */
|
|
564
|
+
exportSyncEntries(root: string): SyncEntry[];
|
|
565
|
+
/** Applies decrypted sync entries with per-record LWW; returns how many
|
|
566
|
+
changed local state. Content was redacted before it was first stored,
|
|
567
|
+
and blobs are AEAD-authenticated, so imports apply verbatim. */
|
|
568
|
+
importSyncEntries(root: string, entries: SyncEntry[]): number;
|
|
569
|
+
/** This store, adapted to the SyncClient contract for one root. */
|
|
570
|
+
syncSource(root: string): LocalBlobSource;
|
|
571
|
+
private applyThreadPayload;
|
|
572
|
+
private applySessionPayload;
|
|
573
|
+
close(): void;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
interface SnapshotService {
|
|
577
|
+
getContext(scope?: ContextScope): Promise<ContextObject>;
|
|
578
|
+
rebuild(onEvent?: (event: BuildEvent) => void): Promise<ContextObject>;
|
|
579
|
+
/** Fresh cache hit only (cacheMaxAgeMs), scope-filtered; null means a rebuild is needed. */
|
|
580
|
+
getCached(scope?: ContextScope): ContextObject | null;
|
|
581
|
+
latestCached(): ContextObject | null;
|
|
582
|
+
recentSnapshots(limit?: number): {
|
|
583
|
+
createdAt: number;
|
|
584
|
+
tokensUsed: number;
|
|
585
|
+
}[];
|
|
586
|
+
}
|
|
587
|
+
declare function createSnapshotService(config: ResolvedConfig, deps: {
|
|
588
|
+
cache: SnapshotCache | null;
|
|
589
|
+
connectors: Connector[];
|
|
590
|
+
summarizer: Summarizer | null;
|
|
591
|
+
/** Agent-reported sessions merged into every snapshot (parser wins on id). */
|
|
592
|
+
ingest?: IngestStore | null;
|
|
593
|
+
}): SnapshotService;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* The Behavior Layer (design doc 11): one canonical behavior spec, rendered
|
|
597
|
+
* into per-harness formats. MCP gives an agent tools; this gives it behavior:
|
|
598
|
+
* checkpoint at the right moments, announce every save, detect handoffs. The
|
|
599
|
+
* "software" is prose; this module is the renderer, the installer, and the
|
|
600
|
+
* consent/pause state the server's guardrails read.
|
|
601
|
+
*/
|
|
602
|
+
declare const BEHAVIOR_HARNESSES: readonly ["claude-code", "cursor", "agents-md", "codex", "generic"];
|
|
603
|
+
type BehaviorHarness = (typeof BEHAVIOR_HARNESSES)[number];
|
|
604
|
+
/** The canonical spec ships inside the npm package beside dist/. */
|
|
605
|
+
declare function loadCanonicalBehaviors(): string;
|
|
606
|
+
interface RenderedBehavior {
|
|
607
|
+
harness: BehaviorHarness;
|
|
608
|
+
/** Path relative to the render root (or the suggested install location). */
|
|
609
|
+
relativePath: string;
|
|
610
|
+
content: string;
|
|
611
|
+
}
|
|
612
|
+
/** Deterministic renders from the single source; the repo's committed
|
|
613
|
+
render/ folder is regenerated by scripts/render-behaviors.mjs and a test
|
|
614
|
+
fails if it drifts from this function's output. */
|
|
615
|
+
declare function renderBehavior(harness: BehaviorHarness, canonical?: string): RenderedBehavior;
|
|
616
|
+
declare function renderAllBehaviors(canonical?: string): RenderedBehavior[];
|
|
617
|
+
interface DetectedHarness {
|
|
618
|
+
harness: BehaviorHarness;
|
|
619
|
+
reason: string;
|
|
620
|
+
}
|
|
621
|
+
/** Cheap, honest detection: directories the harnesses actually create. */
|
|
622
|
+
declare function detectHarnesses(root: string, home: string): DetectedHarness[];
|
|
623
|
+
interface InstallResult {
|
|
624
|
+
harness: BehaviorHarness;
|
|
625
|
+
target: string;
|
|
626
|
+
action: "created" | "updated" | "printed";
|
|
627
|
+
}
|
|
628
|
+
/** Writes the behavior file where the harness looks for it. Append-style
|
|
629
|
+
targets get the managed block (idempotent re-install); file-style targets
|
|
630
|
+
are whole files we own. */
|
|
631
|
+
declare function installBehavior(harness: BehaviorHarness, root: string, canonical?: string): InstallResult;
|
|
632
|
+
interface UninstallResult {
|
|
633
|
+
harness: BehaviorHarness;
|
|
634
|
+
target: string;
|
|
635
|
+
action: "removed" | "stripped" | "absent";
|
|
636
|
+
}
|
|
637
|
+
/** Reverses installBehavior for one harness. File-style targets we own (the
|
|
638
|
+
claude-code skill dir, the cursor rule) are deleted; the AGENTS.md managed
|
|
639
|
+
block is cut by its markers, leaving any surrounding content intact (and the
|
|
640
|
+
file removed only if our block was all it held). codex/generic were never
|
|
641
|
+
written, so there is nothing to remove. Never touches unrelated content. */
|
|
642
|
+
declare function uninstallBehavior(harness: BehaviorHarness, root: string): UninstallResult;
|
|
643
|
+
interface BehaviorState {
|
|
644
|
+
/** Explicit install-time consent (§4.1). Fail closed: an absent or unreadable
|
|
645
|
+
state file means no consent, so auto-captures are refused until `ctxfile
|
|
646
|
+
init` records it (or `--yes`). */
|
|
647
|
+
autoCapture: boolean;
|
|
648
|
+
paused: boolean;
|
|
649
|
+
consentAt?: string;
|
|
650
|
+
pausedAt?: string;
|
|
651
|
+
}
|
|
652
|
+
declare function readBehaviorState(cacheDir: string): BehaviorState;
|
|
653
|
+
declare function writeBehaviorState(cacheDir: string, state: BehaviorState): void;
|
|
654
|
+
/** Removes the consent/pause record, returning the install to a pre-`init`
|
|
655
|
+
state (fail-closed: auto-capture is then off until consent is recorded
|
|
656
|
+
again). Returns true if a state file was present. */
|
|
657
|
+
declare function clearBehaviorState(cacheDir: string): boolean;
|
|
658
|
+
/** True when automatic captures must be refused right now. */
|
|
659
|
+
declare function autoCaptureBlocked(cacheDir: string): {
|
|
660
|
+
blocked: boolean;
|
|
661
|
+
reason: string | null;
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
declare function inspectLicenseKey(key: string, now?: Date): {
|
|
665
|
+
ok: boolean;
|
|
666
|
+
detail: string;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
interface ProUiFeatures {
|
|
670
|
+
sessions: boolean;
|
|
671
|
+
memory: boolean;
|
|
672
|
+
consult: boolean;
|
|
673
|
+
voice: boolean;
|
|
674
|
+
}
|
|
675
|
+
interface ProMemoryEntry {
|
|
676
|
+
id: string;
|
|
677
|
+
agentId: string;
|
|
678
|
+
content: string;
|
|
679
|
+
createdAt: string;
|
|
680
|
+
/** Human-readable provenance: which tool/session wrote this entry. */
|
|
681
|
+
provenance: string;
|
|
682
|
+
}
|
|
683
|
+
interface ProLicenseInfo {
|
|
684
|
+
/** License tier from the verified payload; null when unlicensed. */
|
|
685
|
+
tier: string | null;
|
|
686
|
+
/** ISO expiry timestamp from the verified payload; null when unlicensed. */
|
|
687
|
+
expiresAt: string | null;
|
|
688
|
+
customerId?: string | null;
|
|
689
|
+
}
|
|
690
|
+
interface ProUiApi {
|
|
691
|
+
features(): ProUiFeatures;
|
|
692
|
+
/** Display metadata from the verified license payload — never the key itself. */
|
|
693
|
+
licenseInfo?(): ProLicenseInfo;
|
|
694
|
+
listMemory(): Promise<ProMemoryEntry[]>;
|
|
695
|
+
forgetMemory(id: string): Promise<boolean>;
|
|
696
|
+
/** Streams consult progress via onEvent; resolves when complete. */
|
|
697
|
+
consult?(question: string, onEvent: (event: {
|
|
698
|
+
type: string;
|
|
699
|
+
data: unknown;
|
|
700
|
+
}) => void): Promise<void>;
|
|
701
|
+
}
|
|
702
|
+
interface ProModule {
|
|
703
|
+
name: string;
|
|
704
|
+
connectors?: Connector[];
|
|
705
|
+
/**
|
|
706
|
+
* Handed the resolved config by createRuntime() before any tools or UI are
|
|
707
|
+
* used — the `ui` surface needs it on paths where registerTools never runs
|
|
708
|
+
* (the `ctxfile ui` command has no MCP server).
|
|
709
|
+
*/
|
|
710
|
+
init?(config: ResolvedConfig): void;
|
|
711
|
+
registerTools?(server: McpServer, config: ResolvedConfig): void;
|
|
712
|
+
/** null when licensed and active; otherwise a human-readable reason Pro is inactive. */
|
|
713
|
+
licenseStatus(): string | null;
|
|
714
|
+
/** Optional UI delegation surface consumed by the local dashboard server. */
|
|
715
|
+
ui?: ProUiApi;
|
|
716
|
+
}
|
|
717
|
+
declare function loadProModule(): Promise<ProModule | null>;
|
|
718
|
+
|
|
719
|
+
interface KdfParams {
|
|
720
|
+
/** Argon2id opsLimit; default interactive (client-side, per libsodium guidance). */
|
|
721
|
+
opsLimit?: number;
|
|
722
|
+
/** Argon2id memLimit in bytes; default interactive (64 MiB). */
|
|
723
|
+
memLimit?: number;
|
|
724
|
+
}
|
|
725
|
+
declare const MASTER_KEY_BYTES = 32;
|
|
726
|
+
declare function generateSalt(): Promise<Uint8Array>;
|
|
727
|
+
/** Passphrase -> 32-byte master key (Argon2id13). Store the salt and params
|
|
728
|
+
beside the vault metadata; they are not secret. */
|
|
729
|
+
declare function deriveMasterKey(passphrase: string, salt: Uint8Array, params?: KdfParams): Promise<Uint8Array>;
|
|
730
|
+
/** Encrypts one blob: MAGIC || 24-byte nonce || AEAD ciphertext.
|
|
731
|
+
`aad` binds the ciphertext to its slot (use the blob id). */
|
|
732
|
+
declare function encryptBlob(key: Uint8Array, plaintext: Uint8Array, aad: string): Promise<Uint8Array>;
|
|
733
|
+
/** Reverses encryptBlob. Throws on tampering, a wrong key, or a wrong aad. */
|
|
734
|
+
declare function decryptBlob(key: Uint8Array, blob: Uint8Array, aad: string): Promise<Uint8Array>;
|
|
735
|
+
declare function generateRecoveryCode(): Promise<string>;
|
|
736
|
+
/** Canonical form of a recovery code for use as a wrapping secret, so a user
|
|
737
|
+
may retype it with any casing or dashing and still unwrap. Wrap and unwrap
|
|
738
|
+
MUST both pass the code through this. */
|
|
739
|
+
declare function normalizeRecoveryCode(code: string): string;
|
|
740
|
+
/** Wraps the master key under a secondary secret (the recovery code), so a
|
|
741
|
+
vault stays recoverable when the passphrase is lost. The wrapped copy is
|
|
742
|
+
safe to store beside the vault. */
|
|
743
|
+
declare function wrapMasterKey(masterKey: Uint8Array, wrappingSecret: string, salt: Uint8Array, params?: KdfParams): Promise<Uint8Array>;
|
|
744
|
+
declare function unwrapMasterKey(wrapped: Uint8Array, wrappingSecret: string, salt: Uint8Array, params?: KdfParams): Promise<Uint8Array>;
|
|
745
|
+
/** Best-effort scrub of key material once done with it. */
|
|
746
|
+
declare function zeroKey(key: Uint8Array): Promise<void>;
|
|
747
|
+
/** Opaque, stable blob id: a keyed hash of the natural id, so the relay
|
|
748
|
+
learns nothing from ids (harness names, session ids stay private). */
|
|
749
|
+
declare function deriveBlobId(key: Uint8Array, naturalId: string): Promise<string>;
|
|
750
|
+
declare function toBase64(bytes: Uint8Array): Promise<string>;
|
|
751
|
+
declare function fromBase64(text: string): Promise<Uint8Array>;
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* RelayStore over HTTP: the client half of the relay's blob API. The wire
|
|
755
|
+
* carries only opaque ids, versions, and base64 ciphertext; the bearer token
|
|
756
|
+
* is the vault token minted at vault create/join.
|
|
757
|
+
*/
|
|
758
|
+
interface HttpRelayStoreOptions {
|
|
759
|
+
fetchImpl?: typeof fetch;
|
|
760
|
+
}
|
|
761
|
+
declare class HttpRelayStore implements RelayStore {
|
|
762
|
+
private readonly token;
|
|
763
|
+
private readonly base;
|
|
764
|
+
private readonly fetchImpl;
|
|
765
|
+
constructor(relayUrl: string, token: string, options?: HttpRelayStoreOptions);
|
|
766
|
+
private request;
|
|
767
|
+
list(): Promise<SyncBlobMeta[]>;
|
|
768
|
+
get(id: string): Promise<{
|
|
769
|
+
meta: SyncBlobMeta;
|
|
770
|
+
data: Uint8Array;
|
|
771
|
+
} | null>;
|
|
772
|
+
put(id: string, meta: SyncBlobMeta, data: Uint8Array): Promise<void>;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* The plaintext shapes that travel inside encrypted sync blobs, shared by the
|
|
777
|
+
* local store (export/import) and the relay (which reconstructs a vault view
|
|
778
|
+
* from decrypted payloads to serve the five MCP tools in Standard mode).
|
|
779
|
+
* Every payload carries its own natural identity, so applies are
|
|
780
|
+
* order-independent and never depend on relay-visible ids.
|
|
781
|
+
*/
|
|
782
|
+
interface SessionSyncPayload {
|
|
783
|
+
kind: "session";
|
|
784
|
+
harness: string;
|
|
785
|
+
harness_version: string | null;
|
|
786
|
+
session_id: string;
|
|
787
|
+
door: string;
|
|
788
|
+
started_at: string | null;
|
|
789
|
+
ended_at: string | null;
|
|
790
|
+
summary: string;
|
|
791
|
+
key_decisions: string[];
|
|
792
|
+
files_touched: string[];
|
|
793
|
+
open_items: string[];
|
|
794
|
+
thread_title: string | null;
|
|
795
|
+
continues_from: string | null;
|
|
796
|
+
handoff: boolean;
|
|
797
|
+
state: string | null;
|
|
798
|
+
gotchas: string[];
|
|
799
|
+
artifacts: IngestArtifact[];
|
|
800
|
+
suggested_first_prompt: string | null;
|
|
801
|
+
/** Behavior-layer provenance; absent in pre-v1 payloads means manual. */
|
|
802
|
+
trigger?: "auto" | "manual";
|
|
803
|
+
ingested_at: number;
|
|
804
|
+
updated_at: number;
|
|
805
|
+
revision: number;
|
|
806
|
+
deleted: boolean;
|
|
807
|
+
}
|
|
808
|
+
interface ThreadSyncPayload {
|
|
809
|
+
kind: "thread";
|
|
810
|
+
title: string;
|
|
811
|
+
status: string;
|
|
812
|
+
tags: string[];
|
|
813
|
+
created_at: number;
|
|
814
|
+
last_active: number;
|
|
815
|
+
deleted: boolean;
|
|
816
|
+
/** Local auto-capture exclusion, synced so it holds across devices. Absent
|
|
817
|
+
in pre-v1 payloads means public. */
|
|
818
|
+
private?: boolean;
|
|
819
|
+
}
|
|
820
|
+
type SyncPayload = SessionSyncPayload | ThreadSyncPayload;
|
|
821
|
+
declare function parseSyncPayload(payload: Uint8Array): SyncPayload | null;
|
|
822
|
+
declare function sessionPayloadToIngestedSession(payload: SessionSyncPayload, id: number, root?: string): IngestedSession;
|
|
823
|
+
interface VaultView {
|
|
824
|
+
threads: ThreadSummary[];
|
|
825
|
+
/** Live sessions, chronological (oldest first), threadTitle populated. */
|
|
826
|
+
sessions: IngestedSession[];
|
|
827
|
+
}
|
|
828
|
+
/** Rebuilds the queryable view a local IngestStore would give, from nothing
|
|
829
|
+
but decrypted payloads. Tombstoned records are dropped; a session whose
|
|
830
|
+
thread payload never arrived still yields a thread summary. */
|
|
831
|
+
declare function buildVaultView(payloads: SyncPayload[]): VaultView;
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* The vault client (M2/M3 seam): create or join a vault on a relay, unlock it
|
|
835
|
+
* with the passphrase, and sync the local store through it. Key design: the
|
|
836
|
+
* blob key is a random data key; the passphrase (and a printed recovery code)
|
|
837
|
+
* merely WRAP it, and the wraps live on the relay as vault metadata, so a new
|
|
838
|
+
* device needs only the vault token plus the passphrase, and a passphrase
|
|
839
|
+
* change is a re-wrap, never a re-encrypt of the world. In Standard mode the
|
|
840
|
+
* data key is additionally enrolled with the relay's keyring for serve-time
|
|
841
|
+
* decryption; in Strict mode it never leaves your devices.
|
|
842
|
+
*/
|
|
843
|
+
interface VaultConfig {
|
|
844
|
+
relayUrl: string;
|
|
845
|
+
vaultId: string;
|
|
846
|
+
name: string;
|
|
847
|
+
mode: "standard" | "strict";
|
|
848
|
+
/** Bearer token for this device; treat like a password. */
|
|
849
|
+
token: string;
|
|
850
|
+
}
|
|
851
|
+
interface VaultMeta {
|
|
852
|
+
vault_id: string;
|
|
853
|
+
name: string;
|
|
854
|
+
mode: "standard" | "strict";
|
|
855
|
+
salt_b64: string;
|
|
856
|
+
kdf: {
|
|
857
|
+
ops_limit: number;
|
|
858
|
+
mem_limit: number;
|
|
859
|
+
};
|
|
860
|
+
wrapped_passphrase_b64: string;
|
|
861
|
+
/** Present on device-token reads; used by 'ctxfile vault recover'. */
|
|
862
|
+
wrapped_recovery_b64?: string;
|
|
863
|
+
}
|
|
864
|
+
/** Minimum vault passphrase length. Raised past a throwaway value because the
|
|
865
|
+
KDF salt and parameters are stored as vault metadata: a weak passphrase is
|
|
866
|
+
offline-bruteforceable by anyone who obtains that metadata. */
|
|
867
|
+
declare const MIN_PASSPHRASE_LENGTH = 12;
|
|
868
|
+
declare function assertPassphraseStrength(passphrase: string): void;
|
|
869
|
+
declare const DEFAULT_VAULT_CONFIG_PATH: string;
|
|
870
|
+
declare function loadVaultConfig(configPath?: string): VaultConfig | null;
|
|
871
|
+
declare function saveVaultConfig(config: VaultConfig, configPath?: string): void;
|
|
872
|
+
interface CreateVaultOptions {
|
|
873
|
+
relayUrl: string;
|
|
874
|
+
name: string;
|
|
875
|
+
mode: "standard" | "strict";
|
|
876
|
+
passphrase: string;
|
|
877
|
+
kdf?: KdfParams;
|
|
878
|
+
configPath?: string;
|
|
879
|
+
fetchImpl?: typeof fetch;
|
|
880
|
+
}
|
|
881
|
+
declare function createVault(options: CreateVaultOptions): Promise<{
|
|
882
|
+
config: VaultConfig;
|
|
883
|
+
recoveryCode: string;
|
|
884
|
+
}>;
|
|
885
|
+
interface JoinVaultOptions {
|
|
886
|
+
relayUrl: string;
|
|
887
|
+
token: string;
|
|
888
|
+
passphrase: string;
|
|
889
|
+
configPath?: string;
|
|
890
|
+
fetchImpl?: typeof fetch;
|
|
891
|
+
}
|
|
892
|
+
/** Second device: vault token + passphrase is all it takes; the salt and the
|
|
893
|
+
wrapped key come down from the relay. */
|
|
894
|
+
declare function joinVault(options: JoinVaultOptions): Promise<VaultConfig>;
|
|
895
|
+
interface RecoverVaultOptions {
|
|
896
|
+
relayUrl: string;
|
|
897
|
+
token: string;
|
|
898
|
+
recoveryCode: string;
|
|
899
|
+
newPassphrase: string;
|
|
900
|
+
configPath?: string;
|
|
901
|
+
fetchImpl?: typeof fetch;
|
|
902
|
+
}
|
|
903
|
+
/** Reset a lost passphrase with the printed recovery code. The data key is
|
|
904
|
+
recovered from the recovery wrap, then re-wrapped under the new passphrase
|
|
905
|
+
and a freshly rotated recovery code — nothing is re-encrypted. Requires a
|
|
906
|
+
device token (the recovery code replaces the passphrase factor, not the
|
|
907
|
+
device factor). Returns the new recovery code; the old one stops working. */
|
|
908
|
+
declare function recoverVault(options: RecoverVaultOptions): Promise<{
|
|
909
|
+
config: VaultConfig;
|
|
910
|
+
recoveryCode: string;
|
|
911
|
+
}>;
|
|
912
|
+
declare function fetchVaultMeta(fetchImpl: typeof fetch, relayUrl: string, token: string): Promise<VaultMeta>;
|
|
913
|
+
declare function unlockVault(config: VaultConfig, passphrase: string, fetchImpl?: typeof fetch): Promise<Uint8Array>;
|
|
914
|
+
/** Everything wired: unlock, point the local store at the relay, sync. */
|
|
915
|
+
declare function openVaultSync(config: VaultConfig, passphrase: string, store: IngestStore, root: string, fetchImpl?: typeof fetch): Promise<SyncClient>;
|
|
916
|
+
|
|
917
|
+
declare function isDeniedPath(relPath: string): boolean;
|
|
918
|
+
declare function redactContent(text: string): {
|
|
919
|
+
text: string;
|
|
920
|
+
redactions: number;
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
interface RuntimeOptions {
|
|
924
|
+
/** Pass null to disable snapshot caching (e.g. in tests). */
|
|
925
|
+
cache?: SnapshotCache | null;
|
|
926
|
+
connectors?: Connector[];
|
|
927
|
+
summarizer?: Summarizer | null;
|
|
928
|
+
/** Commercial Pro module, loaded via loadProModule(). */
|
|
929
|
+
pro?: ProModule | null;
|
|
930
|
+
/** Store for agent-reported sessions; pass null to disable (tests). */
|
|
931
|
+
ingest?: IngestStore | null;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
interface ServerOptions extends RuntimeOptions {
|
|
935
|
+
/** Scope allowlist for this connection; undefined (stdio) means everything. */
|
|
936
|
+
scopes?: ServeScope[];
|
|
937
|
+
}
|
|
938
|
+
declare function createServer(config: ResolvedConfig, options?: ServerOptions): McpServer;
|
|
939
|
+
|
|
940
|
+
declare const VERSION = "0.1.0";
|
|
941
|
+
|
|
942
|
+
export { BEHAVIOR_HARNESSES, type BehaviorHarness, type BehaviorState, type BuildEvent, type BuildExportOptions, type Connector, type ConnectorStatus, type ConsultProviderSpec, type ContextMeta, type ContextObject, type ContextScope, type CreateVaultOptions, DEFAULT_VAULT_CONFIG_PATH, EXPORT_PROFILES, EXPORT_SCHEMA_VERSION, EXPORT_SECTIONS, type ExportEnvelope, type ExportProfile, type ExportSection, type ExportedContext, type ExportedKeyFile, type FetchLike, type GitCommit, type GitState, HttpRelayStore, type HttpRelayStoreOptions, INGEST_SCHEMA_VERSION, type IngestArtifact, type IngestDoor, type IngestInput, type IngestResult, IngestStore, type IngestedSession, type JoinVaultOptions, type KdfParams, type KeyFile, type LoadConfigOptions, type LocalBlobSource, MASTER_KEY_BYTES, MIN_PASSPHRASE_LENGTH, type NotionConnectorOptions, type NotionPage, type OllamaSummarizerOptions, type ProLicenseInfo, type ProMemoryEntry, type ProModule, type ProUiApi, type ProUiFeatures, type RecoverVaultOptions, type RelayStore, type RenderedBehavior, type ResolvedConfig, type SaveSessionInput, type ServeTokenSpec, type ServerOptions, type SessionDigest, type SessionSyncPayload, SnapshotCache, type SnapshotInput, type SnapshotService, type Summarizer, type SyncBlobMeta, SyncClient, type SyncEntry, type SyncPayload, type SyncResult, type ThreadResolution, type ThreadSummary, type ThreadSyncPayload, TokenBudget, type UninstallResult, VERSION, type VaultConfig, type VaultMeta, type VaultView, assertPassphraseStrength, autoCaptureBlocked, buildContext, buildExportEnvelope, buildVaultView, clearBehaviorState, createNotionConnector, createOllamaSummarizer, createServer, createSnapshotService, createVault, decryptBlob, deriveBlobId, deriveMasterKey, detectHarnesses, encryptBlob, estimateTokens, fetchVaultMeta, fileConnector, filterScope, formatIngestErrors, fromBase64, generateRecoveryCode, generateSalt, gitConnector, inferHarnessFromClientName, ingestInputSchema, ingestSessionId, ingestSessionSchema, ingestSourceSchema, ingestToSessionDigest, inspectLicenseKey, installBehavior, isDeniedPath, joinVault, loadCanonicalBehaviors, loadConfig, loadProModule, loadVaultConfig, mergeIngestedSessions, normalizeRecoveryCode, openVaultSync, parseSyncPayload, readBehaviorState, recoverVault, redactContent, renderAllBehaviors, renderBehavior, renderExportMarkdown, renderThreadResume, resolveThread, saveSessionSchema, saveVaultConfig, scoreThreadMatch, sessionPayloadToIngestedSession, toBase64, truncateToTokens, uninstallBehavior, unlockVault, unwrapMasterKey, wrapMasterKey, writeBehaviorState, zeroKey };
|