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 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({ sessionId, 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.105.4";
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, { cwd, stdio: ["ignore", "ignore", "inherit"] });
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);
@@ -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
- "--session-id",
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(params.sessionId, state, promptText);
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) return;
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 {
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.105.4",
3
+ "version": "0.107.0",
4
4
  "description": "OpenCara agent-host CLI: register a machine as an agent host and run dispatched agents.",
5
5
  "license": "MIT",
6
6
  "repository": {