Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b408ab0
fix: freeze terminal agent reconciliation timing (#5)
Sep 8, 2026
dcbfbfc
fix(opencode): require current prompt acknowledgement
Sep 8, 2026
0c34f01
fix: enforce exact OpenCode catalog identities (#58)
Sep 8, 2026
f702ceb
feat: add bounded live OpenCode catalog refresh
Sep 8, 2026
b132b22
feat: add fail-closed Cline catalog adapter
Sep 8, 2026
6b6dbef
fix: align agent selector contract with handlers
Sep 8, 2026
da209df
feat: project safe OpenCode catalog metadata
Sep 8, 2026
7da3d80
fix: bind Cline ACP provider family to process args
Sep 8, 2026
504de8f
test: include cline and chat swarm suites
Sep 8, 2026
c64574c
feat: add opt-in sanitized ACP diagnostics
Sep 8, 2026
22b9f72
fix: harden ACP diagnostic redaction
Sep 8, 2026
d8902e7
fix: wire live catalogs and Cline route identity
Sep 8, 2026
973e3c8
fix: bind Cline catalog and direct selector contract
Sep 8, 2026
52f1b9c
fix: isolate OpenCode MCP catalog server
Sep 8, 2026
a2e676a
fix: bind catalog receipts through dispatch
Sep 8, 2026
563bb8c
test: gate worker turns on catalog receipts
Sep 8, 2026
51e1174
test: verify reloaded receipt identity
Sep 8, 2026
8cee8f8
fix: bind receipt freshness and runtime scope
Sep 8, 2026
5a49245
fix: fail closed direct Cline catalog admission
Sep 8, 2026
3056e4a
fix: preserve Cline target and catalog evidence
Sep 8, 2026
f3e9351
test: keep receipt consumer scope identical
Sep 8, 2026
2cd4321
fix: gate catalog receipts on source and age
Sep 8, 2026
0d414ba
test: expose truthful catalog query evidence
Sep 8, 2026
2f4c0b5
fix: fail closed on Cline route readback
Sep 8, 2026
71c9fad
fix: fail closed on unsupported Cline effort
Sep 8, 2026
c4b9335
fix: require verified Cline thinking readiness
Sep 8, 2026
6ba64e8
test: include integration catalog and swarm suites
Sep 8, 2026
dcf62a6
close startup recovery and catalog evidence gaps
Sep 8, 2026
4e5c982
keep Cline thinking evidence optional for other catalog entries
Sep 8, 2026
da344be
cover nonempty OpenCode catalog output validation
Sep 8, 2026
1dc8ad8
test: keep dispatch catalog suite separate from swarm
Sep 8, 2026
489ad06
fix: keep catalog reliability projection in dispatch split
Sep 8, 2026
239fbfd
merge: rebind dispatch dependency to verified main baseline
Sep 10, 2026
8a8844d
test: await continuation fixture manager shutdown
Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dev": "node scripts/dev-server.mjs",
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
"start": "node dist/cli.js serve",
"test": "tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/cli-ci.test.ts",
"test": "tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-cline-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/local-agent-opencode-mcp-catalog.test.ts && tsx src/cli-ci.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"keywords": [],
Expand Down
18 changes: 18 additions & 0 deletions src/capability-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,27 @@ test("loaded capability manifest is deterministic and derived from the registere
"agent_start.tool",
]);
assert.match(first.manifestSha256, /^[0-9a-f]{64}$/);
assert.match(first.inputSchemaFingerprint ?? "", /^[0-9a-f]{64}$/);
assert.equal(first.manifestSha256, second.manifestSha256);
});

test("registered input schema fingerprint tracks selector shape while ignoring key order", () => {
const original = deriveLoadedCapabilityManifest({ agent_start: agentStartInput() });
const reordered = deriveLoadedCapabilityManifest({
agent_start: {
executionContract: agentStartInput().executionContract,
workspaceId: agentStartInput().workspaceId,
},
});
assert.equal(original.inputSchemaFingerprint, reordered.inputSchemaFingerprint);

const changed = agentStartInput();
changed.provider = z.string().optional();
const changedManifest = deriveLoadedCapabilityManifest({ agent_start: changed });
assert.notEqual(original.inputSchemaFingerprint, changedManifest.inputSchemaFingerprint);
assert.notEqual(original.manifestSha256, changedManifest.manifestSha256);
});

test("loaded capability manifest detects removal from the actual registered schema", () => {
const regressed = agentStartInput();
regressed.executionContract = z.object({ idleTimeoutMs: z.number().optional() }).partial().optional();
Expand Down
28 changes: 26 additions & 2 deletions src/capability-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash } from "node:crypto";
import * as z from "zod/v4";

export const CAPABILITY_MANIFEST_SCHEMA = "devspace.capability_manifest.v1" as const;

Expand All @@ -7,6 +8,8 @@ export interface CapabilityManifest {
capabilities: string[];
missing: string[];
manifestSha256: string;
/** Stable fingerprint of the complete registered MCP input schemas. */
inputSchemaFingerprint?: string;
}

interface SchemaLike {
Expand Down Expand Up @@ -60,6 +63,26 @@ function resolveField(
return current;
}

function canonicalValue(value: unknown, key?: string): unknown {
if (Array.isArray(value)) {
const items = value.map((item) => canonicalValue(item));
return key === "required" || key === "enum"
? items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))
: items;
}
if (!value || typeof value !== "object") return value;
return Object.fromEntries(
Object.keys(value as Record<string, unknown>).sort().map((childKey) => [childKey, canonicalValue((value as Record<string, unknown>)[childKey], childKey)]),
);
}

function registeredSchemaFingerprint(tools: ToolInputSchemas): string | undefined {
const schemas = Object.fromEntries(
Object.keys(tools).sort().map((tool) => [tool, z.toJSONSchema(z.object(tools[tool] as Record<string, z.ZodType>))]),
);
return createHash("sha256").update(JSON.stringify(canonicalValue(schemas))).digest("hex");
}

/**
* Derive the runtime manifest from the same schema objects passed to MCP tool
* registration. No source declaration or caller-supplied capability list is
Expand Down Expand Up @@ -94,8 +117,9 @@ export function deriveLoadedCapabilityManifest(
capabilities.sort();
missing.sort();
observed.sort((left, right) => left.id.localeCompare(right.id));
const inputSchemaFingerprint = registeredSchemaFingerprint(tools);
const manifestSha256 = createHash("sha256")
.update(JSON.stringify({ schema: CAPABILITY_MANIFEST_SCHEMA, observed }))
.update(JSON.stringify({ schema: CAPABILITY_MANIFEST_SCHEMA, observed, inputSchemaFingerprint }))
.digest("hex");
return { schema: CAPABILITY_MANIFEST_SCHEMA, capabilities, missing, manifestSha256 };
return { schema: CAPABILITY_MANIFEST_SCHEMA, capabilities, missing, manifestSha256, inputSchemaFingerprint };
}
Loading
Loading