@cortexkit/opencode-magic-context 0.42.0 → 0.42.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/project-security.d.ts +3 -2
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +20 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +8 -4
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/expire-memories.d.ts +15 -0
- package/dist/features/magic-context/dreamer/expire-memories.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/module-apply.d.ts +4 -2
- package/dist/features/magic-context/dreamer/module-apply.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-gates.d.ts +6 -1
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-model-match.d.ts +41 -0
- package/dist/features/magic-context/memory/embedding-model-match.d.ts.map +1 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts +7 -20
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -2
- package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-synapse.d.ts +5 -4
- package/dist/features/magic-context/memory/embedding-synapse.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +4 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +4 -6
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/embed-session-state.d.ts +1 -1
- package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts +2 -0
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/format-embed-failure.d.ts +2 -1
- package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +2 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/maintenance-authority.d.ts +2 -2
- package/dist/hooks/magic-context/maintenance-authority.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +1 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/{index-c9hamh36.js → index-953x7a79.js} +564 -143
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +579 -217
- package/dist/plugin/dream-timer-module-client.d.ts +2 -2
- package/dist/plugin/dream-timer-module-client.d.ts.map +1 -1
- package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -1
- package/dist/plugin/embedding-routing.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-3atsasjy.js → recomp-orchestrator-5g8f5rhy.js} +1 -1
- package/dist/shared/rpc-types.d.ts +10 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/status-detail-text.d.ts +3 -1
- package/dist/shared/status-detail-text.d.ts.map +1 -1
- package/dist/shared/status-summary.d.ts +30 -0
- package/dist/shared/status-summary.d.ts.map +1 -0
- package/dist/shared/tail-hygiene-status.d.ts +1 -0
- package/dist/shared/tail-hygiene-status.d.ts.map +1 -1
- package/dist/shared/transcript.d.ts +2 -1
- package/dist/shared/transcript.d.ts.map +1 -1
- package/dist/shared/user-facing-codes.d.ts +191 -0
- package/dist/shared/user-facing-codes.d.ts.map +1 -0
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config/project-security.test.ts +10 -2
- package/src/config/project-security.ts +12 -5
- package/src/config/schema/magic-context.test.ts +29 -0
- package/src/config/schema/magic-context.ts +24 -0
- package/src/shared/rpc-types.ts +10 -0
- package/src/shared/status-detail-text.test.ts +93 -24
- package/src/shared/status-detail-text.ts +9 -2
- package/src/shared/status-summary.ts +167 -0
- package/src/shared/tag-transcript.ts +14 -7
- package/src/shared/tail-hygiene-status.ts +8 -0
- package/src/shared/transcript.ts +2 -1
- package/src/shared/user-facing-codes.test.ts +140 -0
- package/src/shared/user-facing-codes.ts +276 -0
- package/src/tui/index.tsx +41 -8
- package/src/tui-compiled/index.tsx +660 -647
package/src/shared/rpc-types.ts
CHANGED
|
@@ -14,6 +14,8 @@ import type { LoggerDiagnostics } from "./logger";
|
|
|
14
14
|
export interface TailHygieneStatus {
|
|
15
15
|
/** Tokens in active, non-protected tail content that the agent can reclaim. */
|
|
16
16
|
u: number;
|
|
17
|
+
/** Spent tool outputs contributing positive tokens to U in this baseline. */
|
|
18
|
+
reclaimableToolOutputCount?: number;
|
|
17
19
|
/** Tokens in rendered-tail content eligible for hygiene accounting in the same scan. */
|
|
18
20
|
t: number;
|
|
19
21
|
/** Reclaimable-to-eligible token ratio, clamped to 0–1 and shared by both nudge mechanisms. */
|
|
@@ -55,6 +57,8 @@ export interface SidebarSnapshot {
|
|
|
55
57
|
cacheTtl: string;
|
|
56
58
|
/** Persistent runtime failure shown directly in the sidebar when non-null. */
|
|
57
59
|
lastTransformError: string | null;
|
|
60
|
+
/** Durable history-compression failure count used to select the stable summary warning. */
|
|
61
|
+
historianFailureCount?: number;
|
|
58
62
|
lastDreamerRunAt: number | null;
|
|
59
63
|
projectIdentity: string | null;
|
|
60
64
|
compartmentTokens: number;
|
|
@@ -224,6 +228,12 @@ export interface StatusDetail extends SidebarSnapshot {
|
|
|
224
228
|
* (the plugin owns it), so this surface supplies the live DB value; the module
|
|
225
229
|
* surface supplies the module-store value instead.
|
|
226
230
|
*/
|
|
231
|
+
/** Search-indexing coverage and current command state for the default status summary. */
|
|
232
|
+
embedding?: {
|
|
233
|
+
state: "off" | "running" | "paused" | "stopped" | "ready" | "waiting";
|
|
234
|
+
indexed: number;
|
|
235
|
+
total: number;
|
|
236
|
+
};
|
|
227
237
|
storage_versions: {
|
|
228
238
|
/**
|
|
229
239
|
* Persisted schema version of context.db (MAX of schema_migrations).
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { formatOpenCodeDbMissingStatusLine, resolveOpenCodeDbPath } from "./opencode-db-path";
|
|
3
2
|
import type { StatusDetail } from "./rpc-types";
|
|
4
|
-
import { formatStatusDetailMarkdown } from "./status-detail-text";
|
|
3
|
+
import { formatStatusDetailMarkdown, formatStatusDiagnosticsMarkdown } from "./status-detail-text";
|
|
5
4
|
|
|
6
5
|
const STATUS_FIXTURE: StatusDetail = {
|
|
7
6
|
sessionId: "ses_status",
|
|
@@ -18,9 +17,11 @@ const STATUS_FIXTURE: StatusDetail = {
|
|
|
18
17
|
compartmentInProgress: true,
|
|
19
18
|
sessionNoteCount: 1,
|
|
20
19
|
readySmartNoteCount: 2,
|
|
21
|
-
cacheTtl: "
|
|
22
|
-
cacheTtlSource: "
|
|
20
|
+
cacheTtl: "1h",
|
|
21
|
+
cacheTtlSource: "config",
|
|
22
|
+
cacheTtlModelKey: "anthropic/claude-opus-5",
|
|
23
23
|
lastTransformError: null,
|
|
24
|
+
historianFailureCount: 0,
|
|
24
25
|
lastDreamerRunAt: null,
|
|
25
26
|
projectIdentity: "/repo",
|
|
26
27
|
compartmentTokens: 22_000,
|
|
@@ -62,38 +63,106 @@ const STATUS_FIXTURE: StatusDetail = {
|
|
|
62
63
|
lastErrorMessage: null,
|
|
63
64
|
lastErrorTime: null,
|
|
64
65
|
},
|
|
66
|
+
tailHygiene: {
|
|
67
|
+
u: 14_400,
|
|
68
|
+
t: 48_000,
|
|
69
|
+
severity: 0.3,
|
|
70
|
+
evaluable: true,
|
|
71
|
+
generationInvalidated: false,
|
|
72
|
+
baselineGeneration: 3,
|
|
73
|
+
computedAt: 1_730_000_000_000,
|
|
74
|
+
reclaimableToolOutputCount: 3,
|
|
75
|
+
},
|
|
76
|
+
embedding: {
|
|
77
|
+
state: "running",
|
|
78
|
+
indexed: 9,
|
|
79
|
+
total: 12,
|
|
80
|
+
},
|
|
65
81
|
storage_versions: {
|
|
66
82
|
context_db_schema_version: 79,
|
|
67
83
|
plugin_supported_version: 79,
|
|
68
84
|
},
|
|
69
85
|
};
|
|
70
86
|
|
|
71
|
-
describe("
|
|
72
|
-
test("renders
|
|
73
|
-
expect(formatStatusDetailMarkdown(STATUS_FIXTURE)).toBe(
|
|
74
|
-
`${formatOpenCodeDbMissingStatusLine(resolveOpenCodeDbPath())}\n\n## Magic Context Status
|
|
87
|
+
describe("status detail text", () => {
|
|
88
|
+
test("renders the OpenCode summary golden", () => {
|
|
89
|
+
expect(formatStatusDetailMarkdown(STATUS_FIXTURE)).toBe(`## Magic Context Status
|
|
75
90
|
|
|
76
|
-
- **
|
|
77
|
-
- **
|
|
78
|
-
- **
|
|
79
|
-
- **
|
|
80
|
-
- **
|
|
81
|
-
- **Memory:** 8
|
|
82
|
-
- **
|
|
83
|
-
- **Execute threshold:** 65.0%`,
|
|
84
|
-
);
|
|
91
|
+
- **Context:** 75.0% of usable context (96,000 / 128,000 tokens)
|
|
92
|
+
- **Cache lifetime:** 1h (config for anthropic/claude-opus-5)
|
|
93
|
+
- **Automatic compression:** at 65.0% of usable context
|
|
94
|
+
- **History compression:** Compressing history · 12 history blocks
|
|
95
|
+
- **Reclaimable:** 3 spent tool outputs (~14k tokens)
|
|
96
|
+
- **Memory:** 8 memories · 3 notes
|
|
97
|
+
- **Search indexing:** Running · 9 / 12 history blocks indexed`);
|
|
85
98
|
});
|
|
86
99
|
|
|
87
|
-
test("
|
|
88
|
-
const
|
|
100
|
+
test("renders disabled automatic compression and an empty reclaimable set without a gauge", () => {
|
|
101
|
+
const rendered = formatStatusDetailMarkdown({
|
|
102
|
+
...STATUS_FIXTURE,
|
|
103
|
+
compaction_enabled: false,
|
|
104
|
+
tailHygiene: {
|
|
105
|
+
...STATUS_FIXTURE.tailHygiene!,
|
|
106
|
+
u: 0,
|
|
107
|
+
reclaimableToolOutputCount: 0,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
expect(rendered).toContain("- **Automatic compression:** Off");
|
|
111
|
+
expect(rendered).toContain("- **Reclaimable:** none");
|
|
112
|
+
expect(rendered).not.toContain("Reclaimable: 0");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("renders summary and diagnostics from one snapshot with matching status values", () => {
|
|
116
|
+
const summary = formatStatusDetailMarkdown(STATUS_FIXTURE);
|
|
117
|
+
const diagnostics = formatStatusDiagnosticsMarkdown(STATUS_FIXTURE);
|
|
118
|
+
for (const value of ["75.0%", "65.0%", "1h (config for anthropic/claude-opus-5)"]) {
|
|
119
|
+
expect(summary).toContain(value);
|
|
120
|
+
expect(diagnostics).toContain(value);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("keeps the previous OpenCode detail behind diagnostics", () => {
|
|
125
|
+
const diagnostics = formatStatusDiagnosticsMarkdown(STATUS_FIXTURE);
|
|
126
|
+
expect(diagnostics).toContain("- **Active profile:** work");
|
|
127
|
+
expect(diagnostics).toContain("- **Tags:** 4 active, 1 dropped; 2 pending drops");
|
|
128
|
+
expect(diagnostics).toContain("- **Execute threshold:** 65.0%");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("keeps internal vocabulary and identifiers out of the summary", () => {
|
|
132
|
+
const summary = formatStatusDetailMarkdown({
|
|
89
133
|
...STATUS_FIXTURE,
|
|
134
|
+
sessionId: "session-secret",
|
|
135
|
+
projectIdentity: "/Users/example/secret-project",
|
|
90
136
|
hostBackendsModuleSide: true,
|
|
137
|
+
lastTransformError: "facade MODULE drain failed in mc-store /tmp/private",
|
|
138
|
+
historianFailureCount: 2,
|
|
91
139
|
});
|
|
92
|
-
const
|
|
140
|
+
for (const forbidden of [
|
|
141
|
+
"session-secret",
|
|
142
|
+
"/Users/",
|
|
143
|
+
"tag counter",
|
|
144
|
+
"protection",
|
|
145
|
+
"formula",
|
|
146
|
+
"MODULE",
|
|
147
|
+
"mc-store",
|
|
148
|
+
"harness",
|
|
149
|
+
"compartment",
|
|
150
|
+
"facade",
|
|
151
|
+
"changefeed",
|
|
152
|
+
"drain",
|
|
153
|
+
]) {
|
|
154
|
+
expect(summary.toLowerCase()).not.toContain(forbidden.toLowerCase());
|
|
155
|
+
}
|
|
156
|
+
expect(summary).toContain("(MC-S02)");
|
|
157
|
+
expect(summary).toContain("(MC-H01)");
|
|
158
|
+
});
|
|
93
159
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
160
|
+
test("does not expose module routing in the summary", () => {
|
|
161
|
+
const rustStatus = formatStatusDetailMarkdown({
|
|
162
|
+
...STATUS_FIXTURE,
|
|
163
|
+
hostBackendsModuleSide: true,
|
|
164
|
+
});
|
|
165
|
+
expect(rustStatus).not.toContain("MODULE");
|
|
166
|
+
expect(rustStatus).not.toContain("mc-store");
|
|
98
167
|
});
|
|
99
168
|
});
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from "./opencode-db-path";
|
|
10
10
|
import type { StatusDetail } from "./rpc-types";
|
|
11
11
|
import { RUST_MODE_HOST_PATHS_LINE } from "./rust-mode-status";
|
|
12
|
+
import { renderUserStatusSummary, statusSummaryFromDetail } from "./status-summary";
|
|
13
|
+
import { renderUserFacingFailure } from "./user-facing-codes";
|
|
12
14
|
|
|
13
15
|
function formatCount(value: number): string {
|
|
14
16
|
return Math.round(value).toLocaleString();
|
|
@@ -21,8 +23,13 @@ function formatCacheLane(detail: StatusDetail): string {
|
|
|
21
23
|
return `live (${Math.round(detail.cacheRemainingMs / 1000)}s remaining); TTL ${detail.cacheTtl}`;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/** Render the
|
|
26
|
+
/** Render the default user summary for chat-only OpenCode clients. */
|
|
25
27
|
export function formatStatusDetailMarkdown(detail: StatusDetail): string {
|
|
28
|
+
return renderUserStatusSummary(statusSummaryFromDetail(detail), "markdown");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Render the opt-in operator detail that the status dialog exposes behind Diagnostics. */
|
|
32
|
+
export function formatStatusDiagnosticsMarkdown(detail: StatusDetail): string {
|
|
26
33
|
const usableLimit =
|
|
27
34
|
detail.contextLimit > 0
|
|
28
35
|
? `${formatCount(detail.contextLimit)} usable tokens`
|
|
@@ -71,7 +78,7 @@ export function formatStatusDetailMarkdown(detail: StatusDetail): string {
|
|
|
71
78
|
);
|
|
72
79
|
}
|
|
73
80
|
if (detail.lastTransformError) {
|
|
74
|
-
lines.push(`- **
|
|
81
|
+
lines.push(`- **Warning:** ${renderUserFacingFailure("transform_update_failed")}`);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
return lines.join("\n");
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { formatCacheTtlDisplay } from "./cache-ttl-display";
|
|
2
|
+
import type { StatusDetail } from "./rpc-types";
|
|
3
|
+
import { renderUserFacingFailure, type UserFacingFailureKey } from "./user-facing-codes";
|
|
4
|
+
|
|
5
|
+
export type StatusCompressionState = "off" | "compressing" | "ready" | "waiting";
|
|
6
|
+
export type StatusEmbeddingState = "off" | "running" | "paused" | "stopped" | "ready" | "waiting";
|
|
7
|
+
|
|
8
|
+
export interface UserStatusSummary {
|
|
9
|
+
inputTokens: number;
|
|
10
|
+
usableContextTokens: number;
|
|
11
|
+
usagePercentage: number;
|
|
12
|
+
cacheLifetime: string;
|
|
13
|
+
automaticCompressionThreshold: number | null;
|
|
14
|
+
compression: {
|
|
15
|
+
state: StatusCompressionState;
|
|
16
|
+
historyBlockCount: number;
|
|
17
|
+
};
|
|
18
|
+
reclaimable: {
|
|
19
|
+
toolOutputCount: number;
|
|
20
|
+
tokens: number;
|
|
21
|
+
};
|
|
22
|
+
memoryCount: number;
|
|
23
|
+
noteCount: number;
|
|
24
|
+
embedding: {
|
|
25
|
+
state: StatusEmbeddingState;
|
|
26
|
+
indexed: number;
|
|
27
|
+
total: number;
|
|
28
|
+
};
|
|
29
|
+
warnings: UserFacingFailureKey[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function statusSummaryFromDetail(detail: StatusDetail): UserStatusSummary {
|
|
33
|
+
const compressionState: StatusCompressionState =
|
|
34
|
+
detail.compaction_enabled === false
|
|
35
|
+
? "off"
|
|
36
|
+
: detail.historianRunning ||
|
|
37
|
+
detail.compartmentInProgress ||
|
|
38
|
+
(detail.recompProgress?.phase === "recomp" &&
|
|
39
|
+
detail.recompProgress.kind !== "embed")
|
|
40
|
+
? "compressing"
|
|
41
|
+
: detail.compartmentCount > 0
|
|
42
|
+
? "ready"
|
|
43
|
+
: "waiting";
|
|
44
|
+
const warnings: UserFacingFailureKey[] = [];
|
|
45
|
+
if (detail.lastTransformError) warnings.push("transform_update_failed");
|
|
46
|
+
if ((detail.historianFailureCount ?? 0) > 0) warnings.push("historian_unavailable");
|
|
47
|
+
if ((detail.configParseFailures?.length ?? 0) > 0) warnings.push("configuration_warning");
|
|
48
|
+
if (detail.embedding?.state === "stopped") warnings.push("embedding_unavailable");
|
|
49
|
+
if ((detail.loggerDiagnostics?.swallowedWriteCount ?? 0) > 0) {
|
|
50
|
+
warnings.push("status_log_unavailable");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
inputTokens: detail.inputTokens,
|
|
55
|
+
usableContextTokens: detail.contextLimit,
|
|
56
|
+
usagePercentage: detail.usagePercentage,
|
|
57
|
+
cacheLifetime: formatCacheTtlDisplay({
|
|
58
|
+
value: detail.cacheTtl,
|
|
59
|
+
source: detail.cacheTtlSource ?? "session",
|
|
60
|
+
modelKey: detail.cacheTtlModelKey,
|
|
61
|
+
}).replace(/^Cache TTL:\s*/, ""),
|
|
62
|
+
automaticCompressionThreshold:
|
|
63
|
+
detail.compaction_enabled === false ? null : detail.executeThreshold,
|
|
64
|
+
compression: {
|
|
65
|
+
state: compressionState,
|
|
66
|
+
historyBlockCount: detail.compartmentCount,
|
|
67
|
+
},
|
|
68
|
+
reclaimable: {
|
|
69
|
+
toolOutputCount: detail.tailHygiene?.reclaimableToolOutputCount ?? 0,
|
|
70
|
+
tokens: detail.tailHygiene?.u ?? 0,
|
|
71
|
+
},
|
|
72
|
+
memoryCount: detail.memoryCount,
|
|
73
|
+
noteCount: (detail.sessionNoteCount ?? 0) + (detail.readySmartNoteCount ?? 0),
|
|
74
|
+
embedding: detail.embedding ?? {
|
|
75
|
+
state: "waiting",
|
|
76
|
+
indexed: 0,
|
|
77
|
+
total: 0,
|
|
78
|
+
},
|
|
79
|
+
warnings: [...new Set(warnings)],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatCount(value: number): string {
|
|
84
|
+
return Math.round(value).toLocaleString();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compressionText(summary: UserStatusSummary): string {
|
|
88
|
+
const historyBlocks = `${summary.compression.historyBlockCount} history block${
|
|
89
|
+
summary.compression.historyBlockCount === 1 ? "" : "s"
|
|
90
|
+
}`;
|
|
91
|
+
switch (summary.compression.state) {
|
|
92
|
+
case "off":
|
|
93
|
+
return "Off";
|
|
94
|
+
case "compressing":
|
|
95
|
+
return `Compressing history · ${historyBlocks}`;
|
|
96
|
+
case "ready":
|
|
97
|
+
return `Ready · ${historyBlocks}`;
|
|
98
|
+
case "waiting":
|
|
99
|
+
return "Waiting for enough conversation history";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function reclaimableText(summary: UserStatusSummary): string {
|
|
104
|
+
const count = Math.max(0, Math.floor(summary.reclaimable.toolOutputCount));
|
|
105
|
+
if (count === 0) return "none";
|
|
106
|
+
const outputs = `${count} spent tool output${count === 1 ? "" : "s"}`;
|
|
107
|
+
return `${outputs} (~${Math.round(Math.max(0, summary.reclaimable.tokens) / 1000)}k tokens)`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function embeddingText(summary: UserStatusSummary): string {
|
|
111
|
+
if (summary.embedding.state === "off") return "Off";
|
|
112
|
+
const coverage = `${formatCount(summary.embedding.indexed)} / ${formatCount(summary.embedding.total)} history blocks indexed`;
|
|
113
|
+
switch (summary.embedding.state) {
|
|
114
|
+
case "running":
|
|
115
|
+
return `Running · ${coverage}`;
|
|
116
|
+
case "paused":
|
|
117
|
+
return `Paused · ${coverage}`;
|
|
118
|
+
case "stopped":
|
|
119
|
+
return `Paused after an issue · ${coverage}`;
|
|
120
|
+
case "ready":
|
|
121
|
+
return `Ready · ${coverage}`;
|
|
122
|
+
case "waiting":
|
|
123
|
+
return `Waiting · ${coverage}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function renderUserStatusSummary(
|
|
128
|
+
summary: UserStatusSummary,
|
|
129
|
+
style: "markdown" | "plain",
|
|
130
|
+
): string {
|
|
131
|
+
const context = `${summary.usagePercentage.toFixed(1)}% of usable context (${formatCount(summary.inputTokens)} / ${
|
|
132
|
+
summary.usableContextTokens > 0 ? formatCount(summary.usableContextTokens) : "?"
|
|
133
|
+
} tokens)`;
|
|
134
|
+
const values = [
|
|
135
|
+
["Context", context],
|
|
136
|
+
["Cache lifetime", summary.cacheLifetime],
|
|
137
|
+
[
|
|
138
|
+
"Automatic compression",
|
|
139
|
+
summary.automaticCompressionThreshold === null
|
|
140
|
+
? "Off"
|
|
141
|
+
: `at ${summary.automaticCompressionThreshold.toFixed(1)}% of usable context`,
|
|
142
|
+
],
|
|
143
|
+
["History compression", compressionText(summary)],
|
|
144
|
+
["Reclaimable", reclaimableText(summary)],
|
|
145
|
+
[
|
|
146
|
+
"Memory",
|
|
147
|
+
`${formatCount(summary.memoryCount)} memories · ${formatCount(summary.noteCount)} notes`,
|
|
148
|
+
],
|
|
149
|
+
["Search indexing", embeddingText(summary)],
|
|
150
|
+
] as const;
|
|
151
|
+
const lines =
|
|
152
|
+
style === "markdown"
|
|
153
|
+
? [
|
|
154
|
+
"## Magic Context Status",
|
|
155
|
+
"",
|
|
156
|
+
...values.map(([label, value]) => `- **${label}:** ${value}`),
|
|
157
|
+
]
|
|
158
|
+
: ["Magic Context Status", ...values.map(([label, value]) => `${label}: ${value}`)];
|
|
159
|
+
for (const warning of summary.warnings) {
|
|
160
|
+
lines.push(
|
|
161
|
+
style === "markdown"
|
|
162
|
+
? `- **Warning:** ${renderUserFacingFailure(warning)}`
|
|
163
|
+
: `Warning: ${renderUserFacingFailure(warning)}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
@@ -1063,13 +1063,20 @@ function buildAggregateTarget(
|
|
|
1063
1063
|
occurrences.some((occ) => occ.kind === "tool_use") &&
|
|
1064
1064
|
occurrences.some((occ) => occ.kind === "tool_result");
|
|
1065
1065
|
if (!complete) return "incomplete";
|
|
1066
|
-
if (
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1066
|
+
if (!requiresToolArcSkeleton) {
|
|
1067
|
+
const authorization = occurrences.map((occ) => occ.part.canRemove?.());
|
|
1068
|
+
// A failed durable marker is not permission to substitute a new
|
|
1069
|
+
// skeleton. Keep both halves unchanged and leave the drop queued.
|
|
1070
|
+
if (authorization.includes("defer")) return "incomplete";
|
|
1071
|
+
if (
|
|
1072
|
+
occurrences.every(
|
|
1073
|
+
(occ, index) => occ.part.remove && authorization[index] !== false,
|
|
1074
|
+
)
|
|
1075
|
+
) {
|
|
1076
|
+
let removed = false;
|
|
1077
|
+
for (const occ of occurrences) removed = occ.part.remove?.() || removed;
|
|
1078
|
+
return removed ? "removed" : "absent";
|
|
1079
|
+
}
|
|
1073
1080
|
}
|
|
1074
1081
|
// Adapters without structural removal retain paired sentinels.
|
|
1075
1082
|
const sentinel = `[dropped \u00a7${tagId}\u00a7]`;
|
|
@@ -9,6 +9,7 @@ export interface WireTailHygieneBaseline {
|
|
|
9
9
|
generation_invalidated?: boolean;
|
|
10
10
|
baseline_generation?: number;
|
|
11
11
|
computed_at_ms?: number;
|
|
12
|
+
reclaimable_tool_output_count?: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function finiteNumber(value: unknown, fallback = 0): number {
|
|
@@ -34,6 +35,10 @@ export function resolveTailHygieneStatus(
|
|
|
34
35
|
generationInvalidated: rustBaseline.generation_invalidated === true,
|
|
35
36
|
baselineGeneration: Math.max(0, finiteNumber(rustBaseline.baseline_generation)),
|
|
36
37
|
computedAt: Math.max(0, finiteNumber(rustBaseline.computed_at_ms)),
|
|
38
|
+
reclaimableToolOutputCount: Math.max(
|
|
39
|
+
0,
|
|
40
|
+
Math.floor(finiteNumber(rustBaseline.reclaimable_tool_output_count)),
|
|
41
|
+
),
|
|
37
42
|
};
|
|
38
43
|
}
|
|
39
44
|
if (tsBaseline === undefined) return undefined;
|
|
@@ -47,6 +52,9 @@ export function resolveTailHygieneStatus(
|
|
|
47
52
|
generationInvalidated: tsBaseline.generationInvalidated,
|
|
48
53
|
baselineGeneration: Math.max(0, tsBaseline.baselineGeneration),
|
|
49
54
|
computedAt: Math.max(0, tsBaseline.computedAt),
|
|
55
|
+
reclaimableToolOutputCount: tsBaseline.baselineParts.filter(
|
|
56
|
+
(part) => part.kind === "toolOutput" && part.uTokens > 0,
|
|
57
|
+
).length,
|
|
50
58
|
};
|
|
51
59
|
}
|
|
52
60
|
|
package/src/shared/transcript.ts
CHANGED
|
@@ -170,7 +170,8 @@ export interface TranscriptPart {
|
|
|
170
170
|
/** Remove one half of a complete tool arc. Callers must remove both halves together. */
|
|
171
171
|
remove?(): boolean;
|
|
172
172
|
/** Refuse structural deletion when the adapter cannot match provider-native identity safely. */
|
|
173
|
-
|
|
173
|
+
/** A deferred durable decision vetoes the whole drop, including sentinel fallback. */
|
|
174
|
+
canRemove?(): boolean | "defer";
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
177
|
* Optional: serialized byte size of the part's REAL payload, including
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import type { DreamRunFailureClass } from "../features/magic-context/dreamer/storage-dream-runs";
|
|
5
|
+
import type { EmbeddingFailureClass } from "../features/magic-context/memory/embedding-failure";
|
|
6
|
+
import {
|
|
7
|
+
type CapabilityRefusal,
|
|
8
|
+
dreamFailureCode,
|
|
9
|
+
embeddingFailureCode,
|
|
10
|
+
renderCapabilityRefusal,
|
|
11
|
+
renderDreamFailure,
|
|
12
|
+
renderEmbeddingFailure,
|
|
13
|
+
renderUserFacingFailure,
|
|
14
|
+
USER_FACING_FAILURES,
|
|
15
|
+
} from "./user-facing-codes";
|
|
16
|
+
|
|
17
|
+
const DREAM_CLASSES: DreamRunFailureClass[] = [
|
|
18
|
+
"provider_timeout",
|
|
19
|
+
"provider_error",
|
|
20
|
+
"empty_completion",
|
|
21
|
+
"no_models",
|
|
22
|
+
"child_aborted",
|
|
23
|
+
"parse_failed",
|
|
24
|
+
"unknown",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const EMBEDDING_CLASSES: EmbeddingFailureClass[] = [
|
|
28
|
+
"substitution_rejected",
|
|
29
|
+
"http_error",
|
|
30
|
+
"transport_error",
|
|
31
|
+
"invalid_envelope",
|
|
32
|
+
"empty_result",
|
|
33
|
+
"certification_refusal",
|
|
34
|
+
"credential_required",
|
|
35
|
+
"local_binding_missing",
|
|
36
|
+
"local_fs_unavailable",
|
|
37
|
+
"local_download_failure",
|
|
38
|
+
"local_runtime_error",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
describe("user-facing failure codes", () => {
|
|
42
|
+
test("keeps the closed table unique and renders one calm sentence plus one action", () => {
|
|
43
|
+
const entries = Object.values(USER_FACING_FAILURES);
|
|
44
|
+
expect(new Set(entries.map((entry) => entry.code)).size).toBe(entries.length);
|
|
45
|
+
for (const [key, entry] of Object.entries(USER_FACING_FAILURES)) {
|
|
46
|
+
expect(entry.code).toMatch(/^MC-[CHDERS]\d{2}$/);
|
|
47
|
+
expect(entry.sentence).toEndWith(".");
|
|
48
|
+
expect(entry.action).toEndWith(".");
|
|
49
|
+
expect(renderUserFacingFailure(key as keyof typeof USER_FACING_FAILURES)).toBe(
|
|
50
|
+
`${entry.sentence} ${entry.action} (${entry.code})`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("uses actionable command and configuration guidance in markdown and plain text", () => {
|
|
56
|
+
expect(renderEmbeddingFailure("local_binding_missing")).toContain(
|
|
57
|
+
"Run `npx @cortexkit/magic-context doctor`, then retry. (MC-E08)",
|
|
58
|
+
);
|
|
59
|
+
expect(renderEmbeddingFailure("local_runtime_error", "plain")).toContain(
|
|
60
|
+
"Run npx @cortexkit/magic-context doctor, then retry. (MC-E11)",
|
|
61
|
+
);
|
|
62
|
+
expect(renderEmbeddingFailure("local_runtime_error", "plain")).not.toContain("`");
|
|
63
|
+
expect(renderEmbeddingFailure("certification_refusal")).toContain(
|
|
64
|
+
"Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again. (MC-E06)",
|
|
65
|
+
);
|
|
66
|
+
expect(renderUserFacingFailure("configuration_warning")).toContain(
|
|
67
|
+
"Fix the configuration warning shown in /ctx-status diagnostics, then restart. (MC-S03)",
|
|
68
|
+
);
|
|
69
|
+
expect(renderUserFacingFailure("session_upgrade_unavailable")).toContain(
|
|
70
|
+
"Run /ctx-recomp instead. (MC-C07)",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("maps every structured dream and embedding failure class", () => {
|
|
75
|
+
for (const failureClass of DREAM_CLASSES) {
|
|
76
|
+
expect(renderDreamFailure(failureClass)).toEndWith(
|
|
77
|
+
`(${dreamFailureCode(failureClass)})`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
for (const failureClass of EMBEDDING_CLASSES) {
|
|
81
|
+
expect(renderEmbeddingFailure(failureClass)).toEndWith(
|
|
82
|
+
`(${embeddingFailureCode(failureClass)})`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("renders every capability refusal without internal vocabulary", () => {
|
|
88
|
+
const capabilities: CapabilityRefusal[] = [
|
|
89
|
+
"memory_write",
|
|
90
|
+
"memory_access",
|
|
91
|
+
"note_change",
|
|
92
|
+
"note_access",
|
|
93
|
+
"context_cleanup",
|
|
94
|
+
"partial_history",
|
|
95
|
+
"session_upgrade",
|
|
96
|
+
"smart_note_condition",
|
|
97
|
+
"history_compression",
|
|
98
|
+
"context_service",
|
|
99
|
+
];
|
|
100
|
+
for (const capability of capabilities) {
|
|
101
|
+
const rendered = renderCapabilityRefusal(capability);
|
|
102
|
+
expect(rendered).toMatch(/\(MC-C\d{2}\)$/);
|
|
103
|
+
for (const forbidden of ["authority", "MODULE", "drain", "facade", "changefeed"]) {
|
|
104
|
+
expect(rendered).not.toContain(forbidden);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("source fence keeps raw exceptions out of user-facing reply builders", () => {
|
|
110
|
+
const builders = [
|
|
111
|
+
"hooks/magic-context/command-handler.ts",
|
|
112
|
+
"hooks/magic-context/compartment-runner-validation.ts",
|
|
113
|
+
"hooks/magic-context/compartment-runner-recomp.ts",
|
|
114
|
+
"hooks/magic-context/compartment-runner-partial-recomp.ts",
|
|
115
|
+
"hooks/magic-context/format-embed-failure.ts",
|
|
116
|
+
"hooks/magic-context/recomp-orchestrator.ts",
|
|
117
|
+
"shared/status-detail-text.ts",
|
|
118
|
+
"../../pi-plugin/src/commands/ctx-dream.ts",
|
|
119
|
+
"../../pi-plugin/src/commands/ctx-status.ts",
|
|
120
|
+
];
|
|
121
|
+
const allowedLogFragments = ["command notification delivery failed", "/ctx-dream failed"];
|
|
122
|
+
const violations: string[] = [];
|
|
123
|
+
for (const relativePath of builders) {
|
|
124
|
+
const path = resolve(import.meta.dir, "..", relativePath);
|
|
125
|
+
const lines = readFileSync(path, "utf8").split("\n");
|
|
126
|
+
lines.forEach((line, index) => {
|
|
127
|
+
if (
|
|
128
|
+
!/(error\.message|String\((?:err|error)\)|describeError\([^)]*\)\.brief)/.test(
|
|
129
|
+
line,
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (allowedLogFragments.some((fragment) => line.includes(fragment))) return;
|
|
135
|
+
violations.push(`${relativePath}:${index + 1}: ${line.trim()}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
expect(violations).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
});
|