opencara 0.105.4 → 0.107.0
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/bin.js +240 -17
- package/dist/claude-acp.js +49 -11
- package/dist/opencara-mcp.js +187 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -82,12 +82,19 @@ var AcpHistoryTurnSchema = z3.object({
|
|
|
82
82
|
role: z3.enum(["user", "assistant"]),
|
|
83
83
|
text: z3.string()
|
|
84
84
|
});
|
|
85
|
+
var AcpPermissionModeSchema = z3.enum([
|
|
86
|
+
"default",
|
|
87
|
+
"acceptEdits",
|
|
88
|
+
"plan",
|
|
89
|
+
"bypassPermissions"
|
|
90
|
+
]);
|
|
85
91
|
var AcpSpecSchema = z3.object({
|
|
86
92
|
systemPromptMd: z3.string(),
|
|
87
93
|
userPromptMd: z3.string(),
|
|
88
94
|
history: z3.array(AcpHistoryTurnSchema).default([]),
|
|
89
95
|
pageContextJson: z3.string().optional(),
|
|
90
|
-
priorSessionId: z3.string().optional()
|
|
96
|
+
priorSessionId: z3.string().optional(),
|
|
97
|
+
permissionMode: AcpPermissionModeSchema.optional()
|
|
91
98
|
});
|
|
92
99
|
var AgentSpecSchema = z3.object({
|
|
93
100
|
kind: z3.string(),
|
|
@@ -198,6 +205,11 @@ var HelloAckSchema = z4.object({
|
|
|
198
205
|
agentHostId: z4.string(),
|
|
199
206
|
deviceName: z4.string()
|
|
200
207
|
});
|
|
208
|
+
var CancelJobSchema = z4.object({
|
|
209
|
+
type: z4.literal("cancel"),
|
|
210
|
+
runId: z4.string(),
|
|
211
|
+
reason: z4.enum(["user_stopped", "wave_cancelled"])
|
|
212
|
+
});
|
|
201
213
|
var PingSchema = z4.object({ type: z4.literal("ping") });
|
|
202
214
|
var PongSchema = z4.object({ type: z4.literal("pong") });
|
|
203
215
|
var AgentCallEnvelope = {
|
|
@@ -225,10 +237,56 @@ var TemplateNodeConfigSetCallSchema = z4.object({
|
|
|
225
237
|
nodeId: z4.string().min(1),
|
|
226
238
|
config: z4.record(z4.string(), z4.unknown())
|
|
227
239
|
});
|
|
240
|
+
var KanbanWaveDispatchCallSchema = z4.object({
|
|
241
|
+
...AgentCallEnvelope,
|
|
242
|
+
kind: z4.literal("kanban.wave.dispatch"),
|
|
243
|
+
flowSlug: z4.string().min(1),
|
|
244
|
+
issueNumbers: z4.array(z4.number().int()).min(1).max(10)
|
|
245
|
+
});
|
|
246
|
+
var IssueSubissueCreateCallSchema = z4.object({
|
|
247
|
+
...AgentCallEnvelope,
|
|
248
|
+
kind: z4.literal("issue.subissue.create"),
|
|
249
|
+
parentIssueNumber: z4.number().int(),
|
|
250
|
+
title: z4.string().min(1),
|
|
251
|
+
bodyMd: z4.string(),
|
|
252
|
+
labels: z4.array(z4.string()).optional()
|
|
253
|
+
});
|
|
254
|
+
var IssueCreateCallSchema = z4.object({
|
|
255
|
+
...AgentCallEnvelope,
|
|
256
|
+
kind: z4.literal("issue.create"),
|
|
257
|
+
title: z4.string().min(1),
|
|
258
|
+
bodyMd: z4.string(),
|
|
259
|
+
labels: z4.array(z4.string()).optional()
|
|
260
|
+
});
|
|
261
|
+
var IssueStateSetCallSchema = z4.object({
|
|
262
|
+
...AgentCallEnvelope,
|
|
263
|
+
kind: z4.literal("issue.state.set"),
|
|
264
|
+
issueNumber: z4.number().int(),
|
|
265
|
+
state: z4.enum(["open", "closed"]),
|
|
266
|
+
stateReason: z4.enum(["completed", "not_planned", "reopened"]).nullable().optional()
|
|
267
|
+
});
|
|
268
|
+
var IssueCommentCreateCallSchema = z4.object({
|
|
269
|
+
...AgentCallEnvelope,
|
|
270
|
+
kind: z4.literal("issue.comment.create"),
|
|
271
|
+
issueNumber: z4.number().int(),
|
|
272
|
+
bodyMd: z4.string().min(1)
|
|
273
|
+
});
|
|
274
|
+
var IssueLabelsSetCallSchema = z4.object({
|
|
275
|
+
...AgentCallEnvelope,
|
|
276
|
+
kind: z4.literal("issue.labels.set"),
|
|
277
|
+
issueNumber: z4.number().int(),
|
|
278
|
+
labels: z4.array(z4.string())
|
|
279
|
+
});
|
|
228
280
|
var AgentCallSchema = z4.discriminatedUnion("kind", [
|
|
229
281
|
IssueBodySetCallSchema,
|
|
230
282
|
FlowNodeConfigSetCallSchema,
|
|
231
|
-
TemplateNodeConfigSetCallSchema
|
|
283
|
+
TemplateNodeConfigSetCallSchema,
|
|
284
|
+
KanbanWaveDispatchCallSchema,
|
|
285
|
+
IssueSubissueCreateCallSchema,
|
|
286
|
+
IssueCreateCallSchema,
|
|
287
|
+
IssueStateSetCallSchema,
|
|
288
|
+
IssueCommentCreateCallSchema,
|
|
289
|
+
IssueLabelsSetCallSchema
|
|
232
290
|
]);
|
|
233
291
|
var AgentCallRequestEnvelope = {
|
|
234
292
|
type: z4.literal("agent-call-request"),
|
|
@@ -255,10 +313,56 @@ var TemplateNodeConfigSetCallRequestSchema = z4.object({
|
|
|
255
313
|
nodeId: z4.string().min(1),
|
|
256
314
|
config: z4.record(z4.string(), z4.unknown())
|
|
257
315
|
});
|
|
316
|
+
var KanbanWaveDispatchCallRequestSchema = z4.object({
|
|
317
|
+
...AgentCallRequestEnvelope,
|
|
318
|
+
kind: z4.literal("kanban.wave.dispatch"),
|
|
319
|
+
flowSlug: z4.string().min(1),
|
|
320
|
+
issueNumbers: z4.array(z4.number().int()).min(1).max(10)
|
|
321
|
+
});
|
|
322
|
+
var IssueSubissueCreateCallRequestSchema = z4.object({
|
|
323
|
+
...AgentCallRequestEnvelope,
|
|
324
|
+
kind: z4.literal("issue.subissue.create"),
|
|
325
|
+
parentIssueNumber: z4.number().int(),
|
|
326
|
+
title: z4.string().min(1),
|
|
327
|
+
bodyMd: z4.string(),
|
|
328
|
+
labels: z4.array(z4.string()).optional()
|
|
329
|
+
});
|
|
330
|
+
var IssueCreateCallRequestSchema = z4.object({
|
|
331
|
+
...AgentCallRequestEnvelope,
|
|
332
|
+
kind: z4.literal("issue.create"),
|
|
333
|
+
title: z4.string().min(1),
|
|
334
|
+
bodyMd: z4.string(),
|
|
335
|
+
labels: z4.array(z4.string()).optional()
|
|
336
|
+
});
|
|
337
|
+
var IssueStateSetCallRequestSchema = z4.object({
|
|
338
|
+
...AgentCallRequestEnvelope,
|
|
339
|
+
kind: z4.literal("issue.state.set"),
|
|
340
|
+
issueNumber: z4.number().int(),
|
|
341
|
+
state: z4.enum(["open", "closed"]),
|
|
342
|
+
stateReason: z4.enum(["completed", "not_planned", "reopened"]).nullable().optional()
|
|
343
|
+
});
|
|
344
|
+
var IssueCommentCreateCallRequestSchema = z4.object({
|
|
345
|
+
...AgentCallRequestEnvelope,
|
|
346
|
+
kind: z4.literal("issue.comment.create"),
|
|
347
|
+
issueNumber: z4.number().int(),
|
|
348
|
+
bodyMd: z4.string().min(1)
|
|
349
|
+
});
|
|
350
|
+
var IssueLabelsSetCallRequestSchema = z4.object({
|
|
351
|
+
...AgentCallRequestEnvelope,
|
|
352
|
+
kind: z4.literal("issue.labels.set"),
|
|
353
|
+
issueNumber: z4.number().int(),
|
|
354
|
+
labels: z4.array(z4.string())
|
|
355
|
+
});
|
|
258
356
|
var AgentCallRequestSchema = z4.discriminatedUnion("kind", [
|
|
259
357
|
IssueBodySetCallRequestSchema,
|
|
260
358
|
FlowNodeConfigSetCallRequestSchema,
|
|
261
|
-
TemplateNodeConfigSetCallRequestSchema
|
|
359
|
+
TemplateNodeConfigSetCallRequestSchema,
|
|
360
|
+
KanbanWaveDispatchCallRequestSchema,
|
|
361
|
+
IssueSubissueCreateCallRequestSchema,
|
|
362
|
+
IssueCreateCallRequestSchema,
|
|
363
|
+
IssueStateSetCallRequestSchema,
|
|
364
|
+
IssueCommentCreateCallRequestSchema,
|
|
365
|
+
IssueLabelsSetCallRequestSchema
|
|
262
366
|
]);
|
|
263
367
|
var AgentCallResultSchema = z4.object({
|
|
264
368
|
type: z4.literal("agent-call-result"),
|
|
@@ -273,7 +377,8 @@ var ServerToDeviceMessageSchema = z4.discriminatedUnion("type", [
|
|
|
273
377
|
JobAssignmentSchema,
|
|
274
378
|
HelloAckSchema,
|
|
275
379
|
PingSchema,
|
|
276
|
-
AgentCallResultSchema
|
|
380
|
+
AgentCallResultSchema,
|
|
381
|
+
CancelJobSchema
|
|
277
382
|
]);
|
|
278
383
|
var DeviceToServerMessageSchema = z4.union([
|
|
279
384
|
HelloMessageSchema,
|
|
@@ -1073,9 +1178,35 @@ function runAcpJob(opts) {
|
|
|
1073
1178
|
const translator = createUpdateTranslator(handlers.onLog);
|
|
1074
1179
|
client.onSessionUpdate((p) => translator.handle(p.update));
|
|
1075
1180
|
client.onStderr((chunk) => handlers.onLog("stderr", chunk));
|
|
1181
|
+
let activeSessionId = null;
|
|
1182
|
+
let cancelRequested = false;
|
|
1183
|
+
let forceCloseScheduled = false;
|
|
1184
|
+
const scheduleForceClose = () => {
|
|
1185
|
+
if (forceCloseScheduled) return;
|
|
1186
|
+
forceCloseScheduled = true;
|
|
1187
|
+
setTimeout(() => {
|
|
1188
|
+
client.close(0).catch(() => void 0);
|
|
1189
|
+
}, 2e3);
|
|
1190
|
+
};
|
|
1076
1191
|
const controller = {
|
|
1077
1192
|
onAgentCallResult(msg) {
|
|
1078
1193
|
bridge.onResult(msg);
|
|
1194
|
+
},
|
|
1195
|
+
cancel() {
|
|
1196
|
+
if (cancelRequested) return;
|
|
1197
|
+
cancelRequested = true;
|
|
1198
|
+
if (activeSessionId) {
|
|
1199
|
+
try {
|
|
1200
|
+
client.cancel(activeSessionId);
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
handlers.onLog(
|
|
1203
|
+
"stderr",
|
|
1204
|
+
`[opencara] cancel notify failed: ${err instanceof Error ? err.message : String(err)}
|
|
1205
|
+
`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
scheduleForceClose();
|
|
1079
1210
|
}
|
|
1080
1211
|
};
|
|
1081
1212
|
const promise = (async () => {
|
|
@@ -1102,8 +1233,21 @@ function runAcpJob(opts) {
|
|
|
1102
1233
|
const session = await client.newSession({ cwd, mcpServers });
|
|
1103
1234
|
sessionId = session.sessionId;
|
|
1104
1235
|
}
|
|
1236
|
+
activeSessionId = sessionId;
|
|
1237
|
+
if (cancelRequested) {
|
|
1238
|
+
try {
|
|
1239
|
+
client.cancel(sessionId);
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1242
|
+
result = { exitCode: 1, stopReason: "cancelled", sessionId };
|
|
1243
|
+
return result;
|
|
1244
|
+
}
|
|
1105
1245
|
const prompt = buildPromptContent(acpSpec);
|
|
1106
|
-
const promptResult = await client.prompt({
|
|
1246
|
+
const promptResult = await client.prompt({
|
|
1247
|
+
sessionId,
|
|
1248
|
+
prompt,
|
|
1249
|
+
...acpSpec.permissionMode ? { permissionMode: acpSpec.permissionMode } : {}
|
|
1250
|
+
});
|
|
1107
1251
|
result = {
|
|
1108
1252
|
exitCode: promptResult.stopReason === "end_turn" ? 0 : 1,
|
|
1109
1253
|
stopReason: promptResult.stopReason,
|
|
@@ -1232,7 +1376,7 @@ function resolveLocalAcpAdapter(command, args) {
|
|
|
1232
1376
|
}
|
|
1233
1377
|
|
|
1234
1378
|
// src/commands/run.ts
|
|
1235
|
-
var PKG_VERSION = "0.
|
|
1379
|
+
var PKG_VERSION = "0.107.0";
|
|
1236
1380
|
var LOG_FLUSH_MS = 800;
|
|
1237
1381
|
var MAX_CHUNK_SIZE = 4 * 1024;
|
|
1238
1382
|
async function run(opts = {}) {
|
|
@@ -1283,6 +1427,16 @@ function handleServerMessage(msg, client, _cfg) {
|
|
|
1283
1427
|
acpControllers.get(msg.runId)?.onAgentCallResult(msg);
|
|
1284
1428
|
return;
|
|
1285
1429
|
}
|
|
1430
|
+
if (msg.type === "cancel") {
|
|
1431
|
+
const ctrl = acpControllers.get(msg.runId);
|
|
1432
|
+
if (ctrl) {
|
|
1433
|
+
console.log(
|
|
1434
|
+
`[opencara] job ${msg.runId.slice(-8)} cancel requested (${msg.reason})`
|
|
1435
|
+
);
|
|
1436
|
+
ctrl.cancel();
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1286
1440
|
}
|
|
1287
1441
|
async function executeJob(job, client) {
|
|
1288
1442
|
const runId = job.run.id;
|
|
@@ -1505,13 +1659,15 @@ import {
|
|
|
1505
1659
|
existsSync as existsSync4,
|
|
1506
1660
|
realpathSync,
|
|
1507
1661
|
writeFileSync as writeFileSync2,
|
|
1508
|
-
renameSync
|
|
1662
|
+
renameSync,
|
|
1663
|
+
symlinkSync
|
|
1509
1664
|
} from "node:fs";
|
|
1510
1665
|
import { homedir as homedir2 } from "node:os";
|
|
1511
1666
|
import { join as join2, sep } from "node:path";
|
|
1512
1667
|
var OPENCARA_ROOT = join2(homedir2(), ".opencara");
|
|
1513
1668
|
var WORK_ROOT = join2(OPENCARA_ROOT, "work");
|
|
1514
1669
|
var SESSION_ROOT = join2(OPENCARA_ROOT, "sessions");
|
|
1670
|
+
var CACHE_ROOT = join2(OPENCARA_ROOT, "cache");
|
|
1515
1671
|
async function internal(argv) {
|
|
1516
1672
|
const sub = argv[0];
|
|
1517
1673
|
const rest = argv.slice(1);
|
|
@@ -1554,17 +1710,56 @@ function worktreeCreate(args) {
|
|
|
1554
1710
|
if (!key) fail(`invalid --key '${rawKey}'`);
|
|
1555
1711
|
const sessionDir = join2(SESSION_ROOT, key);
|
|
1556
1712
|
const checkoutDir = join2(WORK_ROOT, key, "checkout");
|
|
1713
|
+
const useCache = hasFlag(args, "--cache-repo");
|
|
1714
|
+
const useLfs = hasFlag(args, "--lfs");
|
|
1715
|
+
if (useLfs && !useCache) {
|
|
1716
|
+
fail("--lfs requires --cache-repo");
|
|
1717
|
+
}
|
|
1718
|
+
if (useLfs && !hasGitLfs()) {
|
|
1719
|
+
fail(
|
|
1720
|
+
"--lfs is set but git-lfs is not installed on this host \u2014 install it (e.g. `apt install git-lfs && git lfs install`) or disable LFS on the agent.worktree.cacheRepo config"
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
const cacheDir = useCache ? join2(CACHE_ROOT, safeKey(repo)) : null;
|
|
1724
|
+
const gitEnv = useCache && !useLfs ? { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" } : void 0;
|
|
1557
1725
|
const HELPER_SNIPPET = '!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f';
|
|
1558
1726
|
const cleanUrl = `https://github.com/${repo}.git`;
|
|
1559
1727
|
mkdirSync2(sessionDir, { recursive: true });
|
|
1728
|
+
if (cacheDir) {
|
|
1729
|
+
if (existsSync4(join2(cacheDir, ".git"))) {
|
|
1730
|
+
git(cacheDir, ["fetch", "--all", "--prune"], gitEnv);
|
|
1731
|
+
} else {
|
|
1732
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
1733
|
+
try {
|
|
1734
|
+
git(
|
|
1735
|
+
cacheDir,
|
|
1736
|
+
["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", cleanUrl, "."],
|
|
1737
|
+
gitEnv
|
|
1738
|
+
);
|
|
1739
|
+
git(cacheDir, ["config", "credential.helper", HELPER_SNIPPET]);
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
try {
|
|
1742
|
+
if (!existsSync4(join2(cacheDir, ".git", "HEAD"))) {
|
|
1743
|
+
rmSync(cacheDir, { recursive: true, force: true });
|
|
1744
|
+
}
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
throw err;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (useLfs) {
|
|
1751
|
+
git(cacheDir, ["lfs", "fetch", "--all"], gitEnv);
|
|
1752
|
+
mkdirSync2(join2(cacheDir, ".git", "lfs", "objects"), { recursive: true });
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1560
1755
|
if (existsSync4(join2(checkoutDir, ".git"))) {
|
|
1561
|
-
git(checkoutDir, ["fetch", "origin"]);
|
|
1756
|
+
git(checkoutDir, ["fetch", "origin"], gitEnv);
|
|
1562
1757
|
if (refExists(checkoutDir, `refs/remotes/origin/${branch}`)) {
|
|
1563
|
-
git(checkoutDir, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
1758
|
+
git(checkoutDir, ["checkout", "-B", branch, `origin/${branch}`], gitEnv);
|
|
1564
1759
|
} else if (refExists(checkoutDir, `refs/heads/${branch}`)) {
|
|
1565
|
-
git(checkoutDir, ["checkout", branch]);
|
|
1760
|
+
git(checkoutDir, ["checkout", branch], gitEnv);
|
|
1566
1761
|
} else if (fromBranch) {
|
|
1567
|
-
git(checkoutDir, ["checkout", "-B", branch, `origin/${fromBranch}`]);
|
|
1762
|
+
git(checkoutDir, ["checkout", "-B", branch, `origin/${fromBranch}`], gitEnv);
|
|
1568
1763
|
} else {
|
|
1569
1764
|
fail(
|
|
1570
1765
|
`worktree create: '${branch}' missing locally and on origin/, no --from-branch to fall back to`
|
|
@@ -1573,18 +1768,29 @@ function worktreeCreate(args) {
|
|
|
1573
1768
|
} else {
|
|
1574
1769
|
mkdirSync2(checkoutDir, { recursive: true });
|
|
1575
1770
|
const cloneArgs = ["-c", `credential.helper=${HELPER_SNIPPET}`, "clone"];
|
|
1771
|
+
if (cacheDir) {
|
|
1772
|
+
cloneArgs.push("--no-checkout", "--reference", cacheDir);
|
|
1773
|
+
}
|
|
1576
1774
|
if (fromBranch) {
|
|
1577
1775
|
cloneArgs.push("--branch", fromBranch);
|
|
1578
1776
|
}
|
|
1579
1777
|
cloneArgs.push(cleanUrl, ".");
|
|
1580
1778
|
try {
|
|
1581
|
-
git(checkoutDir, cloneArgs);
|
|
1779
|
+
git(checkoutDir, cloneArgs, gitEnv);
|
|
1780
|
+
if (cacheDir && useLfs) {
|
|
1781
|
+
const checkoutLfsDir = join2(checkoutDir, ".git", "lfs");
|
|
1782
|
+
mkdirSync2(checkoutLfsDir, { recursive: true });
|
|
1783
|
+
symlinkSync(
|
|
1784
|
+
join2(cacheDir, ".git", "lfs", "objects"),
|
|
1785
|
+
join2(checkoutLfsDir, "objects")
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1582
1788
|
if (fromBranch && branch === fromBranch) {
|
|
1583
|
-
git(checkoutDir, ["checkout", branch]);
|
|
1789
|
+
git(checkoutDir, ["checkout", branch], gitEnv);
|
|
1584
1790
|
} else {
|
|
1585
|
-
git(checkoutDir, ["checkout", "-b", branch]);
|
|
1791
|
+
git(checkoutDir, ["checkout", "-b", branch], gitEnv);
|
|
1586
1792
|
}
|
|
1587
|
-
git(checkoutDir, ["config", "credential.helper", HELPER_SNIPPET]);
|
|
1793
|
+
git(checkoutDir, ["config", "credential.helper", HELPER_SNIPPET], gitEnv);
|
|
1588
1794
|
} catch (err) {
|
|
1589
1795
|
try {
|
|
1590
1796
|
rmSync(checkoutDir, { recursive: true, force: true });
|
|
@@ -1668,8 +1874,12 @@ function worktreeRemove(args) {
|
|
|
1668
1874
|
rmSync(resolved, { recursive: true, force: true });
|
|
1669
1875
|
}
|
|
1670
1876
|
}
|
|
1671
|
-
function git(cwd, args) {
|
|
1672
|
-
execFileSync("git", args, {
|
|
1877
|
+
function git(cwd, args, env) {
|
|
1878
|
+
execFileSync("git", args, {
|
|
1879
|
+
cwd,
|
|
1880
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
1881
|
+
env: env ?? process.env
|
|
1882
|
+
});
|
|
1673
1883
|
}
|
|
1674
1884
|
function refExists(cwd, ref) {
|
|
1675
1885
|
try {
|
|
@@ -1682,11 +1892,24 @@ function refExists(cwd, ref) {
|
|
|
1682
1892
|
return false;
|
|
1683
1893
|
}
|
|
1684
1894
|
}
|
|
1895
|
+
function hasGitLfs() {
|
|
1896
|
+
try {
|
|
1897
|
+
execFileSync("git", ["lfs", "version"], {
|
|
1898
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
1899
|
+
});
|
|
1900
|
+
return true;
|
|
1901
|
+
} catch {
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1685
1905
|
function pickFlag(argv, name) {
|
|
1686
1906
|
const i = argv.indexOf(name);
|
|
1687
1907
|
if (i === -1) return void 0;
|
|
1688
1908
|
return argv[i + 1];
|
|
1689
1909
|
}
|
|
1910
|
+
function hasFlag(argv, name) {
|
|
1911
|
+
return argv.indexOf(name) !== -1;
|
|
1912
|
+
}
|
|
1690
1913
|
function fail(msg) {
|
|
1691
1914
|
console.error(msg);
|
|
1692
1915
|
process.exit(1);
|
package/dist/claude-acp.js
CHANGED
|
@@ -62,8 +62,9 @@ function notify(method, params) {
|
|
|
62
62
|
send({ jsonrpc: "2.0", method, params });
|
|
63
63
|
}
|
|
64
64
|
var sessions = /* @__PURE__ */ new Map();
|
|
65
|
-
async function runClaudeTurn(sessionId, state, promptText) {
|
|
65
|
+
async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
|
|
66
66
|
return new Promise((resolve, reject) => {
|
|
67
|
+
const idFlag = state.resume ? "--resume" : "--session-id";
|
|
67
68
|
const args = [
|
|
68
69
|
"-p",
|
|
69
70
|
"--output-format",
|
|
@@ -73,17 +74,20 @@ async function runClaudeTurn(sessionId, state, promptText) {
|
|
|
73
74
|
// sense only when stdin can be partial too.
|
|
74
75
|
"--include-partial-messages",
|
|
75
76
|
"--verbose",
|
|
76
|
-
|
|
77
|
-
sessionId
|
|
78
|
-
// Headless: no human in the loop to approve tool use. Matches the
|
|
79
|
-
// legacy `claudeAdapter` posture in agents/kinds.ts.
|
|
80
|
-
"--dangerously-skip-permissions"
|
|
77
|
+
idFlag,
|
|
78
|
+
sessionId
|
|
81
79
|
];
|
|
80
|
+
if (permissionMode && permissionMode !== "default") {
|
|
81
|
+
args.push("--permission-mode", permissionMode);
|
|
82
|
+
} else {
|
|
83
|
+
args.push("--dangerously-skip-permissions");
|
|
84
|
+
}
|
|
82
85
|
const child = spawn("claude", args, {
|
|
83
86
|
cwd: state.cwd,
|
|
84
87
|
env: process.env,
|
|
85
88
|
stdio: ["pipe", "pipe", "pipe"]
|
|
86
89
|
});
|
|
90
|
+
state.activeChild = child;
|
|
87
91
|
child.stdin.on("error", () => {
|
|
88
92
|
});
|
|
89
93
|
child.stdin.end(promptText);
|
|
@@ -115,9 +119,14 @@ async function runClaudeTurn(sessionId, state, promptText) {
|
|
|
115
119
|
reject(err);
|
|
116
120
|
}
|
|
117
121
|
});
|
|
118
|
-
child.on("close", (code) => {
|
|
122
|
+
child.on("close", (code, signal) => {
|
|
123
|
+
state.activeChild = null;
|
|
119
124
|
if (!resolved) {
|
|
120
125
|
resolved = true;
|
|
126
|
+
if (signal === "SIGTERM" || signal === "SIGINT") {
|
|
127
|
+
resolve({ stopReason: "cancelled" });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
121
130
|
if (code !== 0) {
|
|
122
131
|
stderr.write(`[claude-acp] claude exited code=${code} without result event
|
|
123
132
|
`);
|
|
@@ -205,14 +214,14 @@ function handleInitialize(_params) {
|
|
|
205
214
|
}
|
|
206
215
|
function handleNewSession(params) {
|
|
207
216
|
const sessionId = randomUUID();
|
|
208
|
-
sessions.set(sessionId, { cwd: params.cwd ?? process.cwd() });
|
|
217
|
+
sessions.set(sessionId, { cwd: params.cwd ?? process.cwd(), resume: false });
|
|
209
218
|
return { sessionId };
|
|
210
219
|
}
|
|
211
220
|
function handleLoadSession(params) {
|
|
212
221
|
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
213
222
|
throw new Error("session/load: sessionId required");
|
|
214
223
|
}
|
|
215
|
-
sessions.set(params.sessionId, { cwd: params.cwd ?? process.cwd() });
|
|
224
|
+
sessions.set(params.sessionId, { cwd: params.cwd ?? process.cwd(), resume: true });
|
|
216
225
|
return {};
|
|
217
226
|
}
|
|
218
227
|
async function handlePrompt(params) {
|
|
@@ -224,9 +233,29 @@ async function handlePrompt(params) {
|
|
|
224
233
|
if (promptText.length === 0) {
|
|
225
234
|
throw new Error("session/prompt: no text content blocks");
|
|
226
235
|
}
|
|
227
|
-
const result = await runClaudeTurn(
|
|
236
|
+
const result = await runClaudeTurn(
|
|
237
|
+
params.sessionId,
|
|
238
|
+
state,
|
|
239
|
+
promptText,
|
|
240
|
+
params.permissionMode
|
|
241
|
+
);
|
|
242
|
+
state.resume = true;
|
|
228
243
|
return { stopReason: result.stopReason };
|
|
229
244
|
}
|
|
245
|
+
function handleCancel(params) {
|
|
246
|
+
const state = sessions.get(params.sessionId);
|
|
247
|
+
if (!state) return;
|
|
248
|
+
const child = state.activeChild;
|
|
249
|
+
if (!child) return;
|
|
250
|
+
try {
|
|
251
|
+
child.kill("SIGTERM");
|
|
252
|
+
} catch (err) {
|
|
253
|
+
stderr.write(
|
|
254
|
+
`[claude-acp] session/cancel kill failed: ${err instanceof Error ? err.message : String(err)}
|
|
255
|
+
`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
230
259
|
var isMainModule = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("claude-acp.ts") === true || process.argv[1]?.endsWith("claude-acp.js") === true;
|
|
231
260
|
if (isMainModule) {
|
|
232
261
|
const decoder = new FrameDecoder();
|
|
@@ -244,7 +273,16 @@ if (isMainModule) {
|
|
|
244
273
|
});
|
|
245
274
|
}
|
|
246
275
|
async function dispatch(msg) {
|
|
247
|
-
if (!("id" in msg) || msg.id == null)
|
|
276
|
+
if (!("id" in msg) || msg.id == null) {
|
|
277
|
+
const notification = msg;
|
|
278
|
+
if (notification.method === "session/cancel") {
|
|
279
|
+
const params = notification.params;
|
|
280
|
+
if (params && typeof params.sessionId === "string") {
|
|
281
|
+
handleCancel(params);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
248
286
|
if ("result" in msg || "error" in msg) return;
|
|
249
287
|
const req = msg;
|
|
250
288
|
try {
|
package/dist/opencara-mcp.js
CHANGED
|
@@ -123,12 +123,19 @@ var AcpHistoryTurnSchema = z2.object({
|
|
|
123
123
|
role: z2.enum(["user", "assistant"]),
|
|
124
124
|
text: z2.string()
|
|
125
125
|
});
|
|
126
|
+
var AcpPermissionModeSchema = z2.enum([
|
|
127
|
+
"default",
|
|
128
|
+
"acceptEdits",
|
|
129
|
+
"plan",
|
|
130
|
+
"bypassPermissions"
|
|
131
|
+
]);
|
|
126
132
|
var AcpSpecSchema = z2.object({
|
|
127
133
|
systemPromptMd: z2.string(),
|
|
128
134
|
userPromptMd: z2.string(),
|
|
129
135
|
history: z2.array(AcpHistoryTurnSchema).default([]),
|
|
130
136
|
pageContextJson: z2.string().optional(),
|
|
131
|
-
priorSessionId: z2.string().optional()
|
|
137
|
+
priorSessionId: z2.string().optional(),
|
|
138
|
+
permissionMode: AcpPermissionModeSchema.optional()
|
|
132
139
|
});
|
|
133
140
|
var AgentSpecSchema = z2.object({
|
|
134
141
|
kind: z2.string(),
|
|
@@ -239,6 +246,11 @@ var HelloAckSchema = z3.object({
|
|
|
239
246
|
agentHostId: z3.string(),
|
|
240
247
|
deviceName: z3.string()
|
|
241
248
|
});
|
|
249
|
+
var CancelJobSchema = z3.object({
|
|
250
|
+
type: z3.literal("cancel"),
|
|
251
|
+
runId: z3.string(),
|
|
252
|
+
reason: z3.enum(["user_stopped", "wave_cancelled"])
|
|
253
|
+
});
|
|
242
254
|
var PingSchema = z3.object({ type: z3.literal("ping") });
|
|
243
255
|
var PongSchema = z3.object({ type: z3.literal("pong") });
|
|
244
256
|
var AgentCallEnvelope = {
|
|
@@ -266,10 +278,56 @@ var TemplateNodeConfigSetCallSchema = z3.object({
|
|
|
266
278
|
nodeId: z3.string().min(1),
|
|
267
279
|
config: z3.record(z3.string(), z3.unknown())
|
|
268
280
|
});
|
|
281
|
+
var KanbanWaveDispatchCallSchema = z3.object({
|
|
282
|
+
...AgentCallEnvelope,
|
|
283
|
+
kind: z3.literal("kanban.wave.dispatch"),
|
|
284
|
+
flowSlug: z3.string().min(1),
|
|
285
|
+
issueNumbers: z3.array(z3.number().int()).min(1).max(10)
|
|
286
|
+
});
|
|
287
|
+
var IssueSubissueCreateCallSchema = z3.object({
|
|
288
|
+
...AgentCallEnvelope,
|
|
289
|
+
kind: z3.literal("issue.subissue.create"),
|
|
290
|
+
parentIssueNumber: z3.number().int(),
|
|
291
|
+
title: z3.string().min(1),
|
|
292
|
+
bodyMd: z3.string(),
|
|
293
|
+
labels: z3.array(z3.string()).optional()
|
|
294
|
+
});
|
|
295
|
+
var IssueCreateCallSchema = z3.object({
|
|
296
|
+
...AgentCallEnvelope,
|
|
297
|
+
kind: z3.literal("issue.create"),
|
|
298
|
+
title: z3.string().min(1),
|
|
299
|
+
bodyMd: z3.string(),
|
|
300
|
+
labels: z3.array(z3.string()).optional()
|
|
301
|
+
});
|
|
302
|
+
var IssueStateSetCallSchema = z3.object({
|
|
303
|
+
...AgentCallEnvelope,
|
|
304
|
+
kind: z3.literal("issue.state.set"),
|
|
305
|
+
issueNumber: z3.number().int(),
|
|
306
|
+
state: z3.enum(["open", "closed"]),
|
|
307
|
+
stateReason: z3.enum(["completed", "not_planned", "reopened"]).nullable().optional()
|
|
308
|
+
});
|
|
309
|
+
var IssueCommentCreateCallSchema = z3.object({
|
|
310
|
+
...AgentCallEnvelope,
|
|
311
|
+
kind: z3.literal("issue.comment.create"),
|
|
312
|
+
issueNumber: z3.number().int(),
|
|
313
|
+
bodyMd: z3.string().min(1)
|
|
314
|
+
});
|
|
315
|
+
var IssueLabelsSetCallSchema = z3.object({
|
|
316
|
+
...AgentCallEnvelope,
|
|
317
|
+
kind: z3.literal("issue.labels.set"),
|
|
318
|
+
issueNumber: z3.number().int(),
|
|
319
|
+
labels: z3.array(z3.string())
|
|
320
|
+
});
|
|
269
321
|
var AgentCallSchema = z3.discriminatedUnion("kind", [
|
|
270
322
|
IssueBodySetCallSchema,
|
|
271
323
|
FlowNodeConfigSetCallSchema,
|
|
272
|
-
TemplateNodeConfigSetCallSchema
|
|
324
|
+
TemplateNodeConfigSetCallSchema,
|
|
325
|
+
KanbanWaveDispatchCallSchema,
|
|
326
|
+
IssueSubissueCreateCallSchema,
|
|
327
|
+
IssueCreateCallSchema,
|
|
328
|
+
IssueStateSetCallSchema,
|
|
329
|
+
IssueCommentCreateCallSchema,
|
|
330
|
+
IssueLabelsSetCallSchema
|
|
273
331
|
]);
|
|
274
332
|
var AgentCallRequestEnvelope = {
|
|
275
333
|
type: z3.literal("agent-call-request"),
|
|
@@ -296,10 +354,56 @@ var TemplateNodeConfigSetCallRequestSchema = z3.object({
|
|
|
296
354
|
nodeId: z3.string().min(1),
|
|
297
355
|
config: z3.record(z3.string(), z3.unknown())
|
|
298
356
|
});
|
|
357
|
+
var KanbanWaveDispatchCallRequestSchema = z3.object({
|
|
358
|
+
...AgentCallRequestEnvelope,
|
|
359
|
+
kind: z3.literal("kanban.wave.dispatch"),
|
|
360
|
+
flowSlug: z3.string().min(1),
|
|
361
|
+
issueNumbers: z3.array(z3.number().int()).min(1).max(10)
|
|
362
|
+
});
|
|
363
|
+
var IssueSubissueCreateCallRequestSchema = z3.object({
|
|
364
|
+
...AgentCallRequestEnvelope,
|
|
365
|
+
kind: z3.literal("issue.subissue.create"),
|
|
366
|
+
parentIssueNumber: z3.number().int(),
|
|
367
|
+
title: z3.string().min(1),
|
|
368
|
+
bodyMd: z3.string(),
|
|
369
|
+
labels: z3.array(z3.string()).optional()
|
|
370
|
+
});
|
|
371
|
+
var IssueCreateCallRequestSchema = z3.object({
|
|
372
|
+
...AgentCallRequestEnvelope,
|
|
373
|
+
kind: z3.literal("issue.create"),
|
|
374
|
+
title: z3.string().min(1),
|
|
375
|
+
bodyMd: z3.string(),
|
|
376
|
+
labels: z3.array(z3.string()).optional()
|
|
377
|
+
});
|
|
378
|
+
var IssueStateSetCallRequestSchema = z3.object({
|
|
379
|
+
...AgentCallRequestEnvelope,
|
|
380
|
+
kind: z3.literal("issue.state.set"),
|
|
381
|
+
issueNumber: z3.number().int(),
|
|
382
|
+
state: z3.enum(["open", "closed"]),
|
|
383
|
+
stateReason: z3.enum(["completed", "not_planned", "reopened"]).nullable().optional()
|
|
384
|
+
});
|
|
385
|
+
var IssueCommentCreateCallRequestSchema = z3.object({
|
|
386
|
+
...AgentCallRequestEnvelope,
|
|
387
|
+
kind: z3.literal("issue.comment.create"),
|
|
388
|
+
issueNumber: z3.number().int(),
|
|
389
|
+
bodyMd: z3.string().min(1)
|
|
390
|
+
});
|
|
391
|
+
var IssueLabelsSetCallRequestSchema = z3.object({
|
|
392
|
+
...AgentCallRequestEnvelope,
|
|
393
|
+
kind: z3.literal("issue.labels.set"),
|
|
394
|
+
issueNumber: z3.number().int(),
|
|
395
|
+
labels: z3.array(z3.string())
|
|
396
|
+
});
|
|
299
397
|
var AgentCallRequestSchema = z3.discriminatedUnion("kind", [
|
|
300
398
|
IssueBodySetCallRequestSchema,
|
|
301
399
|
FlowNodeConfigSetCallRequestSchema,
|
|
302
|
-
TemplateNodeConfigSetCallRequestSchema
|
|
400
|
+
TemplateNodeConfigSetCallRequestSchema,
|
|
401
|
+
KanbanWaveDispatchCallRequestSchema,
|
|
402
|
+
IssueSubissueCreateCallRequestSchema,
|
|
403
|
+
IssueCreateCallRequestSchema,
|
|
404
|
+
IssueStateSetCallRequestSchema,
|
|
405
|
+
IssueCommentCreateCallRequestSchema,
|
|
406
|
+
IssueLabelsSetCallRequestSchema
|
|
303
407
|
]);
|
|
304
408
|
var AgentCallResultSchema = z3.object({
|
|
305
409
|
type: z3.literal("agent-call-result"),
|
|
@@ -314,7 +418,8 @@ var ServerToDeviceMessageSchema = z3.discriminatedUnion("type", [
|
|
|
314
418
|
JobAssignmentSchema,
|
|
315
419
|
HelloAckSchema,
|
|
316
420
|
PingSchema,
|
|
317
|
-
AgentCallResultSchema
|
|
421
|
+
AgentCallResultSchema,
|
|
422
|
+
CancelJobSchema
|
|
318
423
|
]);
|
|
319
424
|
var DeviceToServerMessageSchema = z3.union([
|
|
320
425
|
HelloMessageSchema,
|
|
@@ -383,6 +488,42 @@ var templateNodeConfigSetShape = TemplateNodeConfigSetCallSchema.omit({
|
|
|
383
488
|
callId: true,
|
|
384
489
|
kind: true
|
|
385
490
|
}).shape;
|
|
491
|
+
var kanbanWaveDispatchShape = KanbanWaveDispatchCallSchema.omit({
|
|
492
|
+
type: true,
|
|
493
|
+
runId: true,
|
|
494
|
+
callId: true,
|
|
495
|
+
kind: true
|
|
496
|
+
}).shape;
|
|
497
|
+
var issueSubissueCreateShape = IssueSubissueCreateCallSchema.omit({
|
|
498
|
+
type: true,
|
|
499
|
+
runId: true,
|
|
500
|
+
callId: true,
|
|
501
|
+
kind: true
|
|
502
|
+
}).shape;
|
|
503
|
+
var issueCreateShape = IssueCreateCallSchema.omit({
|
|
504
|
+
type: true,
|
|
505
|
+
runId: true,
|
|
506
|
+
callId: true,
|
|
507
|
+
kind: true
|
|
508
|
+
}).shape;
|
|
509
|
+
var issueStateSetShape = IssueStateSetCallSchema.omit({
|
|
510
|
+
type: true,
|
|
511
|
+
runId: true,
|
|
512
|
+
callId: true,
|
|
513
|
+
kind: true
|
|
514
|
+
}).shape;
|
|
515
|
+
var issueCommentCreateShape = IssueCommentCreateCallSchema.omit({
|
|
516
|
+
type: true,
|
|
517
|
+
runId: true,
|
|
518
|
+
callId: true,
|
|
519
|
+
kind: true
|
|
520
|
+
}).shape;
|
|
521
|
+
var issueLabelsSetShape = IssueLabelsSetCallSchema.omit({
|
|
522
|
+
type: true,
|
|
523
|
+
runId: true,
|
|
524
|
+
callId: true,
|
|
525
|
+
kind: true
|
|
526
|
+
}).shape;
|
|
386
527
|
var TOOLS = [
|
|
387
528
|
{
|
|
388
529
|
name: "opencara_issue_body_set",
|
|
@@ -404,6 +545,48 @@ var TOOLS = [
|
|
|
404
545
|
title: "Update a flow-template draft node's config",
|
|
405
546
|
description: "Replace the config blob of a node in the user's draft of the named flow template. Per-user scope, not per-project. Reject with reason if the template draft isn't owned by the run's user.",
|
|
406
547
|
inputShape: templateNodeConfigSetShape
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: "opencara_kanban_wave_dispatch",
|
|
551
|
+
kind: "kanban.wave.dispatch",
|
|
552
|
+
title: "Dispatch a batch of issues to a flow",
|
|
553
|
+
description: "Dispatch up to 10 issues in parallel to the named project flow. Requires project scope. Reject if the flow does not exist, is disabled, or any of the issue numbers are not in the project. Returns the wave id.",
|
|
554
|
+
inputShape: kanbanWaveDispatchShape
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "opencara_issue_subissue_create",
|
|
558
|
+
kind: "issue.subissue.create",
|
|
559
|
+
title: "Create a GitHub sub-issue under a parent",
|
|
560
|
+
description: "Create a new GitHub issue and link it as a child of the given parent issue via the GraphQL addSubIssue mutation. Requires project scope. Reject if the parent issue is not in the project.",
|
|
561
|
+
inputShape: issueSubissueCreateShape
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: "opencara_issue_create",
|
|
565
|
+
kind: "issue.create",
|
|
566
|
+
title: "Create a top-level GitHub issue",
|
|
567
|
+
description: "Create a new GitHub issue in the run's project with no parent link. Requires project scope. Returns the new issueNumber and nodeId.",
|
|
568
|
+
inputShape: issueCreateShape
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "opencara_issue_state_set",
|
|
572
|
+
kind: "issue.state.set",
|
|
573
|
+
title: "Open or close an existing issue",
|
|
574
|
+
description: "Set an issue's state to open or closed. Optional stateReason: completed | not_planned | reopened. Requires project scope. Reject if the issue is not in the project.",
|
|
575
|
+
inputShape: issueStateSetShape
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "opencara_issue_comment_create",
|
|
579
|
+
kind: "issue.comment.create",
|
|
580
|
+
title: "Post a comment on an issue",
|
|
581
|
+
description: "Post a Markdown comment on the named issue. Comments are not mirrored locally; the comment lives on GitHub. Requires project scope. Reject if the issue is not in the project.",
|
|
582
|
+
inputShape: issueCommentCreateShape
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "opencara_issue_labels_set",
|
|
586
|
+
kind: "issue.labels.set",
|
|
587
|
+
title: "Replace the label set on an issue",
|
|
588
|
+
description: "Set the issue's labels to exactly the listed names (REST setLabels semantics). Any label not in the list is removed; empty array clears all labels. Requires project scope.",
|
|
589
|
+
inputShape: issueLabelsSetShape
|
|
407
590
|
}
|
|
408
591
|
];
|
|
409
592
|
function registerOpencaraTools(server, router) {
|