dsh-context-mode 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/types/index.d.ts +5 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +5 -0
- package/lib/types/precompact.d.ts +88 -0
- package/lib/types/precompact.d.ts.map +1 -0
- package/lib/types/precompact.js +198 -0
- package/lib/types/routing.d.ts.map +1 -1
- package/lib/types/routing.js +5 -4
- package/package.json +1 -1
- package/vendor/context-mode/server.bundle.mjs +1 -1
package/lib/types/index.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface Config {
|
|
|
19
19
|
storageDir?: string;
|
|
20
20
|
/** Timeout for the MCP initialize and tools/list handshake. */
|
|
21
21
|
handshakeTimeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Archive the transcript into the knowledge base when compaction begins, so
|
|
24
|
+
* `ctx_search` can still reach what the compaction summary drops.
|
|
25
|
+
*/
|
|
26
|
+
precompact?: boolean;
|
|
22
27
|
}
|
|
23
28
|
export declare const Config: Schemastery<Config>;
|
|
24
29
|
/** Register the plugin and bridge context-mode's MCP tool catalog into DSH. */
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAOrC,CAAA;AA4DF,+EAA+E;AAC/E,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoF5E"}
|
package/lib/types/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import z from '@deepseek-ai/schemastery';
|
|
|
13
13
|
import { buildCjkQuery, segmentCjk } from './cjk.js';
|
|
14
14
|
import { McpStdioClient } from './mcp-client.js';
|
|
15
15
|
import { installOutputContainment } from './output-containment.js';
|
|
16
|
+
import { installPrecompactArchive } from './precompact.js';
|
|
16
17
|
import { installBashRoutingGuard } from './routing.js';
|
|
17
18
|
import { installSessionMemory } from './session-memory.js';
|
|
18
19
|
export const name = 'dsh-context-mode';
|
|
@@ -22,6 +23,7 @@ export const Config = z.object({
|
|
|
22
23
|
projectDir: z.string().default(''),
|
|
23
24
|
storageDir: z.string().default(''),
|
|
24
25
|
handshakeTimeoutMs: z.number().step(1).min(1_000).default(60_000),
|
|
26
|
+
precompact: z.boolean().default(true),
|
|
25
27
|
});
|
|
26
28
|
const OUTPUT_SCHEMA = {
|
|
27
29
|
type: 'object',
|
|
@@ -72,6 +74,7 @@ export async function apply(ctx, config = {}) {
|
|
|
72
74
|
projectDir: config.projectDir?.trim() || process.cwd(),
|
|
73
75
|
storageDir: config.storageDir?.trim() || join(homedir(), '.dsh', 'context-mode'),
|
|
74
76
|
handshakeTimeoutMs: config.handshakeTimeoutMs ?? 60_000,
|
|
77
|
+
precompact: config.precompact ?? true,
|
|
75
78
|
};
|
|
76
79
|
if (!resolved.enabled)
|
|
77
80
|
return;
|
|
@@ -95,6 +98,8 @@ export async function apply(ctx, config = {}) {
|
|
|
95
98
|
disposers.push(containmentDisposer);
|
|
96
99
|
const memoryDisposer = installSessionMemory(ctx);
|
|
97
100
|
disposers.push(memoryDisposer);
|
|
101
|
+
const precompactDisposer = installPrecompactArchive(ctx, () => client, { enabled: resolved.precompact });
|
|
102
|
+
disposers.push(precompactDisposer);
|
|
98
103
|
const skillDisposer = registerBundledSkill(ctx);
|
|
99
104
|
if (skillDisposer !== undefined)
|
|
100
105
|
disposers.push(skillDisposer);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-compaction transcript archiving for DSH.
|
|
3
|
+
*
|
|
4
|
+
* Compaction replaces the live conversation with a generated summary and
|
|
5
|
+
* prunes the events behind it, so anything the summary omits is gone from the
|
|
6
|
+
* model's reach. This listener captures the transcript as compaction begins
|
|
7
|
+
* and files it into the context-mode knowledge base, where `ctx_search` can
|
|
8
|
+
* retrieve it afterwards.
|
|
9
|
+
*
|
|
10
|
+
* Storage is LAYERED rather than filtered. Every event is archived; the layers
|
|
11
|
+
* differ only in the `source` label they carry, so a caller chooses precision
|
|
12
|
+
* at query time:
|
|
13
|
+
*
|
|
14
|
+
* session/<id>/constraint user messages — requirements, decisions, limits
|
|
15
|
+
* session/<id>/finding tool results — commands run and what they showed
|
|
16
|
+
* session/<id>/narrative assistant prose — reasoning, plans, restatement
|
|
17
|
+
*
|
|
18
|
+
* Nothing is dropped at write time. A message the classifier files as
|
|
19
|
+
* narrative is still present, so a misclassification costs a query's
|
|
20
|
+
* precision rather than the content itself. That is the property a filter
|
|
21
|
+
* would not have.
|
|
22
|
+
*
|
|
23
|
+
* The listener is a best-effort passenger on the compaction path: it never
|
|
24
|
+
* throws, never blocks, and can be disabled by configuration.
|
|
25
|
+
*/
|
|
26
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
27
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
28
|
+
import type { McpStdioClient } from './mcp-client.js';
|
|
29
|
+
/** Layer names appended to the session-scoped source label. */
|
|
30
|
+
export declare const LAYERS: {
|
|
31
|
+
readonly constraint: "constraint";
|
|
32
|
+
readonly finding: "finding";
|
|
33
|
+
readonly narrative: "narrative";
|
|
34
|
+
};
|
|
35
|
+
export type LayerName = (typeof LAYERS)[keyof typeof LAYERS];
|
|
36
|
+
/** Configuration for the pre-compaction archiver. */
|
|
37
|
+
export interface PrecompactOptions {
|
|
38
|
+
/** Whether to archive at all. */
|
|
39
|
+
readonly enabled?: boolean;
|
|
40
|
+
/** Maximum characters archived per layer, guarding against a huge transcript. */
|
|
41
|
+
readonly maxCharsPerLayer?: number;
|
|
42
|
+
}
|
|
43
|
+
interface SessionEventLike {
|
|
44
|
+
readonly type: string;
|
|
45
|
+
readonly seq?: number;
|
|
46
|
+
readonly data?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/** One archived line: the event it came from and the layer it belongs to. */
|
|
49
|
+
interface ArchivedLine {
|
|
50
|
+
readonly layer: LayerName;
|
|
51
|
+
readonly text: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Install the pre-compaction archiver.
|
|
55
|
+
*
|
|
56
|
+
* @param ctx - plugin context carrying the session event bus.
|
|
57
|
+
* @param getClient - resolves the live MCP client, or undefined when the bridge is down.
|
|
58
|
+
* @param options - enablement and size guard.
|
|
59
|
+
* @returns the exact disposer that removes the listener.
|
|
60
|
+
*/
|
|
61
|
+
export declare function installPrecompactArchive(ctx: Context, getClient: () => McpStdioClient | undefined, options?: PrecompactOptions): () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Assign every transcript event to a layer.
|
|
64
|
+
*
|
|
65
|
+
* Classification is by event kind, not by importance scoring: a user message
|
|
66
|
+
* is a constraint because of who produced it, and a tool result is a finding
|
|
67
|
+
* for the same reason. Assistant prose is narrative unless it states a
|
|
68
|
+
* concrete value, which is promoted to `finding` so conclusions the assistant
|
|
69
|
+
* reached are searchable beside the evidence.
|
|
70
|
+
*/
|
|
71
|
+
export declare function classify(events: readonly SessionEventLike[]): ArchivedLine[];
|
|
72
|
+
/**
|
|
73
|
+
* Whether assistant prose states a value worth retrieving on its own.
|
|
74
|
+
*
|
|
75
|
+
* The check targets the shapes that carry an answer: a number with a unit or
|
|
76
|
+
* identifier, a filesystem path, an error identifier, or an explicit finding
|
|
77
|
+
* verb. Prose that merely describes intended work does not qualify, which is
|
|
78
|
+
* what keeps "I need to check the config" out of the findings layer.
|
|
79
|
+
*/
|
|
80
|
+
export declare function statesAConcreteValue(text: string): boolean;
|
|
81
|
+
/** Extract the plain text of one event, ignoring reasoning and non-text blocks. */
|
|
82
|
+
export declare function textOf(event: SessionEventLike): string;
|
|
83
|
+
/** Render one layer body with a retrieval hint the model can act on. */
|
|
84
|
+
export declare function render(text: string): string;
|
|
85
|
+
/** Re-exported for assertions: the content block shape this module reads. */
|
|
86
|
+
export type ArchivedBlock = ContentBlock;
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=precompact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precompact.d.ts","sourceRoot":"","sources":["../../src/precompact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,+DAA+D;AAC/D,eAAO,MAAM,MAAM;;;;CAIT,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAA;AAE5D,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACnC;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAYD,6EAA6E;AAC7E,UAAU,YAAY;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAID;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,cAAc,GAAG,SAAS,EAC3C,OAAO,GAAE,iBAAsB,GAC9B,MAAM,IAAI,CAYZ;AA6CD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,YAAY,EAAE,CAU5E;AAYD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO1D;AAcD,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAwBtD;AAWD,wEAAwE;AACxE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAaD,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,YAAY,CAAA"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-compaction transcript archiving for DSH.
|
|
3
|
+
*
|
|
4
|
+
* Compaction replaces the live conversation with a generated summary and
|
|
5
|
+
* prunes the events behind it, so anything the summary omits is gone from the
|
|
6
|
+
* model's reach. This listener captures the transcript as compaction begins
|
|
7
|
+
* and files it into the context-mode knowledge base, where `ctx_search` can
|
|
8
|
+
* retrieve it afterwards.
|
|
9
|
+
*
|
|
10
|
+
* Storage is LAYERED rather than filtered. Every event is archived; the layers
|
|
11
|
+
* differ only in the `source` label they carry, so a caller chooses precision
|
|
12
|
+
* at query time:
|
|
13
|
+
*
|
|
14
|
+
* session/<id>/constraint user messages — requirements, decisions, limits
|
|
15
|
+
* session/<id>/finding tool results — commands run and what they showed
|
|
16
|
+
* session/<id>/narrative assistant prose — reasoning, plans, restatement
|
|
17
|
+
*
|
|
18
|
+
* Nothing is dropped at write time. A message the classifier files as
|
|
19
|
+
* narrative is still present, so a misclassification costs a query's
|
|
20
|
+
* precision rather than the content itself. That is the property a filter
|
|
21
|
+
* would not have.
|
|
22
|
+
*
|
|
23
|
+
* The listener is a best-effort passenger on the compaction path: it never
|
|
24
|
+
* throws, never blocks, and can be disabled by configuration.
|
|
25
|
+
*/
|
|
26
|
+
import { segmentCjk } from './cjk.js';
|
|
27
|
+
/** Layer names appended to the session-scoped source label. */
|
|
28
|
+
export const LAYERS = {
|
|
29
|
+
constraint: 'constraint',
|
|
30
|
+
finding: 'finding',
|
|
31
|
+
narrative: 'narrative',
|
|
32
|
+
};
|
|
33
|
+
const DEFAULT_MAX_CHARS_PER_LAYER = 120_000;
|
|
34
|
+
/**
|
|
35
|
+
* Install the pre-compaction archiver.
|
|
36
|
+
*
|
|
37
|
+
* @param ctx - plugin context carrying the session event bus.
|
|
38
|
+
* @param getClient - resolves the live MCP client, or undefined when the bridge is down.
|
|
39
|
+
* @param options - enablement and size guard.
|
|
40
|
+
* @returns the exact disposer that removes the listener.
|
|
41
|
+
*/
|
|
42
|
+
export function installPrecompactArchive(ctx, getClient, options = {}) {
|
|
43
|
+
if (options.enabled === false)
|
|
44
|
+
return () => { };
|
|
45
|
+
const maxCharsPerLayer = options.maxCharsPerLayer ?? DEFAULT_MAX_CHARS_PER_LAYER;
|
|
46
|
+
const archived = new Set();
|
|
47
|
+
return ctx.on('session/event', (session, event) => {
|
|
48
|
+
if (event.type !== 'compaction/start')
|
|
49
|
+
return;
|
|
50
|
+
void archive(session, getClient, maxCharsPerLayer, archived).catch(() => {
|
|
51
|
+
// Archiving is a best-effort passenger on the compaction path; a failure
|
|
52
|
+
// here must never surface in the compaction that triggered it.
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Read the transcript, classify it, and file each layer into the knowledge base. */
|
|
57
|
+
async function archive(session, getClient, maxCharsPerLayer, archived) {
|
|
58
|
+
const client = getClient();
|
|
59
|
+
if (client === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const key = sessionId(session);
|
|
62
|
+
// One archive per compaction; repeated start events for the same session
|
|
63
|
+
// compaction must not duplicate the content.
|
|
64
|
+
const stamp = `${key}:${session.seq ?? session.snapshotEvents().length}`;
|
|
65
|
+
if (archived.has(stamp))
|
|
66
|
+
return;
|
|
67
|
+
archived.add(stamp);
|
|
68
|
+
const lines = classify(session.snapshotEvents());
|
|
69
|
+
if (lines.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
const grouped = group(lines, maxCharsPerLayer);
|
|
72
|
+
for (const [layer, text] of grouped) {
|
|
73
|
+
if (text.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
await client.callTool('ctx_index', { content: segmentCjk(render(text)), source: `session/${key}/${layer}` }, new AbortController().signal);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Split archived lines into one text body per layer, respecting the size guard. */
|
|
79
|
+
function group(lines, maxChars) {
|
|
80
|
+
const out = new Map();
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const current = out.get(line.layer) ?? '';
|
|
83
|
+
if (current.length >= maxChars)
|
|
84
|
+
continue;
|
|
85
|
+
const next = current.length === 0 ? line.text : `${current}\n\n${line.text}`;
|
|
86
|
+
out.set(line.layer, next.length > maxChars ? next.slice(0, maxChars) : next);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Assign every transcript event to a layer.
|
|
92
|
+
*
|
|
93
|
+
* Classification is by event kind, not by importance scoring: a user message
|
|
94
|
+
* is a constraint because of who produced it, and a tool result is a finding
|
|
95
|
+
* for the same reason. Assistant prose is narrative unless it states a
|
|
96
|
+
* concrete value, which is promoted to `finding` so conclusions the assistant
|
|
97
|
+
* reached are searchable beside the evidence.
|
|
98
|
+
*/
|
|
99
|
+
export function classify(events) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const event of events) {
|
|
102
|
+
const text = textOf(event);
|
|
103
|
+
if (text.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
const layer = layerOf(event.type, text);
|
|
106
|
+
if (layer === undefined)
|
|
107
|
+
continue;
|
|
108
|
+
lines.push({ layer, text: `${heading(event, layer)}\n${text}` });
|
|
109
|
+
}
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
/** Return the layer for one event, or undefined when it carries no transcript value. */
|
|
113
|
+
function layerOf(type, text) {
|
|
114
|
+
if (type === 'user/message')
|
|
115
|
+
return LAYERS.constraint;
|
|
116
|
+
if (type === 'tool/result')
|
|
117
|
+
return LAYERS.finding;
|
|
118
|
+
if (type === 'assistant/message') {
|
|
119
|
+
return statesAConcreteValue(text) ? LAYERS.finding : LAYERS.narrative;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether assistant prose states a value worth retrieving on its own.
|
|
125
|
+
*
|
|
126
|
+
* The check targets the shapes that carry an answer: a number with a unit or
|
|
127
|
+
* identifier, a filesystem path, an error identifier, or an explicit finding
|
|
128
|
+
* verb. Prose that merely describes intended work does not qualify, which is
|
|
129
|
+
* what keeps "I need to check the config" out of the findings layer.
|
|
130
|
+
*/
|
|
131
|
+
export function statesAConcreteValue(text) {
|
|
132
|
+
return (/\d+\s*(ms|s|m|h|kb|mb|gb|%|个|次|行|条|秒|分|小时)/i.test(text) ||
|
|
133
|
+
/\b[A-Za-z]:[\\/]|\/(?:Users|home|var|etc|opt|tmp)\//.test(text) ||
|
|
134
|
+
/\b[A-Z]{2,}[_-]\d+\b|\b[a-z]+(?:[A-Z][a-z]+)+\b/.test(text) ||
|
|
135
|
+
/(根因|原因是|结果是|结论是|发现|定位到|确认了|实际上|真实值|上限为|下限为|等于|超过)/.test(text));
|
|
136
|
+
}
|
|
137
|
+
/** Build the model-facing heading that names the event and its layer. */
|
|
138
|
+
function heading(event, layer) {
|
|
139
|
+
const data = asRecord(event.data);
|
|
140
|
+
if (layer === LAYERS.constraint)
|
|
141
|
+
return `## [约束] 用户 (seq ${event.seq ?? '?'})`;
|
|
142
|
+
if (event.type === 'tool/result') {
|
|
143
|
+
const name = typeof data?.name === 'string' ? data.name : 'tool';
|
|
144
|
+
const failed = data?.isError === true ? ' (失败)' : '';
|
|
145
|
+
return `## [结论] ${name}${failed} (seq ${event.seq ?? '?'})`;
|
|
146
|
+
}
|
|
147
|
+
return `## [发现] 助手 (seq ${event.seq ?? '?'})`;
|
|
148
|
+
}
|
|
149
|
+
/** Extract the plain text of one event, ignoring reasoning and non-text blocks. */
|
|
150
|
+
export function textOf(event) {
|
|
151
|
+
const data = asRecord(event.data);
|
|
152
|
+
if (data === undefined)
|
|
153
|
+
return '';
|
|
154
|
+
if (event.type === 'tool/call') {
|
|
155
|
+
const name = typeof data.name === 'string' ? data.name : '';
|
|
156
|
+
const args = typeof data.arguments === 'string' ? data.arguments : '';
|
|
157
|
+
return name.length === 0 ? '' : `调用 ${name} ${clip(args, 240)}`.trim();
|
|
158
|
+
}
|
|
159
|
+
const message = asRecord(data.message) ?? data;
|
|
160
|
+
const content = message.content;
|
|
161
|
+
if (typeof content === 'string')
|
|
162
|
+
return clip(content, 4_000);
|
|
163
|
+
if (!Array.isArray(content))
|
|
164
|
+
return '';
|
|
165
|
+
const parts = [];
|
|
166
|
+
for (const block of content) {
|
|
167
|
+
if (block === null || typeof block !== 'object')
|
|
168
|
+
continue;
|
|
169
|
+
const record = block;
|
|
170
|
+
// Reasoning is the model's scratch space, not a transcript fact.
|
|
171
|
+
if (record.type !== 'text' || typeof record.text !== 'string')
|
|
172
|
+
continue;
|
|
173
|
+
parts.push(record.text);
|
|
174
|
+
}
|
|
175
|
+
return clip(parts.join('\n'), 4_000);
|
|
176
|
+
}
|
|
177
|
+
/** Stable session identity used in the source label. */
|
|
178
|
+
function sessionId(session) {
|
|
179
|
+
if (typeof session.id === 'string' && session.id.length > 0)
|
|
180
|
+
return session.id;
|
|
181
|
+
const events = session.snapshotEvents();
|
|
182
|
+
const first = events[0]?.seq ?? 0;
|
|
183
|
+
const last = events.at(-1)?.seq ?? 0;
|
|
184
|
+
return `seq${first}-${last}`;
|
|
185
|
+
}
|
|
186
|
+
/** Render one layer body with a retrieval hint the model can act on. */
|
|
187
|
+
export function render(text) {
|
|
188
|
+
return text;
|
|
189
|
+
}
|
|
190
|
+
function asRecord(value) {
|
|
191
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
192
|
+
? value
|
|
193
|
+
: undefined;
|
|
194
|
+
}
|
|
195
|
+
function clip(value, max) {
|
|
196
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
197
|
+
return normalized.length <= max ? normalized : `${normalized.slice(0, max - 1)}…`;
|
|
198
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAgExE,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAiCtE;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM1D;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAcvD"}
|
package/lib/types/routing.js
CHANGED
|
@@ -12,8 +12,8 @@ const BLOCKED_HTTP_PATTERNS = [
|
|
|
12
12
|
* the right tool. Anything not listed here is judged by the flood rules below.
|
|
13
13
|
*/
|
|
14
14
|
const SAFE_COMMAND_PATTERNS = [
|
|
15
|
-
// File mutations and
|
|
16
|
-
/^(mkdir|rmdir|mv|cp|ln|touch|chmod|chown|cd|pwd|which|command|type|rm|unlink)\b/,
|
|
15
|
+
// File mutations, navigation, and short directory listings.
|
|
16
|
+
/^(mkdir|rmdir|mv|cp|ln|touch|chmod|chown|cd|pwd|which|command|type|rm|unlink|ls)\b/,
|
|
17
17
|
// Process control.
|
|
18
18
|
/^(kill|pkill|killall|jobs|fg|bg)\b/,
|
|
19
19
|
// Package management (progress output, no data payload).
|
|
@@ -28,9 +28,10 @@ const FLOOD_COMMAND_PATTERNS = [
|
|
|
28
28
|
// Tests, builds, linters, type checks.
|
|
29
29
|
/^(npm|pnpm|yarn|bun)\s+(run\s+)?(test|build|lint|check|typecheck|coverage|audit|outdated|why|ls|view)\b/,
|
|
30
30
|
/^(jest|vitest|mocha|ava|pytest|tox|nose|go\s+test|cargo\s+(test|build|check|clippy)|mvn|gradle|make|tsc|eslint|ruff|mypy|flake8)\b/,
|
|
31
|
-
// Filesystem and text dumps.
|
|
31
|
+
// Filesystem and text dumps. `ls` is deliberately absent: it stays on Bash
|
|
32
|
+
// because a directory listing is short in practice and is used constantly.
|
|
32
33
|
/^(cat|bat|less|more|head|tail|nl|tac|xxd|od|strings|wc)\b/,
|
|
33
|
-
/^(find|fd|tree|
|
|
34
|
+
/^(find|fd|tree|du|df|stat|file)\b/,
|
|
34
35
|
// Log and stream readers.
|
|
35
36
|
/^(journalctl|dmesg|log\s+show|syslog)\b/,
|
|
36
37
|
// Repository history and search.
|
package/package.json
CHANGED
|
@@ -582,7 +582,7 @@ ${s}`}}import{execFileSync as WN}from"node:child_process";function GN(){if(proce
|
|
|
582
582
|
WHERE tool IN ('ctx_search', 'ctx_fetch_and_index')`).get();m?.bytes&&(a+=Number(m.bytes))}catch{}}}finally{h.close()}}catch{}}let u=0;t.sessionId&&t.contentDbPath&&(u=UD(t.sessionId,t.contentDbPath,{loadDatabase:t.loadDatabase}),i+=u);let l=Math.floor((o+i+c)/4);return{eventDataBytes:o,bytesAvoided:i,bytesReturned:a,snapshotBytes:c,contentBytes:u,totalSavedTokens:l}}function am(t){let e=pc({worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir}),r=pc({sessionId:t.sessionId,worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir,contentDbPath:t.contentDbPath}),n=r.bytesReturned,s=e.bytesAvoided+e.bytesReturned,o=Math.max(0,s-n);return{eventDataBytes:e.eventDataBytes,bytesAvoided:o,bytesReturned:n,snapshotBytes:e.snapshotBytes,contentBytes:r.contentBytes,totalSavedTokens:Math.floor((e.eventDataBytes+o+e.snapshotBytes)/4)}}var qD={minEvents:100,minProjects:5,recencyMs:30*864e5,minAvgBytes:50};function BD(t,e,r){let n={name:t.name,eventCount:0,sessionCount:0,dataBytes:0,rescueBytes:0,contentBytes:0,uuidConvs:0,projectDirs:[],firstMs:Number.POSITIVE_INFINITY,lastMs:0,isReal:!1};if(!_r(t.sessionsDir))return n;let s=[];try{s=ks(t.sessionsDir).filter(l=>l.endsWith(".db"))}catch{return n}if(s.length===0)return n;let o=null;try{o=e()}catch{return n}if(!o)return n;let i=new Set,a=new Set;for(let l of s){let d=vt(t.sessionsDir,l);try{let f=new o(d,{readonly:!0});try{let h=f.prepare("SELECT COUNT(*) AS cnt, COALESCE(SUM(LENGTH(data)), 0) AS bytes FROM session_events").get();h&&(n.eventCount+=Number(h.cnt??0),n.dataBytes+=Number(h.bytes??0));try{let p=f.prepare("SELECT COUNT(*) AS cnt FROM session_meta").get();n.sessionCount+=Number(p?.cnt??0)}catch{}try{let p=f.prepare("SELECT COALESCE(SUM(length(snapshot)), 0) AS bytes FROM session_resume WHERE consumed = 1").get();p?.bytes&&(n.rescueBytes+=Number(p.bytes))}catch{}try{let p=f.prepare("SELECT MIN(created_at) AS mn, MAX(created_at) AS mx FROM session_events").get();if(p?.mn){let m=Date.parse(p.mn+(p.mn.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m<n.firstMs&&(n.firstMs=m)}if(p?.mx){let m=Date.parse(p.mx+(p.mx.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m>n.lastMs&&(n.lastMs=m)}}catch{}try{let p=f.prepare("SELECT DISTINCT project_dir AS p FROM session_events WHERE project_dir != ''").all();for(let m of p)m.p&&i.add(m.p)}catch{}try{let p=f.prepare("SELECT DISTINCT session_id AS s FROM session_events").all();for(let m of p)m.s&&a.add(m.s)}catch{}}finally{f.close()}}catch{}}n.projectDirs=Array.from(i),n.uuidConvs=a.size;let c=n.eventCount>0?n.dataBytes/n.eventCount:0,u=n.lastMs>0&&r.nowMs-n.lastMs<=r.recencyMs;return n.isReal=n.eventCount>=r.minEvents&&i.size>=r.minProjects&&u&&c>=r.minAvgBytes,n}function gc(t){let e=FD({home:t?.home}),r=t?.loadDatabase??He,n={...qD,...t?.filter??{},nowMs:t?.filter?.nowMs??Date.now()},s=[],o=0,i=0,a=0;for(let c of e){if(!_r(c.sessionsDir))continue;let u=BD(c,r,n);s.push(u),o+=u.eventCount,i+=u.sessionCount,a+=u.dataBytes+u.rescueBytes}return{totalEvents:o,totalSessions:i,totalBytes:a,perAdapter:s}}var SS={project:"What you're building",feedback:"How you work",user:"Who you are",reference:"Where to look",memory:"Long-term context",other:"Other notes"},HD={"claude-code":"Claude Code","gemini-cli":"Gemini CLI",antigravity:"Antigravity","antigravity-cli":"Antigravity CLI",openclaw:"Openclaw",codex:"Codex CLI",cursor:"Cursor","vscode-copilot":"VS Code Copilot","copilot-cli":"GitHub Copilot CLI",kiro:"Kiro",pi:"Pi",omp:"OMP","qwen-code":"Qwen Code",kilo:"Kilo",opencode:"OpenCode",zed:"Zed","jetbrains-copilot":"JetBrains"};function fc(t){return HD[t]??t}function Ve(t){if(!Number.isFinite(t)||t<=0)return"0 B";if(t<1024)return`${Math.round(t)} B`;let e=t/1024;if(e<1024)return e<100?`${e.toFixed(1)} KB`:`${Math.round(e)} KB`;let r=e/1024;if(r<1024)return r<100?`${r.toFixed(1)} MB`:`${Math.round(r)} MB`;let n=r/1024;return n<100?`${n.toFixed(2)} GB`:`${n.toFixed(1)} GB`}function VD(t){let e=parseFloat(t);if(isNaN(e)||e<1)return"< 1 min";if(e<60)return`${Math.round(e)} min`;let r=Math.floor(e/60),n=Math.round(e%60);return n>0?`${r}h ${n}m`:`${r}h`}function lc(t){if(!t)return!1;try{return Intl.DateTimeFormat.supportedLocalesOf(t).length===0?!1:(new Intl.DateTimeFormat(t),!0)}catch{return!1}}function WD(){let t=process.env??{},e=t.CONTEXT_MODE_LOCALE??"";if(e&&!lc(e)&&(e=""),!e){if(process.platform==="darwin"){try{let n=zD("defaults",["read","-g","AppleLocale"],{encoding:"utf8",timeout:500}).trim();n&&(e=n.replace(/_/g,"-"))}catch{}e&&!lc(e)&&(e="")}if(!e&&(t.LC_TIME||t.LANG)){let n=(t.LC_TIME||t.LANG||"").split(".")[0];n&&(e=n.replace(/_/g,"-")),e&&!lc(e)&&(e="")}if(!e)try{e=new Intl.DateTimeFormat().resolvedOptions().locale}catch{e="en-US"}}let r=t.CONTEXT_MODE_TZ??"";if(!r)try{r=new Intl.DateTimeFormat().resolvedOptions().timeZone}catch{r="UTC"}return lc(e)||(e="en-US"),{locale:e,tz:r||"UTC"}}function pS(t){let e=hc();return e?t===e?"~":t.startsWith(e+LD)?"~"+t.slice(e.length):t:t}function GD(t,e,r){if(!Number.isFinite(e)||e<=0)return[];let n=e*ni(),s=(y,b=2)=>y.toFixed(b),o=Math.round(n/20),i=(n/200).toFixed(1),a=Math.round(n/73.67),c=Math.round(n*10),u=r>0?Math.round(n*10/r*365):0,l=(e*3/1e6).toFixed(2),d=(e*2.5/1e6).toFixed(2),f=(e*1.25/1e6).toFixed(2),h=(e*1/1e6).toFixed(2),p=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN!==void 0,m=process.env.PI_CONTEXT_MODE_MODEL_ID,g=[];return p&&m?g.push(` $${s(n)} of ${m} tokens your team didn't burn.`):p?g.push(` $${s(n)} of tokens your team didn't burn.`):g.push(` $${s(n)} of Opus 4.7 tokens your team didn't burn.`),g.push(` context-mode kept ${Ve(t)} out of context \u2014 that's ${o} months of Cursor Pro paid for itself.`),c>0&&u>0&&(g.push(""),g.push(` Scale across a 10-dev team and that's ~$${u.toLocaleString("en-US")}/year saved.`)),p||(g.push(""),g.push(" (Opus rates shown for context. On cheaper models the dollar number drops; the savings ratio holds.)")),g}function KD(t){let{conversation:e,lifetime:r,multiAdapter:n,realBytes:s,cwd:o,locale:i,tz:a,now:c,version:u,latestVersion:l}=t,d=[],f=e.events*yS,h=Math.round((e.snapshotBytes??0)/4),p=f+h,m=s?.conversation?.totalSavedTokens??0,g=Math.max(p,m),y=(r?.totalEvents??0)*yS,b=Math.round((r?.rescueBytes??0)/4),_=y+b,x=s?.lifetime?.totalSavedTokens??0,w=Math.max(_,x),I=s?.lifetime?.bytesReturned??0,R=s?.lifetime?.bytesAvoided??0,M=I+R>0?Math.max(1,Math.floor(I/4)):Math.max(1,Math.round(w*.02)),z=n?.totalBytes&&n.totalBytes>0?n.totalBytes:w*4,W=s?.conversation?s.conversation.eventDataBytes+s.conversation.bytesAvoided+s.conversation.snapshotBytes:g*4,$=e.daysAlive>=1?`${e.daysAlive.toFixed(1)} days alive \xB7 still going`:`${Math.max(1,Math.round(e.daysAlive*24))} hr alive \xB7 still going`,T=r?.firstEventMs??n?.perAdapter?.[0]?.firstMs??0,F=T>0?Math.max(1,Math.round((c-T)/864e5)):0,de=n?.totalSessions??r?.totalSessions??1,nt=n?.perAdapter.filter(Pe=>Pe.isReal).length??0,ir;if(n&&nt>=2)ir=`across ${nt} AI tools`;else if(n&&nt===1){let Pe=n.perAdapter.find(Ct=>Ct.isReal);ir=`in ${Pe?fc(Pe.name):"Claude Code"}`}else ir="in Claude Code";F>0?d.push(` Across ${F} days you ran ${qt(de)} conversations ${ir}.`):d.push(` You ran ${qt(de)} conversations ${ir}.`);let Ac=F>0?z/F:0;d.push(` context-mode kept ${Ve(z)} out of your context window \u2014 about ${Ve(Ac)} every single day.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 1. Where you are now \u2500\u2500\u2500"),d.push("");let Em=e.firstEventMs&&e.firstEventMs>0?fS(e.firstEventMs,i,a):"";if(Em?d.push(` This conversation started ${Em} in ${pS(o)}.`):d.push(` This conversation lives in ${pS(o)}.`),d.push(` ${$}.`),e.snapshotsConsumed>0&&e.snapshotBytes>0){let Pe=e.lastRescueMs&&e.lastRescueMs>0?fS(e.lastRescueMs,i,a):"",Ct=Math.round(e.snapshotBytes/1024);Pe?d.push(` On ${Pe}, /compact fired \u2014 ${Ct} KB rescued from snapshot.`):d.push(` /compact fired \u2014 ${Ct} KB rescued from snapshot.`),d.push(" Without that, you'd be re-explaining everything to a blank model right now.")}d.push("");let km=s?.conversation,Tm=km?.bytesAvoided??0,Nc=km?.bytesReturned??0;if(Tm+Nc===0)d.push(" No measurable redirect activity captured yet \u2014 bars will appear once context-mode diverts its first payload."),d.push("");else{let Pe=Tm+Nc,Ct=Math.max(1,Nc),It=Math.max(1,Math.floor(Pe/4)),ar=Math.max(1,Math.floor(Ct/4)),Dc=Ur(It,It,32),XS=Ur(ar,It,32),YS=(1-ar/It)*100,QS=Math.max(1,Math.round(It/ar));d.push(` Without context-mode ${Ve(Pe).padStart(8)} ${Dc} ${qt(It).padStart(7)} tokens`),d.push(` With context-mode ${Ve(Ct).padStart(8)} ${XS} ${qt(ar).padStart(7)} tokens`),d.push(` ${YS.toFixed(1)}% kept out of context \xB7 your AI ran ${QS}\xD7 longer before /compact fired`),d.push("")}if(e.byDay&&e.byDay.length>0){let Pe=e.lastEventMs&&e.firstEventMs?Math.max(1,Math.round((e.lastEventMs-e.firstEventMs)/864e5)+1):e.byDay.length;d.push(` How that ${Ve(W)} built up \u2014 ${Pe} days, ${e.byDay.length} active:`),d.push(""),d.push(...XD(e.byDay,i,a))}d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 2. What this chat captured (used when you --continue or /resume here) \u2500\u2500\u2500"),d.push("");let WS=e.byCategory.reduce((Pe,Ct)=>Pe+Ct.count,0).toLocaleString(i);d.push(` ${WS} things \u2014 files, errors, decisions, agent runs:`),d.push("");let GS=e.byCategory[0]?.count??1;for(let Pe of e.byCategory)d.push(` ${Pe.label.padEnd(26)} ${String(Pe.count).padStart(5)} ${Ur(Pe.count,GS,28)}`);d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 3. The scope, getting wider \u2500\u2500\u2500"),d.push("");let $m=e.firstEventMs&&e.firstEventMs>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(e.firstEventMs)):"",Rm=T>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(T)):"",Pm=r?.distinctProjects??0,KS=r?.totalEvents??n?.totalEvents??0;if(d.push(` This chat: ${Ve(W)} kept out \xB7 ${e.events.toLocaleString(i)} captures${$m?` \xB7 started ${$m}`:""}.`),d.push(` All your work: ${Ve(z)} kept out \xB7 ${KS.toLocaleString(i)} captures across ${Pm} project${Pm===1?"":"s"}${Rm?` \xB7 since ${Rm}`:""}.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 4. The bottom line \u2500\u2500\u2500"),d.push(""),d.push(...GD(z,w,F)),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 5. What context-mode learned about how you work \u2500\u2500\u2500"),d.push(""),r&&r.autoMemoryCount>0){d.push(` ${r.autoMemoryCount} preferences picked up across ${r.autoMemoryProjects} project${r.autoMemoryProjects===1?"":"s"}:`);let Pe=Object.entries(r.autoMemoryByPrefix).sort((It,ar)=>ar[1]-It[1]),Ct=Pe.length>0?Pe[0][1]:1;for(let[It,ar]of Pe){let Dc=SS[It]??It;d.push(` ${Dc.padEnd(26)} ${String(ar).padStart(2)} ${Ur(ar,Ct,20)}`)}}else d.push(" No preferences learned yet \u2014 context-mode picks them up automatically.");d.push(""),d.push(""),d.push(" Your AI talks less, remembers more, costs less."),d.push(` Locale ${i} \xB7 timezone ${a} \xB7 pricing examples for illustration only.`),d.push("");let JS=u?`v${u}`:"context-mode";return d.push(` ${JS}`),u&&l&&l!=="unknown"&&bS(l,u)&&d.push(` Update available: v${u} -> v${l} | ctx_upgrade`),JD(d)}function JD(t){let e=[],r=0;for(let n of t)n===""?(r++,r<=2&&e.push(n)):(r=0,e.push(n));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e}function XD(t,e,r){if(t.length===0)return[];let n=[...t].sort((f,h)=>f.ms-h.ms),s=n[0],o=n[n.length-1],i=Math.max(1,o.ms-s.ms),a=n[0];for(let f of n)f.count>a.count&&(a=f);let c=56,u=Array.from({length:c},()=>"\u2500");for(let f of n){let h=Math.round((f.ms-s.ms)/i*(c-1)),p="\u25CF";f===a&&(p="\u2588"),(f.rescueBytes??0)>0&&(p="\u25C6"),u[h]=p}let l=f=>{let h=new Intl.DateTimeFormat(e,{timeZone:r,month:"short",day:"numeric"}).formatToParts(new Date(f)),p=(h.find(g=>g.type==="month")?.value??"").toLowerCase(),m=h.find(g=>g.type==="day")?.value??"";return`${p} ${m}`},d=[];d.push(` ${l(s.ms)} ${u.join("")} ${l(o.ms)}`),d.push("");for(let f of n){let h=l(f.ms).padEnd(7),p=`${f.count} captures`,m=f===a?" \u2190 peak":"",g=(f.rescueBytes??0)>0?` \u25C6 /compact rescued ${Math.round((f.rescueBytes??0)/1024)} KB`:"";d.push(` ${h} ${p}${m}${g}`)}return d.push(""),d.push(" \u25CF active day \u2588 peak day \u25C6 /compact rescue"),d}function fS(t,e,r){if(!Number.isFinite(t)||t<=0)return"";let n=new Date(t);if(Number.isNaN(n.getTime()))return"";let s=new Intl.DateTimeFormat(e,{timeZone:r,year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(n),o=d=>s.find(f=>f.type===d)?.value??"",i=o("day"),a=o("month"),c=o("year"),u=o("hour"),l=o("minute");return u==="24"&&(u="00"),`${i} ${a} ${c} at ${u}:${l} (${r})`}function qt(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function ni(){let t=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN;if(t!==void 0&&t!==""){let e=Number(t);if(Number.isFinite(e)&&e>0)return e}return 5/1e6}var eB=5/1e6;function mc(t){return`$${((Number.isFinite(t)&&t>0?t:0)*ni()).toFixed(2)}`}function Ur(t,e,r=40){if(e<=0)return"\u2591".repeat(r);let n=Math.max(1,Math.round(t/e*r));return"\u2588".repeat(Math.min(n,r))+"\u2591".repeat(Math.max(0,r-n))}function mS(t,e){let r=e?.sessionTokensSaved??0;if(t.total_events===0&&(e?.lifetime?.totalEvents??0)===0&&r===0&&(e?.multiAdapter?.totalEvents??0)===0)return[];let n=e?.topN??Number.POSITIVE_INFINITY,s=[];s.push("");let o=e?.multiAdapter,i=o?.perAdapter.filter(m=>m.isReal).length??0,a=o?.totalEvents??e?.lifetime?.totalEvents??t.total_events,c=o?.totalSessions??e?.lifetime?.totalSessions??t.session_count,u=e?.lifetime?.distinctProjects;if(a>0&&u&&u>0){let m=i>=2?" everywhere":"";s.push(` All your work${m} \xB7 ${qt(a)} events captured across ${u} project${u===1?"":"s"} \xB7 ${qt(c)} conversations`)}else{s.push("Persistent memory \u2713 preserved across compact, restart & upgrade");let m=c===0&&r>0?1:c,g=m===1?"1 session":`${qt(m)} sessions`,y=a*256+r;s.push(` ${qt(a)} events \xB7 ${g} \xB7 ~${mc(y)} saved lifetime`)}s.push("");let l=e?.lifetime?.categoryCounts,d;l&&Object.keys(l).length>0?d=Object.entries(l).filter(([,m])=>m>0).map(([m,g])=>({category:m,count:g,label:dc[m]||m})).sort((m,g)=>g.count-m.count):d=(t.by_category??[]).filter(m=>m&&m.count>0);let f=d.slice(0,n),h=f.length>0?f[0].count:1;for(let m of f)s.push(` ${m.label.padEnd(26)} ${String(m.count).padStart(5)} ${Ur(m.count,h,30)}`);let p=Math.max(0,d.length-n);return p>0&&s.push(` ... ${p} more categor${p===1?"y":"ies"}`),s}function hS(t){if(!t||t.autoMemoryCount===0)return[];let e=[];e.push(""),e.push(` Preferences learned \xB7 ${t.autoMemoryCount} across ${t.autoMemoryProjects} project${t.autoMemoryProjects===1?"":"s"}`);let r=Object.entries(t.autoMemoryByPrefix).sort((s,o)=>o[1]-s[1]).slice(0,6),n=r.length>0?r[0][1]:1;for(let[s,o]of r){let i=SS[s]??s;e.push(` ${i.padEnd(26)} ${String(o).padStart(2)} ${Ur(o,n,20)}`)}return e}function gS(t,e){let r=[],n=mc(t),s=(e?.totalEvents??0)*256+t,o=mc(s);return r.push(""),r.push("\u2500".repeat(65)),r.push("Your AI talks less, remembers more, costs less."),r.push(`${n} this session \xB7 ${o} lifetime`),r.push("\u2500".repeat(65)),r}var yS=256;function _S(t){if(!t)return[];let e=[],r=[];for(let s of t.perAdapter)(s.isReal?e:r).push(s);if(e.length===0&&r.length===0)return[];let n=[];if(e.length>0){n.push(""),n.push("Where it came from (tools you actually used \u2014 fixtures + probes filtered):"),n.push("");let s=16,o=10,i=10,a=16;n.push(` ${"Tool".padEnd(s)}${"Captures".padStart(o)}${"Indexed".padStart(i)}${"Total kept out".padStart(a)}`);let c=[...e].sort((u,l)=>l.dataBytes+l.rescueBytes-(u.dataBytes+u.rescueBytes));for(let u of c){let l=u.dataBytes+u.rescueBytes,d=u.eventCount>0?qt(u.eventCount):"\u2014",f=Ve(u.dataBytes),h=Ve(l);n.push(` ${fc(u.name).padEnd(s)}${d.padStart(o)}${f.padStart(i)}${h.padStart(a)}`)}}if(r.length>0){e.length>0&&n.push("");let s=r.map(o=>fc(o.name)).join(", ");n.push(` Skipped (${r.length}): ${s}`),n.push(" These adapters have DBs on disk but only test fixtures, dev skeletons,"),n.push(" or detection probes \u2014 no real chat activity.")}return n}function yc(t,e,r,n){let s=[],o=VD(t.session.uptime_min),i=n?.lifetime,a=n?.mcpUsage,c=n?.conversation,u=n?.realBytes,l=n?.multiAdapter,d=l?.perAdapter.filter(I=>I.isReal).length??0;if(l&&d>0){let I=l.totalSessions||i?.totalSessions||0,R=i?.firstEventMs??0,M=R>0?Math.max(1,Math.round((Date.now()-R)/864e5)):0,z=M>0?`Across ${M} day${M===1?"":"s"} `:"",W=I>0?`you ran ${qt(I)} conversation${I===1?"":"s"} `:"you ran ",$;if(d>=2)$=`across ${d} AI tools`;else{let T=l.perAdapter.find(F=>F.isReal);$=`in ${T?fc(T.name):"Claude Code"}`}s.push(`${z}${W}${$}.`),s.push("")}if(c&&c.events>0){s.length>0&&(s.length=0);let I=WD(),R=n?.cwd??process.cwd(),M=n?.now??Date.now(),z=n?.locale??I.locale,W=n?.tz??I.tz;return s.push(...KD({conversation:c,lifetime:i,multiAdapter:l,realBytes:u,cwd:R,locale:z,tz:W,now:M,version:e,latestVersion:r})),s.join(`
|
|
583
583
|
`)}let f=t.savings.kept_out+(t.cache?t.cache.bytes_saved:0),h=t.savings.total_bytes_returned,p=t.savings.total_calls,m=f+h,g=m>0?f/m*100:0,y=Math.round(f/4),b=h>0?Math.max(1,Math.round(m/Math.max(h,1))):0;if(f===0){s.push(`context-mode ${o} ${p} calls`),s.push(""),p===0?s.push("No tool calls yet. Use batch_execute or execute to start saving tokens."):s.push(`${Ve(h)} entered context | 0 tokens saved`),s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:0})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(0,i)),s.push("");let I=e?`v${e}`:"context-mode";return s.push(I),e&&r&&r!=="unknown"&&bS(r,e)&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
|
|
584
584
|
`)}s.push(`${qt(y)} tokens saved \xB7 ${g.toFixed(1)}% reduction \xB7 ${o} \xB7 ~${mc(y)} saved (Opus)`),s.push(""),s.push(`Without context-mode |${Ur(m,m)}| ${Ve(m)}`),s.push(`With context-mode |${Ur(h,m)}| ${Ve(h)}`),s.push(""),b>=2?s.push(`${Ve(f)} kept out of your conversation \u2014 ${b}\xD7 longer sessions before compact.`):s.push(`${Ve(f)} kept out of your conversation. Never entered context.`),s.push("");let _=[`${p} calls`];t.cache&&t.cache.hits>0&&_.push(`${t.cache.hits} cache hits (+${Ve(t.cache.bytes_saved)})`),s.push(_.join(" \xB7 "));let x=t.savings.by_tool.filter(I=>I.calls>0);if(x.length>=2){s.push("");let I=x.map(R=>{let M=R.context_kb*1024,z=g<100?M/(1-g/100):M,W=Math.max(0,z-M);return{...R,returnedBytes:M,estimatedSaved:W}}).sort((R,M)=>M.estimatedSaved-R.estimatedSaved);for(let R of I){let M=R.tool.length>22?R.tool.slice(0,19)+"...":R.tool;s.push(` ${M.padEnd(22)} ${String(R.calls).padStart(4)} calls ${Ve(R.estimatedSaved).padStart(8)} saved`)}}if(a&&a.length>0){let I=a.filter(R=>R.median_concurrency!=null&&(R.max_concurrency??1)>1);if(I.length>0){s.push(""),s.push("Parallel I/O \u2713 one call did the work of many \u2014 faster runs, lower bill, same answer.");for(let R of I){let M=R.tool_name.replace(/^mcp__.*?__/,"");s.push(` ${M.padEnd(22)} ${R.calls} batches \xB7 ${R.median_concurrency} typical, ${R.max_concurrency} peak`)}}}s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:y})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(y,i)),s.push("");let w=e?`v${e}`:"context-mode";return s.push(w),e&&r&&r!=="unknown"&&r!==e&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
|
|
585
|
-
`)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.
|
|
585
|
+
`)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.3.0";if(t!==void 0&&t.trim().length>0)return t.trim();for(let e of["../../package.json","../package.json","./package.json"]){let r=rt(xc,e);if(Ie(r))try{return JSON.parse(oi(r,"utf8")).version}catch{}}return"unknown"})(),Ic=process.env.CONTEXT_MODE_UPSTREAM_CHECK==="1";function LS(){return Ie(rt(xc,"package.json"))?xc:Os(xc)}function cz(t){try{let e=process.platform==="win32"?kc("cmd.exe",["/d","/s","/c","codex plugin list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3}):kc("codex",["plugin","list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3});if(e.status!==0)return t;let r=Lf(String(e.stdout));if(r&&Ie(rt(r,".codex-plugin","hooks.json")))return r}catch{}return t}function jS(t){let e=LS();return t==="codex"?cz(e):e}process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&(process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
586
586
|
`)}),process.on("uncaughtException",t=>{try{YD(2,`[context-mode] uncaughtException: ${t?.message??t}
|
|
587
587
|
`)}finally{process.exit(1)}}));var An=Va(),Sc=Hv(An),Se=new Ua({name:"context-mode",version:sr}),uz=[];function lz(t={}){if((t.embedded??process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS)==="1")return!1;let r=t.platform??mt().platform;if(r!=="opencode"&&r!=="kilo")return!1;let n=t.settings??dz(r);return pz(n)&&fz(n)}function dz(t){let e=t==="kilo"?"kilo":"opencode",r=[rt(`${e}.json`),rt(`${e}.jsonc`),rt(`.${e}`,`${e}.json`),rt(`.${e}`,`${e}.jsonc`),gt(Is(),".config",e,`${e}.json`),gt(Is(),".config",e,`${e}.jsonc`)];for(let n of r)try{if(!Ie(n))continue;return JSON.parse(uS(oi(n,"utf8")))}catch{}return null}function pz(t){let e=t?.plugin;return Array.isArray(e)&&e.some(r=>typeof r=="string"&&r.includes("context-mode"))}function fz(t){let e=t?.mcp;return!!(e&&typeof e=="object"&&!Array.isArray(e)&&Object.prototype.hasOwnProperty.call(e,"context-mode"))}var FS=lz(),pm=!1;function mz(t={}){if(pm)return;pm=!0;let e=t.write??(n=>{process.stderr.write(n)}),r=t.platform??"opencode/kilo";e(`[context-mode] ctx_* tools/list intentionally empty on this MCP child: legacy mcp.context-mode block coexists with plugin: ["context-mode"] in ${r}.json \u2014 plugin-native tools are the supported path (#623). Run \`context-mode upgrade\` to remove the legacy block (preserves other MCP servers).
|
|
588
588
|
`)}function ZB(){pm=!1}function hz(t=Se){t.server.registerCapabilities({tools:{listChanged:!1}}),t.server.setRequestHandler(mn,async()=>({tools:[]}))}var gz=Se.registerTool.bind(Se);Se.registerTool=(...t)=>{let[e,r,n]=t;if(FS){mz();return}let s=yz(e,n);return uz.push({name:e,config:r,handler:s}),t[2]=s,gz(...t)};function yz(t,e){return async r=>{$x();try{return await e(r)}catch(n){let s=Ez(n);if(s)try{return V(t,s)}catch(o){if(o instanceof Zr)return s;throw o}throw n}finally{Rx()}}}FS&&process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&hz(Se);var _m=new az;async function UB(t,e){let r=typeof t=="string"?{projectDir:t}:t;return _m.run(r,e)}Se.server.registerCapabilities({prompts:{listChanged:!1},resources:{listChanged:!1}});Se.server.setRequestHandler(Yn,async()=>({prompts:[]}));Se.server.setRequestHandler(Jn,async()=>({resources:[]}));Se.server.setRequestHandler(Xn,async()=>({resourceTemplates:[]}));function fm(t){if(Array.isArray(t))return t.map(fm);if(t===null||typeof t!="object")return t;let e={};for(let[r,n]of Object.entries(t))if(r!=="additionalProperties"){if(r==="const"){e.enum=[n];continue}e[r]=fm(n)}return e}function _z(t=Se){try{let r=t.server._requestHandlers?.get("tools/list");if(typeof r!="function")return;t.server.setRequestHandler(mn,async(n,s)=>{let o=await r(n,s);if(o&&Array.isArray(o.tools)){for(let i of o.tools)if(!(!i||i.inputSchema==null))try{i.inputSchema=fm(i.inputSchema)}catch{}}return o})}catch{}}var ii=new qo({runtimes:An,projectRoot:()=>ht()}),Oc=gt(ym(),`cm-fs-preload-${process.pid}.js`);Ec(Oc,`(function(){var __cm_fs=0;process.on('exit',function(){if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch(e){}});try{var f=require('fs');var ors=f.readFileSync;f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};}catch(e){}})();
|