Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions src/core-mutation-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,51 @@ export function registerCoreMutationSessionTools(
},
);

registerAppTool(
server,
"core_mutation_session_reconcile_synchronous",
{
title: "Reconcile Core synchronous Git effect",
description:
"Inspect the exact physical workspace after an unresolved synchronous Git effect and clear only the SYNCHRONOUS_GIT writer pin when scope and deletion checks remain valid. This never retries Git, clears other writer domains, or grants completion authority.",
inputSchema: {
workspaceId: z.string(),
sessionId: z.string(),
bindingHash: z.string(),
},
outputSchema: z.object({
session: outputSchema,
snapshot: z.record(z.string(), z.unknown()),
}),
_meta: {},
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
},
async ({ workspaceId, sessionId, bindingHash }, extra) => {
const workspace = workspaces.getWorkspace(workspaceId);
try {
const reconciled = await store.reconcileSynchronousEffect({
sessionId,
workspaceSessionId: workspaceId,
workspaceRoot: workspace.root,
actorKey: actorKeyRequired(extra),
bindingHash,
});
return {
content: [{
type: "text" as const,
text: `Core synchronous Git effect reconciled for ${sessionId}; no Git effect was replayed.`,
}],
structuredContent: {
session: publicSession(reconciled.session),
snapshot: reconciled.snapshot as unknown as Record<string, unknown>,
},
};
} catch (error) {
throw toolError(error);
}
},
);

registerAppTool(
server,
"core_mutation_session_snapshot",
Expand Down
42 changes: 42 additions & 0 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,48 @@ test("Core-bound mutation session tools are registered for durable mutation admi
const tools = await context.client.listTools();
assert.ok(tools.tools.some((tool) => tool.name === "core_mutation_session_open"));
assert.ok(tools.tools.some((tool) => tool.name === "core_mutation_session_status"));
assert.ok(tools.tools.some((tool) => tool.name === "core_mutation_session_reconcile_synchronous"));
});

test("Core synchronous Git reconciliation clears the exact writer pin without replaying Git", async (t) => {
const conversationScopeId = "core-sync-git-reconcile";
const conversation = { "openai/session": conversationScopeId };
const context = await fixture(t, { git: true, coreMutation: true });
const opened = await callOpen(context.client, context.project, conversationScopeId);
const workspaceId = structuredContent(opened).workspaceId as string;
const bound = await bindTestCoreSession({
fixture: context,
workspaceId,
workspaceRoot: context.project,
conversationScopeId,
allowedPaths: ["AGENTS.md"],
});
const actorKey = `openai:${createHash("sha256").update(conversationScopeId).digest("hex")}`;
await context.coreMutationSessions!.admitEffect({
workspaceSessionId: workspaceId,
workspaceRoot: context.project,
workspaceMode: "checkout",
managed: false,
actorKey,
pointer: { required: true, sessionId: bound.session.id, bindingHash: bound.session.bindingHash },
paths: ["AGENTS.md"],
pathContainment: "NOT_PROVEN",
synchronousPostEffectCheck: true,
});
const beforeHead = (await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: context.project })).stdout.trim();
assert.deepEqual(context.coreMutationSessions!.getById(bound.session.id)?.writerDomains, ["SYNCHRONOUS_GIT"]);
assert.equal(context.coreMutationSessions!.getById(bound.session.id)?.writerReconciliationState, "OUTCOME_UNKNOWN");

const reconciled = await context.client.callTool({
name: "core_mutation_session_reconcile_synchronous",
arguments: { workspaceId, sessionId: bound.session.id, bindingHash: bound.session.bindingHash },
_meta: conversation,
});
assert.equal(reconciled.isError, undefined, responseText(reconciled));
const session = structuredContent(reconciled).session as Record<string, unknown>;
assert.deepEqual(session.writerDomains, []);
assert.equal(session.writerReconciliationState, "CLEAR");
assert.equal((await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: context.project })).stdout.trim(), beforeHead);
});

test("createMcpServer without Core store fails closed unless explicit test bypass is supplied", async (t) => {
Expand Down
Loading