pi-cursor-bridge 0.1.2 → 0.1.4
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/.codex-plugin/plugin.json +9 -9
- package/README.md +13 -13
- package/dist/cursor-bridge.mjs +171 -149
- package/dist/cursor-lifecycle-supervisor.mjs +17 -17
- package/extensions/index.ts +28 -28
- package/extensions/mcp-stdio.ts +92 -92
- package/package.json +2 -2
- package/skills/cce-routing/SKILL.md +3 -0
- package/skills/cursor-delegate/SKILL.md +10 -8
- package/skills/cursor-delegate/references/delegation-contract.md +2 -2
|
@@ -601,8 +601,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
601
601
|
windowGuard: windowGuard2,
|
|
602
602
|
needsAction: "install_or_locate_cursor",
|
|
603
603
|
retryable: true,
|
|
604
|
-
nextStep: "
|
|
605
|
-
message: `CCE
|
|
604
|
+
nextStep: "Confirm that Cursor is installed and signed in. For a portable or custom installation, set CURSOR_EXE and run the same initialization command again.",
|
|
605
|
+
message: `CCE connected to Cursor but cannot open workspace ${projectPath} because the Cursor executable was not found.`
|
|
606
606
|
};
|
|
607
607
|
}
|
|
608
608
|
const beforeTargetIds = new Set(currentTargets.map((target2) => target2.id));
|
|
@@ -629,8 +629,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
629
629
|
cursorExecutableSource: cursorExecutable2.source,
|
|
630
630
|
needsAction: "retry_initialization",
|
|
631
631
|
retryable: true,
|
|
632
|
-
nextStep: "
|
|
633
|
-
message: `Cursor
|
|
632
|
+
nextStep: "Wait for Cursor to finish opening the project, then run the same initialization command again.",
|
|
633
|
+
message: `Cursor opened the project, but CCE has not confirmed that workspace ${projectPath} is ready. Initialization stopped safely to avoid searching the wrong project.`
|
|
634
634
|
};
|
|
635
635
|
}
|
|
636
636
|
targetId2 = openedTarget2.id;
|
|
@@ -647,7 +647,7 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
647
647
|
windowGuard: windowGuard2,
|
|
648
648
|
targetId: targetId2,
|
|
649
649
|
workspaceAction,
|
|
650
|
-
message: workspaceAction === "reused-agents-window" ? `CDP ${CDP_PORT}
|
|
650
|
+
message: workspaceAction === "reused-agents-window" ? `CDP ${CDP_PORT} responded as Cursor; Agents Window ${targetId2} was reused without opening another IDE window.` : `CDP ${CDP_PORT} responded as Cursor; the target workspace is bound to CDP target ${targetId2 || "default"}.`
|
|
651
651
|
};
|
|
652
652
|
}
|
|
653
653
|
return {
|
|
@@ -656,8 +656,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
656
656
|
port: CDP_PORT,
|
|
657
657
|
needsAction: "free_cce_port",
|
|
658
658
|
retryable: true,
|
|
659
|
-
nextStep:
|
|
660
|
-
message: `CCE
|
|
659
|
+
nextStep: `Local port ${CDP_PORT} is in use by another program. Close that program, then run the same initialization command again.`,
|
|
660
|
+
message: `CCE cannot connect to Cursor because required local port ${CDP_PORT} is occupied by another program.`
|
|
661
661
|
};
|
|
662
662
|
}
|
|
663
663
|
if (cursorRunningImpl()) {
|
|
@@ -670,8 +670,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
670
670
|
cursorExecutableSource: cursorExecutable2 && cursorExecutable2.source || null,
|
|
671
671
|
needsAction: "close_cursor_and_retry",
|
|
672
672
|
retryable: true,
|
|
673
|
-
nextStep: projectPath ?
|
|
674
|
-
message: "Cursor
|
|
673
|
+
nextStep: projectPath ? `Save your work, exit Cursor normally once, then initialize CCE for workspace ${projectPath} again.` : "Save your work, exit Cursor normally once, then retry the previous CCE operation.",
|
|
674
|
+
message: "Cursor was already running, so CCE cannot add the required connection capability in place. Cursor Bridge will not force-close it, protecting unsaved work."
|
|
675
675
|
};
|
|
676
676
|
}
|
|
677
677
|
const cursorExecutable = findCursorExeDetailsImpl();
|
|
@@ -683,8 +683,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
683
683
|
port: CDP_PORT,
|
|
684
684
|
needsAction: "install_or_locate_cursor",
|
|
685
685
|
retryable: true,
|
|
686
|
-
nextStep: "
|
|
687
|
-
message: "
|
|
686
|
+
nextStep: "Install and sign in to Cursor first. For a portable or custom installation, set CURSOR_EXE and run the same initialization command again.",
|
|
687
|
+
message: "Cursor was not found. Standard Windows and macOS installations are detected automatically and normally do not require an explicit executable path."
|
|
688
688
|
};
|
|
689
689
|
}
|
|
690
690
|
const launchPort = resolveCursorLaunchCdpPort(CDP_PORT);
|
|
@@ -719,8 +719,8 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
719
719
|
cursorExecutableSource: cursorExecutable.source,
|
|
720
720
|
needsAction: "retry_initialization",
|
|
721
721
|
retryable: true,
|
|
722
|
-
nextStep: "
|
|
723
|
-
message: "Cursor
|
|
722
|
+
nextStep: "Wait a moment, then run the same initialization command again.",
|
|
723
|
+
message: "Cursor started, but CCE is not ready yet. No port setting needs to be changed."
|
|
724
724
|
};
|
|
725
725
|
}
|
|
726
726
|
const cursorPid = findCursorPidByPort(CDP_PORT) || child.pid || null;
|
|
@@ -740,13 +740,13 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
740
740
|
cursorExecutableSource: cursorExecutable.source,
|
|
741
741
|
needsAction: "retry_initialization",
|
|
742
742
|
retryable: true,
|
|
743
|
-
nextStep: "
|
|
744
|
-
message: `Cursor
|
|
743
|
+
nextStep: "Wait for Cursor to finish opening the project, then run the same initialization command again.",
|
|
744
|
+
message: `Cursor started, but CCE has not confirmed that workspace ${projectPath} is ready. Initialization stopped safely to avoid searching the wrong project.`
|
|
745
745
|
};
|
|
746
746
|
}
|
|
747
747
|
if (projectPath && targetId) PROJECT_TARGETS.set(normalizeProjectKey(projectPath), targetId);
|
|
748
748
|
const windowGuard = effectiveRuntimeMode === "minimal" && cursorPid ? startMinimalWindowGuard(cursorPid) : null;
|
|
749
|
-
const target = projectPath ?
|
|
749
|
+
const target = projectPath ? `opening ${projectPath}` : "restoring the previous workspace";
|
|
750
750
|
const presentation = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid }) : null;
|
|
751
751
|
return {
|
|
752
752
|
ok: true,
|
|
@@ -763,7 +763,7 @@ async function ensureCursorRunningLocal(options = {}) {
|
|
|
763
763
|
cursorExecutableSource: cursorExecutable.source,
|
|
764
764
|
targetId,
|
|
765
765
|
workspaceAction: projectPath ? "launched-project" : "launched-last-workspace",
|
|
766
|
-
message:
|
|
766
|
+
message: `Cursor started (${exe}, ${target}); CDP ${CDP_PORT} is ready.`
|
|
767
767
|
};
|
|
768
768
|
}
|
|
769
769
|
function normalizeProjectKey(projectPath) {
|
package/extensions/index.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { createStdioMcpExtension } from "./mcp-stdio.ts";
|
|
5
|
-
|
|
6
|
-
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
-
const hostCwd = resolve(process.cwd());
|
|
8
|
-
const hostWorkspaceId = hostCwd.replace(/\\/g, "/").toLowerCase();
|
|
9
|
-
|
|
10
|
-
export default createStdioMcpExtension({
|
|
11
|
-
label: "Cursor Bridge",
|
|
12
|
-
clientName: "pi-cursor-bridge",
|
|
13
|
-
packageVersion: "0.1.
|
|
14
|
-
serverName: "cursor-bridge",
|
|
15
|
-
serverScript: join(packageRoot, "dist", "cursor-bridge.mjs"),
|
|
16
|
-
cwd: hostCwd,
|
|
17
|
-
env: {
|
|
18
|
-
// A Pi process can be launched from Codex or Claude Code during testing.
|
|
19
|
-
// Do not let inherited host variables steal Pi's workspace identity.
|
|
20
|
-
CODEX_THREAD_ID: undefined,
|
|
21
|
-
CLAUDE_PROJECT_DIR: undefined,
|
|
22
|
-
CLAUDE_CODE_PROJECT_DIR: undefined,
|
|
23
|
-
CLAUDE_CODE_SESSION_ID: undefined,
|
|
24
|
-
CLAUDE_SESSION_ID: undefined,
|
|
25
|
-
CURSOR_BRIDGE_HOST_ID: `pi:${hostWorkspaceId}`,
|
|
26
|
-
CURSOR_PROJECT_PATH: hostCwd,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createStdioMcpExtension } from "./mcp-stdio.ts";
|
|
5
|
+
|
|
6
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const hostCwd = resolve(process.cwd());
|
|
8
|
+
const hostWorkspaceId = hostCwd.replace(/\\/g, "/").toLowerCase();
|
|
9
|
+
|
|
10
|
+
export default createStdioMcpExtension({
|
|
11
|
+
label: "Cursor Bridge",
|
|
12
|
+
clientName: "pi-cursor-bridge",
|
|
13
|
+
packageVersion: "0.1.3",
|
|
14
|
+
serverName: "cursor-bridge",
|
|
15
|
+
serverScript: join(packageRoot, "dist", "cursor-bridge.mjs"),
|
|
16
|
+
cwd: hostCwd,
|
|
17
|
+
env: {
|
|
18
|
+
// A Pi process can be launched from Codex or Claude Code during testing.
|
|
19
|
+
// Do not let inherited host variables steal Pi's workspace identity.
|
|
20
|
+
CODEX_THREAD_ID: undefined,
|
|
21
|
+
CLAUDE_PROJECT_DIR: undefined,
|
|
22
|
+
CLAUDE_CODE_PROJECT_DIR: undefined,
|
|
23
|
+
CLAUDE_CODE_SESSION_ID: undefined,
|
|
24
|
+
CLAUDE_SESSION_ID: undefined,
|
|
25
|
+
CURSOR_BRIDGE_HOST_ID: `pi:${hostWorkspaceId}`,
|
|
26
|
+
CURSOR_PROJECT_PATH: hostCwd,
|
|
27
|
+
},
|
|
28
|
+
});
|
package/extensions/mcp-stdio.ts
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
|
-
|
|
6
|
-
const DEFAULT_TOOL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
7
|
-
|
|
8
|
-
function stringEnvironment(extra = {}) {
|
|
9
|
-
return Object.fromEntries(
|
|
10
|
-
Object.entries({ ...process.env, ...extra })
|
|
11
|
-
.filter(([, value]) => typeof value === "string"),
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function toolContent(result) {
|
|
16
|
-
const items = Array.isArray(result?.content) ? result.content : [];
|
|
17
|
-
if (items.length === 0) {
|
|
18
|
-
return [{ type: "text", text: JSON.stringify(result ?? null) }];
|
|
19
|
-
}
|
|
20
|
-
return items.map((item) => {
|
|
21
|
-
if (item?.type === "text" && typeof item.text === "string") {
|
|
22
|
-
return { type: "text", text: item.text };
|
|
23
|
-
}
|
|
24
|
-
return { type: "text", text: JSON.stringify(item) };
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createStdioMcpExtension(options) {
|
|
29
|
-
return async function registerStdioMcp(pi) {
|
|
30
|
-
if (!existsSync(options.serverScript)) {
|
|
31
|
-
throw new Error(`${options.label} MCP entrypoint is missing: ${options.serverScript}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const client = new Client(
|
|
35
|
-
{ name: options.clientName, version: options.packageVersion },
|
|
36
|
-
{ capabilities: {} },
|
|
37
|
-
);
|
|
38
|
-
const toolTimeoutMs = Number.isInteger(options.toolTimeoutMs) && options.toolTimeoutMs > 0
|
|
39
|
-
? options.toolTimeoutMs
|
|
40
|
-
: DEFAULT_TOOL_TIMEOUT_MS;
|
|
41
|
-
const transport = new StdioClientTransport({
|
|
42
|
-
command: options.nodeCommand || "node",
|
|
43
|
-
args: [options.serverScript],
|
|
44
|
-
cwd: options.cwd,
|
|
45
|
-
env: stringEnvironment(options.env),
|
|
46
|
-
stderr: "pipe",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
await client.connect(transport);
|
|
51
|
-
const listed = await client.listTools();
|
|
52
|
-
for (const tool of listed.tools || []) {
|
|
53
|
-
if (!tool?.name || !tool?.inputSchema) continue;
|
|
54
|
-
pi.registerTool({
|
|
55
|
-
name: tool.name,
|
|
56
|
-
label: tool.title || tool.name,
|
|
57
|
-
description: tool.description || `${options.label} MCP tool ${tool.name}`,
|
|
58
|
-
parameters: tool.inputSchema,
|
|
59
|
-
async execute(_toolCallId, params, signal) {
|
|
60
|
-
const result = await client.callTool(
|
|
61
|
-
{ name: tool.name, arguments: params || {} },
|
|
62
|
-
undefined,
|
|
63
|
-
{
|
|
64
|
-
signal,
|
|
65
|
-
timeout: toolTimeoutMs,
|
|
66
|
-
maxTotalTimeout: toolTimeoutMs,
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
return {
|
|
70
|
-
content: toolContent(result),
|
|
71
|
-
details: {
|
|
72
|
-
mcpServer: options.serverName,
|
|
73
|
-
mcpTool: tool.name,
|
|
74
|
-
isError: result?.isError === true,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
} catch (error) {
|
|
81
|
-
await client.close().catch(() => {});
|
|
82
|
-
throw new Error(`${options.label} MCP startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let closed = false;
|
|
86
|
-
pi.on("session_shutdown", async () => {
|
|
87
|
-
if (closed) return;
|
|
88
|
-
closed = true;
|
|
89
|
-
await client.close().catch(() => {});
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
}
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TOOL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
function stringEnvironment(extra = {}) {
|
|
9
|
+
return Object.fromEntries(
|
|
10
|
+
Object.entries({ ...process.env, ...extra })
|
|
11
|
+
.filter(([, value]) => typeof value === "string"),
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toolContent(result) {
|
|
16
|
+
const items = Array.isArray(result?.content) ? result.content : [];
|
|
17
|
+
if (items.length === 0) {
|
|
18
|
+
return [{ type: "text", text: JSON.stringify(result ?? null) }];
|
|
19
|
+
}
|
|
20
|
+
return items.map((item) => {
|
|
21
|
+
if (item?.type === "text" && typeof item.text === "string") {
|
|
22
|
+
return { type: "text", text: item.text };
|
|
23
|
+
}
|
|
24
|
+
return { type: "text", text: JSON.stringify(item) };
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createStdioMcpExtension(options) {
|
|
29
|
+
return async function registerStdioMcp(pi) {
|
|
30
|
+
if (!existsSync(options.serverScript)) {
|
|
31
|
+
throw new Error(`${options.label} MCP entrypoint is missing: ${options.serverScript}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const client = new Client(
|
|
35
|
+
{ name: options.clientName, version: options.packageVersion },
|
|
36
|
+
{ capabilities: {} },
|
|
37
|
+
);
|
|
38
|
+
const toolTimeoutMs = Number.isInteger(options.toolTimeoutMs) && options.toolTimeoutMs > 0
|
|
39
|
+
? options.toolTimeoutMs
|
|
40
|
+
: DEFAULT_TOOL_TIMEOUT_MS;
|
|
41
|
+
const transport = new StdioClientTransport({
|
|
42
|
+
command: options.nodeCommand || "node",
|
|
43
|
+
args: [options.serverScript],
|
|
44
|
+
cwd: options.cwd,
|
|
45
|
+
env: stringEnvironment(options.env),
|
|
46
|
+
stderr: "pipe",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await client.connect(transport);
|
|
51
|
+
const listed = await client.listTools();
|
|
52
|
+
for (const tool of listed.tools || []) {
|
|
53
|
+
if (!tool?.name || !tool?.inputSchema) continue;
|
|
54
|
+
pi.registerTool({
|
|
55
|
+
name: tool.name,
|
|
56
|
+
label: tool.title || tool.name,
|
|
57
|
+
description: tool.description || `${options.label} MCP tool ${tool.name}`,
|
|
58
|
+
parameters: tool.inputSchema,
|
|
59
|
+
async execute(_toolCallId, params, signal) {
|
|
60
|
+
const result = await client.callTool(
|
|
61
|
+
{ name: tool.name, arguments: params || {} },
|
|
62
|
+
undefined,
|
|
63
|
+
{
|
|
64
|
+
signal,
|
|
65
|
+
timeout: toolTimeoutMs,
|
|
66
|
+
maxTotalTimeout: toolTimeoutMs,
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
content: toolContent(result),
|
|
71
|
+
details: {
|
|
72
|
+
mcpServer: options.serverName,
|
|
73
|
+
mcpTool: tool.name,
|
|
74
|
+
isError: result?.isError === true,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
await client.close().catch(() => {});
|
|
82
|
+
throw new Error(`${options.label} MCP startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let closed = false;
|
|
86
|
+
pi.on("session_shutdown", async () => {
|
|
87
|
+
if (closed) return;
|
|
88
|
+
closed = true;
|
|
89
|
+
await client.close().catch(() => {});
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cursor-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Use Cursor Context Engine and bounded Cursor Agent execution from the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
},
|
|
48
48
|
"piPackage": {
|
|
49
49
|
"embeddedProduct": "Cursor Bridge",
|
|
50
|
-
"embeddedProductVersion": "5.
|
|
50
|
+
"embeddedProductVersion": "5.5.0"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -37,6 +37,7 @@ Do not submit the same lookup to CCE and another semantic system in parallel. Us
|
|
|
37
37
|
Call `cursor_context_engine` once with the question's real intent. Include a known symbol, subsystem, or path only when it is a useful lead.
|
|
38
38
|
|
|
39
39
|
- Describe the relationship or behavior to establish and the evidence needed.
|
|
40
|
+
- Preserve the language of the user's current substantive request unless the user explicitly asks for another language. Do not infer or persist a different language from the operating system when the conversation already provides a clear signal.
|
|
40
41
|
- Do not prescribe Cursor's internal search sequence, harness, Explore usage, or number of files.
|
|
41
42
|
- Do not invent hidden parameters; the public input is only `query`.
|
|
42
43
|
- Allow a cold or large workspace enough time to complete its serialized Cursor UI turn.
|
|
@@ -50,4 +51,6 @@ Treat CCE output as evidence leads, not final authority.
|
|
|
50
51
|
3. If CCE returns `NOT_FOUND` or names gaps, report those gaps or perform one bounded fallback search; do not guess from framework convention.
|
|
51
52
|
4. Keep edits, final review, tests, and acceptance with the primary agent unless a separate bounded delegation is appropriate.
|
|
52
53
|
|
|
54
|
+
Explain the result in the language of the user's current task unless an explicit language override applies. Preserve `CCE_SEARCH_RESULT`, field names, enum values, `NOT_FOUND`, paths, symbols, line anchors, hashes, IDs, commands, and error/status codes verbatim. When a result was authored in another host task or language, keep the evidence unchanged and explain it in the current task language rather than rewriting the artifact.
|
|
55
|
+
|
|
53
56
|
CCE is strongly prompted and audited for read-only investigation, but it is not a filesystem sandbox. Preserve user changes and normal workspace safety boundaries.
|
|
@@ -56,11 +56,11 @@ For an exact known-file or known-symbol lookup, establish the cheapest determini
|
|
|
56
56
|
|
|
57
57
|
Do not choose parallel execution merely because there are many tasks. When dependency or path relationships are unclear, use `fifo`.
|
|
58
58
|
|
|
59
|
-
## Dispatch a task
|
|
60
|
-
|
|
61
|
-
1. Record the relevant pre-dispatch workspace state so later review can distinguish existing user changes.
|
|
62
|
-
2. Form one independent task envelope per task using [delegation-contract.md](references/delegation-contract.md).
|
|
63
|
-
3. Call `cursor_do` with `background=true`; do not invent a chat-selection parameter.
|
|
59
|
+
## Dispatch a task
|
|
60
|
+
|
|
61
|
+
1. Record the relevant pre-dispatch workspace state so later review can distinguish existing user changes.
|
|
62
|
+
2. Form one independent task envelope per task using [delegation-contract.md](references/delegation-contract.md). Write its narrative instructions in the language of the user's current substantive task unless the user explicitly requests another language. Do not persist an inferred language or replace a clear conversational signal with the host/OS locale.
|
|
63
|
+
3. Call `cursor_do` with `background=true`; do not invent a chat-selection parameter.
|
|
64
64
|
4. Save each returned `task_id`; also save `agent_id` whenever `cursor_status` publishes one.
|
|
65
65
|
5. If a parallel submission does not return a usable `agent_id`, stop expanding the parallel batch and use `fifo` or report the ambiguous state.
|
|
66
66
|
|
|
@@ -72,9 +72,11 @@ The envelope may contain a small number of local implementation `open_questions`
|
|
|
72
72
|
2. Treat `submitting`, `running`, and `collecting` as normal in-progress states. More than two minutes is not itself a failure; wait for an explicit terminal state.
|
|
73
73
|
3. Compare Cursor's claimed work with the real diff, `allowed_paths`, and acceptance contract.
|
|
74
74
|
4. Run risk-proportionate verification in the primary agent. Cursor's response alone cannot support a formal pass, verified state, or governance transition.
|
|
75
|
-
5. Record each task as complete, partial, failed, timed out, or ambiguous before summarizing the batch.
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
5. Record each task as complete, partial, failed, timed out, or ambiguous before summarizing the batch.
|
|
76
|
+
|
|
77
|
+
Report the accepted result in the language of the user's current task. Keep `task_id`, `agent_id`, tool names, states, enum values, paths, commands, hashes, exact permission options, and error/status codes verbatim. If Cursor returned an artifact or report in another language, preserve it and summarize the relevant facts in the current task language.
|
|
78
|
+
|
|
79
|
+
Read [delegation-contract.md](references/delegation-contract.md) for state interpretation and recovery details.
|
|
78
80
|
|
|
79
81
|
## Handle abnormal states
|
|
80
82
|
|
|
@@ -15,11 +15,11 @@ Provide every task independently:
|
|
|
15
15
|
|
|
16
16
|
| Field | Requirement |
|
|
17
17
|
|---|---|
|
|
18
|
-
| `prompt` | State one objective, the necessary context, prohibited actions, and the expected report. Do not ask Cursor to repeat the primary agent's scope decision. |
|
|
18
|
+
| `prompt` | State one objective, the necessary context, prohibited actions, and the expected report. Use the current user-task language unless the user explicitly requests another language. Do not ask Cursor to repeat the primary agent's scope decision. |
|
|
19
19
|
| `execution` | Use only `fifo` or `parallel_agent`. Use `fifo` when safe parallelism cannot be demonstrated. |
|
|
20
20
|
| `read_only` | Use `true` for lookup and analysis; use `false` for any file modification. |
|
|
21
21
|
| `allowed_paths` | Required when `read_only=false`. Provide the smallest workspace-relative path set, with no glob, absolute path, or workspace-escaping `..`. Omit it when `read_only=true`. This is not a filesystem sandbox. |
|
|
22
|
-
| `completion_contract` | State the deliverables, validation commands, permitted incomplete items,
|
|
22
|
+
| `completion_contract` | State the deliverables, validation commands, permitted incomplete items, final report format, and that narrative output should follow the task language. Preserve paths, commands, identifiers, and machine tokens verbatim. |
|
|
23
23
|
| `background` | Default to `true` so the primary agent may continue independent work. |
|
|
24
24
|
|
|
25
25
|
## Routing contract
|