@martinloop/mcp 0.3.9 → 0.5.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/README.md +82 -141
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +62 -98
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +192 -15
- package/dist/tools/doctor.js +5 -4
- package/dist/tools/get-status.d.ts +2 -0
- package/dist/tools/get-status.js +1 -0
- package/dist/tools/preflight.d.ts +2 -0
- package/dist/tools/preflight.js +43 -3
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +5 -3
- package/dist/tools/run-loop.js +15 -9
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +4 -2
- package/dist/tools/tool-support.js +6 -15
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +97 -69
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +2 -4
- package/dist/vendor/adapters/index.js +2 -4
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +80 -15
- package/dist/vendor/adapters/verifier-only.js +9 -4
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/delivery.d.ts +47 -0
- package/dist/vendor/contracts/delivery.js +18 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +55 -6
- package/dist/vendor/contracts/index.js +24 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +106 -0
- package/dist/vendor/contracts/verified-handoff.js +40 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/delivery/index.d.ts +6 -0
- package/dist/vendor/core/delivery/index.js +4 -0
- package/dist/vendor/core/delivery/message-client.d.ts +18 -0
- package/dist/vendor/core/delivery/message-client.js +38 -0
- package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
- package/dist/vendor/core/delivery/message-ledger.js +97 -0
- package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
- package/dist/vendor/core/delivery/message-schema.js +111 -0
- package/dist/vendor/core/delivery/update-check.d.ts +19 -0
- package/dist/vendor/core/delivery/update-check.js +107 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -0
- package/dist/vendor/core/grounding.d.ts +2 -1
- package/dist/vendor/core/grounding.js +40 -5
- package/dist/vendor/core/index.d.ts +47 -6
- package/dist/vendor/core/index.js +1102 -670
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +11 -8
- package/dist/vendor/core/red-blue/red-phase.d.ts +2 -1
- package/dist/vendor/core/red-blue/red-phase.js +3 -3
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +2 -4
- package/dist/vendor/core/red-blue/risk-tiers.js +1 -3
- package/dist/vendor/core/rollback.js +15 -1
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +72 -0
- package/dist/vendor/core/verified-handoff.js +238 -0
- package/package.json +4 -10
- package/server.json +3 -3
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Usage:
|
|
15
15
|
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
16
|
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
-
* MARTIN_OPENAI_MODEL
|
|
17
|
+
* MARTIN_OPENAI_MODEL=<provider-model-id>
|
|
18
18
|
* martin-loop run "fix the bug" --engine openai
|
|
19
19
|
*
|
|
20
20
|
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
@@ -69,6 +69,37 @@ function estimateCost(model, inputChars, outputChars) {
|
|
|
69
69
|
const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
|
|
70
70
|
return { tokensIn, tokensOut, actualUsd };
|
|
71
71
|
}
|
|
72
|
+
function normalizeOpenAiCompatibleUsage(input) {
|
|
73
|
+
const hasKnownPricing = KNOWN_MODEL_PRICING[input.model] !== undefined;
|
|
74
|
+
const pricing = KNOWN_MODEL_PRICING[input.model] ?? {
|
|
75
|
+
inputPer1K: FALLBACK_INPUT_PER_1K,
|
|
76
|
+
outputPer1K: FALLBACK_OUTPUT_PER_1K
|
|
77
|
+
};
|
|
78
|
+
const actualUsd = (input.tokensIn / 1000) * pricing.inputPer1K +
|
|
79
|
+
(input.tokensOut / 1000) * pricing.outputPer1K;
|
|
80
|
+
const provenance = input.usageWasFullyProviderReported && hasKnownPricing ? "calculated" : "estimated";
|
|
81
|
+
return normalizeUsage({
|
|
82
|
+
actualUsd,
|
|
83
|
+
...(provenance === "estimated" ? { estimatedUsd: actualUsd } : {}),
|
|
84
|
+
tokensIn: input.tokensIn,
|
|
85
|
+
tokensOut: input.tokensOut,
|
|
86
|
+
provenance,
|
|
87
|
+
providerSettlement: {
|
|
88
|
+
providerId: "openai-compatible",
|
|
89
|
+
model: input.model,
|
|
90
|
+
transport: "http",
|
|
91
|
+
source: input.usageWasFullyProviderReported ? "openai_compatible_json" : "estimated_fallback",
|
|
92
|
+
inputTokens: input.tokensIn,
|
|
93
|
+
outputTokens: input.tokensOut,
|
|
94
|
+
billingMode: input.billingMode,
|
|
95
|
+
modelSource: input.modelSource,
|
|
96
|
+
pricingSource: hasKnownPricing ? "static_catalog" : "blended_fallback",
|
|
97
|
+
pricingVersion: "embedded-v1",
|
|
98
|
+
rawUsageAvailable: input.usageWasFullyProviderReported,
|
|
99
|
+
settledAt: new Date().toISOString()
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
72
103
|
// ---------------------------------------------------------------------------
|
|
73
104
|
// Prompt builder
|
|
74
105
|
// ---------------------------------------------------------------------------
|
|
@@ -80,12 +111,11 @@ Follow these rules exactly:
|
|
|
80
111
|
- Be precise, minimal, and test-backed in all changes.
|
|
81
112
|
- State what you changed and why at the end of your response.`;
|
|
82
113
|
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
83
|
-
export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
84
114
|
export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
|
|
85
115
|
const apiKey = env["MARTIN_OPENAI_API_KEY"] ?? "";
|
|
86
116
|
return {
|
|
87
117
|
baseUrl: env["MARTIN_OPENAI_BASE_URL"] ?? DEFAULT_OPENAI_BASE_URL,
|
|
88
|
-
model: env["MARTIN_OPENAI_MODEL"]
|
|
118
|
+
...(env["MARTIN_OPENAI_MODEL"] ? { model: env["MARTIN_OPENAI_MODEL"] } : {}),
|
|
89
119
|
apiKey,
|
|
90
120
|
apiKeyConfigured: apiKey.length > 0,
|
|
91
121
|
authPosture: apiKey.length > 0 ? "api_key" : "anonymous_or_local"
|
|
@@ -126,7 +156,16 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
126
156
|
const runtimeConfig = resolveOpenAiCompatibleRuntimeConfig();
|
|
127
157
|
const baseUrl = (options.baseUrl ?? runtimeConfig.baseUrl).replace(/\/$/, "");
|
|
128
158
|
const model = options.model ?? runtimeConfig.model;
|
|
159
|
+
if (!model) {
|
|
160
|
+
throw new Error("MODEL_CONFIGURATION_REQUIRED: set --model or MARTIN_OPENAI_MODEL for direct OpenAI-compatible execution.");
|
|
161
|
+
}
|
|
129
162
|
const apiKey = options.apiKey ?? runtimeConfig.apiKey;
|
|
163
|
+
const modelSource = options.model ? "explicit_override" : "provider_configured";
|
|
164
|
+
const billingMode = /^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1|\[::1\])/iu.test(baseUrl)
|
|
165
|
+
? "local_unmetered"
|
|
166
|
+
: apiKey
|
|
167
|
+
? "metered_api"
|
|
168
|
+
: "unknown";
|
|
130
169
|
return {
|
|
131
170
|
adapterId: `openai-compatible:${model}`,
|
|
132
171
|
kind: "direct-provider",
|
|
@@ -172,10 +211,12 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
172
211
|
// indicate a permanent configuration problem.
|
|
173
212
|
const MAX_RETRIES = 3;
|
|
174
213
|
const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
214
|
+
const NON_RETRYABLE_STATUS = new Set([400, 401, 403]);
|
|
175
215
|
const endpoint = `${baseUrl}/v1/chat/completions`;
|
|
176
216
|
let responseText = "";
|
|
177
217
|
let tokensIn = estimated.tokensIn;
|
|
178
218
|
let tokensOut = 0;
|
|
219
|
+
let usageWasFullyProviderReported = false;
|
|
179
220
|
const headers = { "Content-Type": "application/json" };
|
|
180
221
|
if (apiKey)
|
|
181
222
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
@@ -207,6 +248,17 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
207
248
|
const body = (await res.json());
|
|
208
249
|
if (!res.ok || body.error) {
|
|
209
250
|
const errMsg = body.error?.message ?? `HTTP ${res.status}`;
|
|
251
|
+
// Fail immediately on non-retryable errors (auth, bad request)
|
|
252
|
+
if (NON_RETRYABLE_STATUS.has(res.status)) {
|
|
253
|
+
return {
|
|
254
|
+
status: "failed",
|
|
255
|
+
summary: `${model} API error: ${errMsg}`,
|
|
256
|
+
usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
|
|
257
|
+
verification: { passed: false, summary: "API call failed before verifier." },
|
|
258
|
+
failure: { message: errMsg, classHint: "infrastructure_error" }
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// Retry on transient errors
|
|
210
262
|
if (RETRYABLE_STATUS.has(res.status) && attempt < MAX_RETRIES - 1) {
|
|
211
263
|
lastError = errMsg;
|
|
212
264
|
// Exponential backoff: 1s, 2s, 4s
|
|
@@ -223,8 +275,13 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
223
275
|
}
|
|
224
276
|
responseText = body.choices?.[0]?.message?.content ?? "";
|
|
225
277
|
if (body.usage) {
|
|
226
|
-
|
|
227
|
-
|
|
278
|
+
const providerPromptTokens = body.usage.prompt_tokens;
|
|
279
|
+
const providerCompletionTokens = body.usage.completion_tokens;
|
|
280
|
+
usageWasFullyProviderReported =
|
|
281
|
+
typeof providerPromptTokens === "number" &&
|
|
282
|
+
typeof providerCompletionTokens === "number";
|
|
283
|
+
tokensIn = providerPromptTokens ?? tokensIn;
|
|
284
|
+
tokensOut = providerCompletionTokens ?? Math.ceil(responseText.length / CHARS_PER_TOKEN);
|
|
228
285
|
}
|
|
229
286
|
else {
|
|
230
287
|
tokensOut = Math.ceil(responseText.length / CHARS_PER_TOKEN);
|
|
@@ -267,33 +324,41 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
267
324
|
return {
|
|
268
325
|
status: "failed",
|
|
269
326
|
summary: `${model} returned an empty response.`,
|
|
270
|
-
usage:
|
|
327
|
+
usage: normalizeOpenAiCompatibleUsage({
|
|
328
|
+
model,
|
|
329
|
+
tokensIn,
|
|
330
|
+
tokensOut: 0,
|
|
331
|
+
usageWasFullyProviderReported,
|
|
332
|
+
modelSource,
|
|
333
|
+
billingMode
|
|
334
|
+
}),
|
|
271
335
|
verification: { passed: false, summary: "Empty response — nothing to verify." },
|
|
272
336
|
failure: { message: "empty_response" }
|
|
273
337
|
};
|
|
274
338
|
}
|
|
275
339
|
// Run verification
|
|
276
|
-
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack
|
|
340
|
+
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, undefined, {
|
|
341
|
+
runId: request.loopId,
|
|
342
|
+
workspaceId: request.workspaceId,
|
|
343
|
+
cwd: workingDirectory,
|
|
344
|
+
});
|
|
277
345
|
const execution = {
|
|
278
346
|
changedFiles: hasVerificationSteps
|
|
279
347
|
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
280
348
|
: []
|
|
281
349
|
};
|
|
282
|
-
const pricing = KNOWN_MODEL_PRICING[model] ?? {
|
|
283
|
-
inputPer1K: FALLBACK_INPUT_PER_1K,
|
|
284
|
-
outputPer1K: FALLBACK_OUTPUT_PER_1K
|
|
285
|
-
};
|
|
286
|
-
const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
|
|
287
350
|
return {
|
|
288
351
|
status: verification.passed ? "completed" : "failed",
|
|
289
352
|
summary: verification.passed
|
|
290
353
|
? `${model} completed the task. Verifier passed.`
|
|
291
354
|
: `${model} completed but verifier failed: ${verification.summary}`,
|
|
292
|
-
usage:
|
|
293
|
-
|
|
355
|
+
usage: normalizeOpenAiCompatibleUsage({
|
|
356
|
+
model,
|
|
294
357
|
tokensIn,
|
|
295
358
|
tokensOut,
|
|
296
|
-
|
|
359
|
+
usageWasFullyProviderReported,
|
|
360
|
+
modelSource,
|
|
361
|
+
billingMode
|
|
297
362
|
}),
|
|
298
363
|
verification,
|
|
299
364
|
execution,
|
|
@@ -13,18 +13,23 @@ export function createVerifierOnlyAdapter(options = {}) {
|
|
|
13
13
|
transport: "cli",
|
|
14
14
|
capabilities: createAdapterCapabilities({
|
|
15
15
|
usageSettlement: true,
|
|
16
|
-
diffArtifacts: true
|
|
16
|
+
diffArtifacts: true,
|
|
17
|
+
workspaceMutations: false
|
|
17
18
|
})
|
|
18
19
|
},
|
|
19
20
|
async execute(request) {
|
|
20
21
|
const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
|
|
21
22
|
(request.context.verificationStack?.length ?? 0) > 0;
|
|
22
23
|
const baselineChangedFiles = shouldTrackVerifierWrites
|
|
23
|
-
? new Set(await readGitChangedFiles(workingDirectory, 5_000
|
|
24
|
+
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
24
25
|
: new Set();
|
|
25
|
-
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl
|
|
26
|
+
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl, {
|
|
27
|
+
runId: request.loopId,
|
|
28
|
+
workspaceId: request.workspaceId,
|
|
29
|
+
cwd: workingDirectory
|
|
30
|
+
});
|
|
26
31
|
const changedFiles = shouldTrackVerifierWrites
|
|
27
|
-
? (await readGitChangedFiles(workingDirectory, 5_000
|
|
32
|
+
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
28
33
|
: [];
|
|
29
34
|
const execution = { changedFiles };
|
|
30
35
|
if (verification.passed) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type DiffVisibilityLevel = "none" | "git" | "adapter_reported";
|
|
2
|
-
export type VerifierCompatibility = "full" | "
|
|
2
|
+
export type VerifierCompatibility = "full" | "proof" | "unsupported";
|
|
3
3
|
export type SandboxExpectation = "host_process" | "workspace_write" | "provider_managed" | "not_applicable";
|
|
4
4
|
export type LaunchReadiness = "path_lookup" | "configured_endpoint" | "built_in";
|
|
5
5
|
export type AdapterUsageSettlement = "actual" | "estimated" | "unavailable";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Handoff — A-CTX-2 contracts.
|
|
3
|
+
*
|
|
4
|
+
* Chain verification circuit breaker for upstream-to-downstream context handoff.
|
|
5
|
+
*
|
|
6
|
+
* Rule: a downstream agent MUST NOT execute before verifyContextHandoff returns ok=true.
|
|
7
|
+
* Healthy handoffs are silent. Broken handoffs stop execution and return one reason code
|
|
8
|
+
* plus one recovery instruction. Never silently discard excluded or unverifiable context.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Overall integrity state of the upstream chain.
|
|
12
|
+
* "verified" — all upstream receipts and artifacts check out
|
|
13
|
+
* "evidence_boundary" — upstream integrity could not be established (no receipt)
|
|
14
|
+
* "tamper_detected" — receipt hash or artifact hash does not match
|
|
15
|
+
* "incomplete" — required fields or artifacts are absent
|
|
16
|
+
* "unsupported_schema"— handoff schemaVersion is not recognised
|
|
17
|
+
*/
|
|
18
|
+
export type ChainIntegrityState = "verified" | "evidence_boundary" | "tamper_detected" | "incomplete" | "unsupported_schema";
|
|
19
|
+
export type HandoffClaimState = "verified" | "unverified" | "rejected" | "unknown";
|
|
20
|
+
/** A single verifiable statement carried across the handoff boundary. */
|
|
21
|
+
export interface ContextHandoffClaim {
|
|
22
|
+
claimId: string;
|
|
23
|
+
statement: string;
|
|
24
|
+
evidenceRefs: string[];
|
|
25
|
+
verificationState: HandoffClaimState;
|
|
26
|
+
}
|
|
27
|
+
/** A content-addressed artifact that must be present and hash-stable across the boundary. */
|
|
28
|
+
export interface ContextHandoffArtifact {
|
|
29
|
+
path?: string;
|
|
30
|
+
sha256: string;
|
|
31
|
+
required: boolean;
|
|
32
|
+
label?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const HANDOFF_SCHEMA_VERSION: "martin.handoff.v1";
|
|
35
|
+
/**
|
|
36
|
+
* Immutable record produced by the upstream agent and verified before the
|
|
37
|
+
* downstream agent executes.
|
|
38
|
+
*
|
|
39
|
+
* upstreamIntegrity propagates from the parent handoff — it can never be
|
|
40
|
+
* upgraded by downstream success. A "tamper_detected" upstream remains
|
|
41
|
+
* "tamper_detected" regardless of what the current agent does.
|
|
42
|
+
*/
|
|
43
|
+
export interface ContextHandoffReceipt {
|
|
44
|
+
schemaVersion: typeof HANDOFF_SCHEMA_VERSION;
|
|
45
|
+
handoffId: string;
|
|
46
|
+
chainId: string;
|
|
47
|
+
missionId?: string;
|
|
48
|
+
producerRunId: string;
|
|
49
|
+
/** SHA-256 of the upstream run's canonical receipt file. */
|
|
50
|
+
producerReceiptHash: string;
|
|
51
|
+
/** Parent handoff IDs for multi-hop chain lineage. */
|
|
52
|
+
parentHandoffIds?: string[];
|
|
53
|
+
claims: ContextHandoffClaim[];
|
|
54
|
+
artifacts: ContextHandoffArtifact[];
|
|
55
|
+
/** Free-text assumptions that have not yet been verified at handoff time. */
|
|
56
|
+
unresolvedAssumptions: string[];
|
|
57
|
+
upstreamIntegrity: "verified" | "evidence_boundary" | "tamper_detected" | "incomplete";
|
|
58
|
+
createdAt: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Safe ledger record for a context object that was denied before reaching
|
|
62
|
+
* executable context.
|
|
63
|
+
*
|
|
64
|
+
* MUST NOT contain the secret value or raw denied content — only safe
|
|
65
|
+
* identity, hashes, and reason codes.
|
|
66
|
+
*/
|
|
67
|
+
export interface ContextExclusionDecision {
|
|
68
|
+
decision: "excluded";
|
|
69
|
+
objectId: string;
|
|
70
|
+
reasonCode: "secret_detected" | "policy_denied" | "authority_boundary" | "integrity_unverified";
|
|
71
|
+
reason: string;
|
|
72
|
+
/** SHA-256 of the denied content — never the content itself. */
|
|
73
|
+
sourceHash?: string;
|
|
74
|
+
excludedAt: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ContextHandoffVerification {
|
|
77
|
+
ok: boolean;
|
|
78
|
+
integrity: ChainIntegrityState;
|
|
79
|
+
reasons: Array<{
|
|
80
|
+
code: string;
|
|
81
|
+
message: string;
|
|
82
|
+
claimId?: string;
|
|
83
|
+
artifactSha256?: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Decision produced by decideContextCircuitBreak.
|
|
88
|
+
*
|
|
89
|
+
* Healthy: { shouldStop: false, silent: true }
|
|
90
|
+
* Broken: { shouldStop: true, silent: false, reasonCode, message, nextAction }
|
|
91
|
+
*/
|
|
92
|
+
export interface ContextCircuitBreakResult {
|
|
93
|
+
shouldStop: boolean;
|
|
94
|
+
/** true = healthy, no human-visible output; false = must surface to caller */
|
|
95
|
+
silent: boolean;
|
|
96
|
+
verification: ContextHandoffVerification;
|
|
97
|
+
reasonCode?: string;
|
|
98
|
+
message?: string;
|
|
99
|
+
nextAction?: string;
|
|
100
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Handoff — A-CTX-2 contracts.
|
|
3
|
+
*
|
|
4
|
+
* Chain verification circuit breaker for upstream-to-downstream context handoff.
|
|
5
|
+
*
|
|
6
|
+
* Rule: a downstream agent MUST NOT execute before verifyContextHandoff returns ok=true.
|
|
7
|
+
* Healthy handoffs are silent. Broken handoffs stop execution and return one reason code
|
|
8
|
+
* plus one recovery instruction. Never silently discard excluded or unverifiable context.
|
|
9
|
+
*/
|
|
10
|
+
// ─── Receipt ──────────────────────────────────────────────────────────────────
|
|
11
|
+
export const HANDOFF_SCHEMA_VERSION = "martin.handoff.v1";
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Runtime — A-CTX-1 contracts.
|
|
3
|
+
*
|
|
4
|
+
* Defines the governed context layer: objects, budgets, manifests, ledger
|
|
5
|
+
* entries, fault protocol, and continuation checkpoints.
|
|
6
|
+
*
|
|
7
|
+
* Non-negotiable invariants (enforced in implementation, not type system):
|
|
8
|
+
* - No `confidence` float anywhere — use UsageEvidence enums only
|
|
9
|
+
* - No `outcome_delta` field anywhere — permanently banned
|
|
10
|
+
* - `nowMs` is always a captured input; never Date.now() inside a compiler
|
|
11
|
+
* - Secret scan hit → deny persistence entirely; reason-coded ledger entry only
|
|
12
|
+
* - Fault budget accumulates by taskId, NOT by runId
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Evidence quality for a metric or measurement.
|
|
16
|
+
*
|
|
17
|
+
* "observed" = distinctive substring from the object appears verbatim in model output
|
|
18
|
+
* "inferred" = weak kind correlation, narrow and documented per kind
|
|
19
|
+
* "unknown" = default; metrics MUST be withheld, never reported as zero
|
|
20
|
+
*/
|
|
21
|
+
export type UsageEvidence = "observed" | "inferred" | "unknown";
|
|
22
|
+
export type ContextKind = "mission" | "policy" | "security" | "task" | "continuation" | "working_diff" | "diagnostic" | "source" | "test" | "decision" | "todo" | "receipt_summary" | "tool_result";
|
|
23
|
+
export type ContextPriority = "required" | "high" | "normal" | "low";
|
|
24
|
+
export type ContextTrust = "authoritative" | "workspace" | "external" | "untrusted";
|
|
25
|
+
export type ContextSensitivity = "public" | "workspace" | "restricted" | "secret";
|
|
26
|
+
/** A typed, hashed, provenance-tracked object eligible for working-set inclusion. */
|
|
27
|
+
export interface ContextObject {
|
|
28
|
+
/** Stable identifier across revisions. */
|
|
29
|
+
id: string;
|
|
30
|
+
kind: ContextKind;
|
|
31
|
+
priority: ContextPriority;
|
|
32
|
+
trust: ContextTrust;
|
|
33
|
+
sensitivity: ContextSensitivity;
|
|
34
|
+
/** File path, receipt hash, or MCP URI — where the object originates. */
|
|
35
|
+
sourceRef: string;
|
|
36
|
+
/** SHA-256 of raw content. */
|
|
37
|
+
contentHash: string;
|
|
38
|
+
/** Git SHA or equivalent revision of the source file, if known. */
|
|
39
|
+
sourceRevisionHash?: string;
|
|
40
|
+
/** Pre-render token estimate used during selection. */
|
|
41
|
+
estimatedTokens: number;
|
|
42
|
+
/** Set iff the object was manually pinned; counts against pinnedTokensMax. */
|
|
43
|
+
pinnedBy?: string;
|
|
44
|
+
tags?: string[];
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Token budget for one context compilation pass.
|
|
49
|
+
*
|
|
50
|
+
* pinnedTokensMax REQUIRED: caps manual-pin tokens to prevent deterministic
|
|
51
|
+
* selector starvation. Any pin that would exceed this cap must be rejected.
|
|
52
|
+
*/
|
|
53
|
+
export interface ContextBudget {
|
|
54
|
+
modelWindowTokens: number;
|
|
55
|
+
systemReserveTokens: number;
|
|
56
|
+
outputReserveTokens: number;
|
|
57
|
+
toolReserveTokens: number;
|
|
58
|
+
overflowReserveTokens: number;
|
|
59
|
+
maxWorkingSetTokens: number;
|
|
60
|
+
/** Hard cap on manually-pinned token allocation. */
|
|
61
|
+
pinnedTokensMax: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The compiler's per-object inclusion/exclusion record.
|
|
65
|
+
*
|
|
66
|
+
* "included_truncated" is DISTINCT from "included" — truncation must be
|
|
67
|
+
* explicit in every receipt. Never use "included" for a truncated object.
|
|
68
|
+
*/
|
|
69
|
+
export interface ContextCandidateDecision {
|
|
70
|
+
objectId: string;
|
|
71
|
+
decision: "included" | "included_truncated" | "excluded" | "deferred" | "fault_loaded";
|
|
72
|
+
/**
|
|
73
|
+
* Only present when decision === "included_truncated".
|
|
74
|
+
* Records the pre-truncation token count for receipts.
|
|
75
|
+
*/
|
|
76
|
+
truncatedFromTokens?: number;
|
|
77
|
+
/** Machine-readable reason code for this decision. */
|
|
78
|
+
reason: string;
|
|
79
|
+
/** Actual post-render token count after adapter recount. Omitted pre-render. */
|
|
80
|
+
renderedTokens?: number;
|
|
81
|
+
}
|
|
82
|
+
export declare const CONTEXT_MANIFEST_VERSION: "context-manifest/1";
|
|
83
|
+
/**
|
|
84
|
+
* Immutable record of one context compilation pass.
|
|
85
|
+
*
|
|
86
|
+
* Determinism invariant:
|
|
87
|
+
* same inputs + policyHash + adapterVersion + nowMs → same manifestHash
|
|
88
|
+
*
|
|
89
|
+
* nowMs MUST be an explicit captured input — never Date.now() inside the
|
|
90
|
+
* compiler or rankContextObject.
|
|
91
|
+
*/
|
|
92
|
+
export interface ContextManifest {
|
|
93
|
+
schemaVersion: typeof CONTEXT_MANIFEST_VERSION;
|
|
94
|
+
/** SHA-256 of (decisions + budget + policyHash + adapterVersion + nowMs). */
|
|
95
|
+
manifestHash: string;
|
|
96
|
+
taskId: string;
|
|
97
|
+
runId: string;
|
|
98
|
+
/** Explicit input — never Date.now() inside the compiler. */
|
|
99
|
+
nowMs: number;
|
|
100
|
+
adapterVersion: string;
|
|
101
|
+
policyHash: string;
|
|
102
|
+
budget: ContextBudget;
|
|
103
|
+
decisions: ContextCandidateDecision[];
|
|
104
|
+
totalRenderedTokens: number;
|
|
105
|
+
/** Render pass count; must be ≤ MAX_RENDER_PASSES (5). */
|
|
106
|
+
renderPasses: number;
|
|
107
|
+
compilationDurationMs: number;
|
|
108
|
+
}
|
|
109
|
+
export declare const CONTEXT_LEDGER_VERSION: "context-ledger/1";
|
|
110
|
+
/**
|
|
111
|
+
* Fourth ledger entry type — on the SAME receipt chain as cost and verification.
|
|
112
|
+
*
|
|
113
|
+
* FORBIDDEN: confidence field, outcome_delta field — both permanently banned.
|
|
114
|
+
* overflowCount and faultBudgetUsed accumulate by taskId, NOT by runId.
|
|
115
|
+
*/
|
|
116
|
+
export interface ContextLedgerEntry {
|
|
117
|
+
schemaVersion: typeof CONTEXT_LEDGER_VERSION;
|
|
118
|
+
entryType: "context";
|
|
119
|
+
taskId: string;
|
|
120
|
+
runId: string;
|
|
121
|
+
manifestHash: string;
|
|
122
|
+
totalIncluded: number;
|
|
123
|
+
totalExcluded: number;
|
|
124
|
+
totalFaultLoaded: number;
|
|
125
|
+
/** Cumulative overflow count — resets per task, never per run. */
|
|
126
|
+
overflowCount: number;
|
|
127
|
+
/** Cumulative fault budget consumed — resets per task, never per run. */
|
|
128
|
+
faultBudgetUsed: number;
|
|
129
|
+
adapterVersion: string;
|
|
130
|
+
}
|
|
131
|
+
/** Request to demand-load a missing context object on fault. */
|
|
132
|
+
export interface ContextFaultRequest {
|
|
133
|
+
objectId: string;
|
|
134
|
+
taskId: string;
|
|
135
|
+
runId: string;
|
|
136
|
+
reason: string;
|
|
137
|
+
}
|
|
138
|
+
/** Result of a context fault resolution attempt. */
|
|
139
|
+
export interface ContextFaultResult {
|
|
140
|
+
objectId: string;
|
|
141
|
+
status: "loaded" | "unavailable" | "secret_blocked" | "budget_exceeded";
|
|
142
|
+
/** Post-render token count when status === "loaded". */
|
|
143
|
+
renderedTokens?: number;
|
|
144
|
+
ledgerEventEmitted: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** Minimal task item tracked within a ContinuationCheckpoint. */
|
|
147
|
+
export interface TaskItem {
|
|
148
|
+
id: string;
|
|
149
|
+
description: string;
|
|
150
|
+
status: "pending" | "in_progress" | "completed" | "failed";
|
|
151
|
+
createdAtMs: number;
|
|
152
|
+
completedAtMs?: number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Durable task state preserved outside the provider transcript.
|
|
156
|
+
*
|
|
157
|
+
* Write discipline (all enforced in CheckpointStore):
|
|
158
|
+
* - All writes MUST go through CheckpointStore.withTaskLock (CAS-locked).
|
|
159
|
+
* - Reject if store.read(taskId).sequenceNumber !== checkpoint.sequenceNumber - 1.
|
|
160
|
+
* - All array fields MUST be bounded before persistence.
|
|
161
|
+
* - Old completed entries MUST be compacted into completedSummaryHash.
|
|
162
|
+
*
|
|
163
|
+
* Testing requirement: two real concurrent Node processes, not jest mocks.
|
|
164
|
+
*/
|
|
165
|
+
export interface ContinuationCheckpoint {
|
|
166
|
+
schemaVersion: "checkpoint/1";
|
|
167
|
+
taskId: string;
|
|
168
|
+
/** CAS lock key: reject write if stored sequenceNumber !== this - 1. */
|
|
169
|
+
sequenceNumber: number;
|
|
170
|
+
/** SHA-256 of the previous checkpoint — integrity chain. */
|
|
171
|
+
parentHash: string;
|
|
172
|
+
/** Old completed[] compacted into a hash-referenced summary to bound size. */
|
|
173
|
+
completedSummaryHash?: string;
|
|
174
|
+
pending: TaskItem[];
|
|
175
|
+
inProgress: TaskItem[];
|
|
176
|
+
decisions: ContextCandidateDecision[];
|
|
177
|
+
createdAtMs: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Governs what may enter the working set, how much, and why.
|
|
181
|
+
* policyHash must be included in every manifestHash computation.
|
|
182
|
+
*/
|
|
183
|
+
export interface ContextPolicy {
|
|
184
|
+
policyHash: string;
|
|
185
|
+
/** Sensitivity levels that are unconditionally excluded. */
|
|
186
|
+
deniedSensitivities: ContextSensitivity[];
|
|
187
|
+
/** Trust levels that are unconditionally excluded. */
|
|
188
|
+
deniedTrustLevels: ContextTrust[];
|
|
189
|
+
/** Behaviour when required objects alone exceed budget. */
|
|
190
|
+
requiredOverBudgetAction: "explicit_escalation" | "fail_closed";
|
|
191
|
+
/** Maximum allowed compiler wall-clock duration in ms. */
|
|
192
|
+
maxCompilerDurationMs: number;
|
|
193
|
+
/** Maximum overhead ratio: compilationDurationMs / totalRenderedTokens. */
|
|
194
|
+
maxOverheadRatio: number;
|
|
195
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Runtime — A-CTX-1 contracts.
|
|
3
|
+
*
|
|
4
|
+
* Defines the governed context layer: objects, budgets, manifests, ledger
|
|
5
|
+
* entries, fault protocol, and continuation checkpoints.
|
|
6
|
+
*
|
|
7
|
+
* Non-negotiable invariants (enforced in implementation, not type system):
|
|
8
|
+
* - No `confidence` float anywhere — use UsageEvidence enums only
|
|
9
|
+
* - No `outcome_delta` field anywhere — permanently banned
|
|
10
|
+
* - `nowMs` is always a captured input; never Date.now() inside a compiler
|
|
11
|
+
* - Secret scan hit → deny persistence entirely; reason-coded ledger entry only
|
|
12
|
+
* - Fault budget accumulates by taskId, NOT by runId
|
|
13
|
+
*/
|
|
14
|
+
// ─── ContextManifest ─────────────────────────────────────────────────────────
|
|
15
|
+
export const CONTEXT_MANIFEST_VERSION = "context-manifest/1";
|
|
16
|
+
// ─── ContextLedgerEntry ───────────────────────────────────────────────────────
|
|
17
|
+
export const CONTEXT_LEDGER_VERSION = "context-ledger/1";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Shadow — A-CTX-0 schema contracts.
|
|
3
|
+
*
|
|
4
|
+
* Shadow mode observes the existing prompt without altering it.
|
|
5
|
+
* No source text is retained in manifests or receipts — only hashes and counts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONTEXT_SHADOW_MANIFEST_VERSION: "context-shadow-manifest/1";
|
|
8
|
+
export declare const CONTEXT_C5_VERSION: "context-c5/1";
|
|
9
|
+
export type ContextEvidence = "observed" | "configured" | "inferred" | "unknown";
|
|
10
|
+
export type ContextShadowSegmentKind = "system" | "mission" | "task" | "workspace" | "diagnostic" | "tool" | "other";
|
|
11
|
+
export interface ContextShadowSegmentInput {
|
|
12
|
+
segmentId: string;
|
|
13
|
+
kind: ContextShadowSegmentKind;
|
|
14
|
+
required: boolean;
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ContextShadowDecisionV1 {
|
|
18
|
+
segmentId: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
/** SHA-256 of segment.text — never the source text itself. */
|
|
21
|
+
contentHash: string;
|
|
22
|
+
estimatedTokens: number;
|
|
23
|
+
/** Always true in shadow mode — actual prompt is never altered. */
|
|
24
|
+
actuallyIncluded: true;
|
|
25
|
+
/** What the shadow compiler would have decided if it controlled selection. */
|
|
26
|
+
proposedDecision: "included" | "excluded";
|
|
27
|
+
reason: "required" | "within_shadow_budget" | "shadow_budget_exhausted" | "required_over_budget";
|
|
28
|
+
}
|
|
29
|
+
export interface ContextShadowManifestV1 {
|
|
30
|
+
schemaVersion: typeof CONTEXT_SHADOW_MANIFEST_VERSION;
|
|
31
|
+
manifestId: string;
|
|
32
|
+
/** SHA-256 of the canonical JSON of the manifest (excluding manifestId and manifestHash). */
|
|
33
|
+
manifestHash: string;
|
|
34
|
+
/** SHA-256 of the canonical compiler input, for cross-run determinism checks. */
|
|
35
|
+
compilerInputHash: string;
|
|
36
|
+
runId: string;
|
|
37
|
+
taskId?: string;
|
|
38
|
+
adapter: string;
|
|
39
|
+
model?: string;
|
|
40
|
+
capturedAt: string;
|
|
41
|
+
mode: "shadow";
|
|
42
|
+
modelWindowTokens: number | null;
|
|
43
|
+
modelWindowEvidence: ContextEvidence;
|
|
44
|
+
shadowBudgetTokens: number;
|
|
45
|
+
/** SHA-256 of framed source segments — confirms prompt identity without storing content. */
|
|
46
|
+
actualPromptHash: string;
|
|
47
|
+
/** Estimated token count of all segments (actual prompt). */
|
|
48
|
+
actualEstimatedTokens: number;
|
|
49
|
+
/** Estimated token count of shadow-selected segments only. */
|
|
50
|
+
proposedEstimatedTokens: number;
|
|
51
|
+
/** True if required segments alone exceed shadowBudgetTokens. */
|
|
52
|
+
requiredOverBudget: boolean;
|
|
53
|
+
decisions: ContextShadowDecisionV1[];
|
|
54
|
+
}
|
|
55
|
+
export interface ContextC5EnvelopeV1 {
|
|
56
|
+
schemaVersion: typeof CONTEXT_C5_VERSION;
|
|
57
|
+
resource: "context";
|
|
58
|
+
event: "shadow_compiled";
|
|
59
|
+
runId: string;
|
|
60
|
+
taskId?: string;
|
|
61
|
+
manifestId: string;
|
|
62
|
+
manifestHash: string;
|
|
63
|
+
adapter: string;
|
|
64
|
+
model?: string;
|
|
65
|
+
mode: "shadow";
|
|
66
|
+
actualEstimatedTokens: number;
|
|
67
|
+
proposedEstimatedTokens: number;
|
|
68
|
+
candidateCount: number;
|
|
69
|
+
includedCount: number;
|
|
70
|
+
excludedCount: number;
|
|
71
|
+
requiredOverBudget: boolean;
|
|
72
|
+
modelWindowTokens: number | null;
|
|
73
|
+
modelWindowEvidence: ContextEvidence;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Shadow — A-CTX-0 schema contracts.
|
|
3
|
+
*
|
|
4
|
+
* Shadow mode observes the existing prompt without altering it.
|
|
5
|
+
* No source text is retained in manifests or receipts — only hashes and counts.
|
|
6
|
+
*/
|
|
7
|
+
export const CONTEXT_SHADOW_MANIFEST_VERSION = "context-shadow-manifest/1";
|
|
8
|
+
export const CONTEXT_C5_VERSION = "context-c5/1";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R4 Delivery — M1 Contract
|
|
3
|
+
*
|
|
4
|
+
* Shared with Lane B (Control Plane). Do not modify unilaterally.
|
|
5
|
+
* The server selects one message; the client never downloads the catalog.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DELIVERY_MESSAGE_SCHEMA_VERSION: "delivery-message/1";
|
|
8
|
+
export declare const DELIVERY_RECORD_SCHEMA_VERSION: "delivery-record/1";
|
|
9
|
+
export declare const MESSAGE_SELECTION_RESPONSE_SCHEMA_VERSION: "martin-message-selection/1";
|
|
10
|
+
export declare const ALLOWED_ACTION_TYPES: readonly ["upgrade_cli", "upgrade_mcp", "submit_feedback", "open_release_notes", "dismiss", "view_spend_report", "view_run_explain"];
|
|
11
|
+
export type ActionType = (typeof ALLOWED_ACTION_TYPES)[number];
|
|
12
|
+
export type MessageKind = "update" | "feedback_request" | "milestone";
|
|
13
|
+
export interface DeliveryMessage {
|
|
14
|
+
schemaVersion: typeof DELIVERY_MESSAGE_SCHEMA_VERSION;
|
|
15
|
+
id: string;
|
|
16
|
+
revision: number;
|
|
17
|
+
kind: MessageKind;
|
|
18
|
+
title: string;
|
|
19
|
+
body: string;
|
|
20
|
+
action: {
|
|
21
|
+
type: ActionType;
|
|
22
|
+
url?: string;
|
|
23
|
+
targetVersion?: string;
|
|
24
|
+
};
|
|
25
|
+
expiresAt: string;
|
|
26
|
+
cooldownHours: number;
|
|
27
|
+
}
|
|
28
|
+
export interface MessageSelectionResponse {
|
|
29
|
+
schemaVersion: typeof MESSAGE_SELECTION_RESPONSE_SCHEMA_VERSION;
|
|
30
|
+
message?: DeliveryMessage;
|
|
31
|
+
}
|
|
32
|
+
/** Local cooldown/dismissal state — persisted in .martin/delivery-record.json */
|
|
33
|
+
export interface DeliveryRecord {
|
|
34
|
+
schemaVersion: typeof DELIVERY_RECORD_SCHEMA_VERSION;
|
|
35
|
+
lastMessageId?: string;
|
|
36
|
+
lastShownAtEpochMs?: number;
|
|
37
|
+
dismissedIds: string[];
|
|
38
|
+
cooldownUntilEpochMs?: number;
|
|
39
|
+
cachedMessage?: DeliveryMessage;
|
|
40
|
+
cachedAtEpochMs?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Structured update field for --json and MCP responses */
|
|
43
|
+
export interface UpdateAvailableField {
|
|
44
|
+
targetVersion: string;
|
|
45
|
+
kind: "cli" | "mcp";
|
|
46
|
+
message?: string;
|
|
47
|
+
}
|