@cortexkit/opencode-magic-context 0.42.0 → 0.42.1
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/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/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/project-embedding-registry.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.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/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/{index-c9hamh36.js → index-1q9kay7n.js} +534 -120
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +264 -108
- 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-m33b3rk9.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/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/tail-hygiene-status.ts +8 -0
- 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
|
@@ -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
|
+
}
|
|
@@ -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
|
|
|
@@ -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
|
+
});
|