opencara 0.109.0 → 0.110.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,6 +82,12 @@ var AcpHistoryTurnSchema = z3.object({
82
82
  role: z3.enum(["user", "assistant"]),
83
83
  text: z3.string()
84
84
  });
85
+ var AcpImageInputSchema = z3.object({
86
+ /** Base64-encoded image bytes, without the `data:<mime>;base64,` prefix. */
87
+ data: z3.string(),
88
+ /** IANA image MIME type, e.g. `image/png`, `image/jpeg`, `image/webp`. */
89
+ mimeType: z3.string()
90
+ });
85
91
  var AcpPermissionModeSchema = z3.enum([
86
92
  "default",
87
93
  "acceptEdits",
@@ -94,7 +100,10 @@ var AcpSpecSchema = z3.object({
94
100
  history: z3.array(AcpHistoryTurnSchema).default([]),
95
101
  pageContextJson: z3.string().optional(),
96
102
  priorSessionId: z3.string().optional(),
97
- permissionMode: AcpPermissionModeSchema.optional()
103
+ permissionMode: AcpPermissionModeSchema.optional(),
104
+ instructionsFile: z3.string().optional(),
105
+ images: z3.array(AcpImageInputSchema).default([]),
106
+ model: z3.string().optional()
98
107
  });
99
108
  var AgentSpecSchema = z3.object({
100
109
  kind: z3.string(),
@@ -350,6 +359,9 @@ var IssueDetailSchema = IssueSummarySchema.extend({
350
359
  draftUpdatedAt: z5.string().datetime().nullable()
351
360
  });
352
361
 
362
+ // ../shared/dist/cron.js
363
+ var MAX_STEP_MINUTES = 366 * 24 * 60 + 24 * 60;
364
+
353
365
  // src/commands/register.ts
354
366
  var POLL_INTERVAL_MS = 2e3;
355
367
  async function register(opts = {}) {
@@ -546,6 +558,7 @@ var ACP_METHODS = {
546
558
  session_load: "session/load",
547
559
  session_prompt: "session/prompt",
548
560
  session_cancel: "session/cancel",
561
+ session_set_config_option: "session/set_config_option",
549
562
  // Client methods (agent → client) — handled minimally in this spike.
550
563
  session_update: "session/update",
551
564
  session_request_permission: "session/request_permission",
@@ -601,6 +614,15 @@ var AcpConnection = class {
601
614
  prompt(req) {
602
615
  return this.request(ACP_METHODS.session_prompt, req);
603
616
  }
617
+ /**
618
+ * Select a session config option (e.g. the model) advertised by the agent in
619
+ * the session/new response. pi-acp maps `configId: "model"` onto its model
620
+ * selector. Resolves on success; rejects with the agent's JSON-RPC error
621
+ * (e.g. "Model not found") — callers log and continue rather than fail the run.
622
+ */
623
+ setConfigOption(req) {
624
+ return this.request(ACP_METHODS.session_set_config_option, req);
625
+ }
604
626
  /** Notification (no reply). Used to cancel a turn mid-flight. */
605
627
  cancel(sessionId) {
606
628
  this.notify(ACP_METHODS.session_cancel, { sessionId });
@@ -765,6 +787,9 @@ var AcpClient = class {
765
787
  prompt(req) {
766
788
  return this.must().prompt(req);
767
789
  }
790
+ setConfigOption(req) {
791
+ return this.must().setConfigOption(req);
792
+ }
768
793
  cancel(sessionId) {
769
794
  this.must().cancel(sessionId);
770
795
  }
@@ -1152,21 +1177,35 @@ function runAcpJob(opts) {
1152
1177
  const cwd = spec.cwd ?? process.cwd();
1153
1178
  const mcpServers = [host.acpServerEntry()];
1154
1179
  const shimSupportsLoad = initResult.agentCapabilities?.loadSession === true;
1180
+ const instructionsExtra = acpSpec.instructionsFile ? { instructionsFile: acpSpec.instructionsFile } : {};
1155
1181
  let sessionId;
1156
1182
  let resumed = false;
1183
+ let configOptions;
1157
1184
  if (acpSpec.priorSessionId && shimSupportsLoad) {
1158
- await client.loadSession({
1185
+ const loaded = await client.loadSession({
1159
1186
  sessionId: acpSpec.priorSessionId,
1160
1187
  cwd,
1161
- mcpServers
1188
+ mcpServers,
1189
+ ...instructionsExtra
1162
1190
  });
1163
1191
  sessionId = acpSpec.priorSessionId;
1164
1192
  resumed = true;
1193
+ configOptions = loaded.configOptions;
1165
1194
  } else {
1166
- const session = await client.newSession({ cwd, mcpServers });
1195
+ const session = await client.newSession({ cwd, mcpServers, ...instructionsExtra });
1167
1196
  sessionId = session.sessionId;
1197
+ configOptions = session.configOptions;
1168
1198
  }
1169
1199
  activeSessionId = sessionId;
1200
+ if (acpSpec.model) {
1201
+ await selectAcpModel(
1202
+ client,
1203
+ sessionId,
1204
+ acpSpec.model,
1205
+ configOptions,
1206
+ handlers.onLog
1207
+ );
1208
+ }
1170
1209
  if (cancelRequested) {
1171
1210
  try {
1172
1211
  client.cancel(sessionId);
@@ -1232,7 +1271,58 @@ ${turns}`);
1232
1271
  parts.push(`# Current message
1233
1272
 
1234
1273
  ${acp.userPromptMd}`);
1235
- return [{ type: "text", text: parts.join("\n\n---\n\n") }];
1274
+ const blocks = [{ type: "text", text: parts.join("\n\n---\n\n") }];
1275
+ for (const img of acp.images ?? []) {
1276
+ blocks.push({ type: "image", data: img.data, mimeType: img.mimeType });
1277
+ }
1278
+ return blocks;
1279
+ }
1280
+ function matchModelValue(requested, values) {
1281
+ const want = requested.trim();
1282
+ if (!want) return void 0;
1283
+ const exact = values.find((v) => v === want);
1284
+ if (exact) return exact;
1285
+ const ci = values.find((v) => v.toLowerCase() === want.toLowerCase());
1286
+ if (ci) return ci;
1287
+ const suffix = `/${want.toLowerCase()}`;
1288
+ return values.find((v) => v.toLowerCase().endsWith(suffix));
1289
+ }
1290
+ async function selectAcpModel(client, sessionId, requested, configOptions, onLog) {
1291
+ const modelOption = configOptions?.find(
1292
+ (o) => o.id === "model" || o.category === "model"
1293
+ );
1294
+ if (!modelOption) {
1295
+ onLog(
1296
+ "stderr",
1297
+ `[acp] model "${requested}" requested but the agent advertised no model option; using its default
1298
+ `
1299
+ );
1300
+ return;
1301
+ }
1302
+ const values = (modelOption.options ?? []).map((o) => o.value);
1303
+ const target = matchModelValue(requested, values);
1304
+ if (!target) {
1305
+ onLog(
1306
+ "stderr",
1307
+ `[acp] model "${requested}" not among available models [${values.join(", ")}]; using the default
1308
+ `
1309
+ );
1310
+ return;
1311
+ }
1312
+ if (modelOption.currentValue === target) return;
1313
+ try {
1314
+ await client.setConfigOption({
1315
+ sessionId,
1316
+ configId: modelOption.id,
1317
+ value: target
1318
+ });
1319
+ onLog("stderr", `[acp] selected model ${target}
1320
+ `);
1321
+ } catch (err) {
1322
+ const msg = err instanceof Error ? err.message : String(err);
1323
+ onLog("stderr", `[acp] model selection failed (${msg}); using the default
1324
+ `);
1325
+ }
1236
1326
  }
1237
1327
  function createUpdateTranslator(onLog) {
1238
1328
  let inThought = false;
@@ -1312,7 +1402,7 @@ function resolveLocalAcpAdapter(command, args) {
1312
1402
  }
1313
1403
 
1314
1404
  // src/commands/run.ts
1315
- var PKG_VERSION = "0.109.0";
1405
+ var PKG_VERSION = "0.110.0";
1316
1406
  var LOG_FLUSH_MS = 800;
1317
1407
  var MAX_CHUNK_SIZE = 4 * 1024;
1318
1408
  async function run(opts = {}) {
@@ -1599,7 +1689,7 @@ import {
1599
1689
  symlinkSync
1600
1690
  } from "node:fs";
1601
1691
  import { homedir as homedir2 } from "node:os";
1602
- import { join as join2, sep } from "node:path";
1692
+ import { dirname as dirname3, join as join2, sep } from "node:path";
1603
1693
  var OPENCARA_ROOT = join2(homedir2(), ".opencara");
1604
1694
  var WORK_ROOT = join2(OPENCARA_ROOT, "work");
1605
1695
  var SESSION_ROOT = join2(OPENCARA_ROOT, "sessions");
@@ -1662,17 +1752,24 @@ function worktreeCreate(args) {
1662
1752
  const cleanUrl = `https://github.com/${repo}.git`;
1663
1753
  mkdirSync2(sessionDir, { recursive: true });
1664
1754
  if (cacheDir) {
1755
+ const cacheLockPath = `${cacheDir}.lock`;
1756
+ mkdirSync2(dirname3(cacheLockPath), { recursive: true });
1665
1757
  if (existsSync4(join2(cacheDir, ".git"))) {
1666
- git(cacheDir, ["fetch", "--all", "--prune"], gitEnv);
1758
+ gitLocked(cacheDir, ["fetch", "--all", "--prune"], cacheLockPath, gitEnv);
1667
1759
  } else {
1668
1760
  mkdirSync2(cacheDir, { recursive: true });
1669
1761
  try {
1670
- git(
1762
+ gitLocked(
1671
1763
  cacheDir,
1672
1764
  ["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", cleanUrl, "."],
1765
+ cacheLockPath,
1673
1766
  gitEnv
1674
1767
  );
1675
- git(cacheDir, ["config", "credential.helper", HELPER_SNIPPET]);
1768
+ gitLocked(
1769
+ cacheDir,
1770
+ ["config", "credential.helper", HELPER_SNIPPET],
1771
+ cacheLockPath
1772
+ );
1676
1773
  } catch (err) {
1677
1774
  try {
1678
1775
  if (!existsSync4(join2(cacheDir, ".git", "HEAD"))) {
@@ -1684,7 +1781,7 @@ function worktreeCreate(args) {
1684
1781
  }
1685
1782
  }
1686
1783
  if (useLfs) {
1687
- git(cacheDir, ["lfs", "fetch", "--all"], gitEnv);
1784
+ gitLocked(cacheDir, ["lfs", "fetch", "--all"], cacheLockPath, gitEnv);
1688
1785
  mkdirSync2(join2(cacheDir, ".git", "lfs", "objects"), { recursive: true });
1689
1786
  }
1690
1787
  }
@@ -1831,6 +1928,17 @@ function git(cwd, args, env) {
1831
1928
  env: env ?? process.env
1832
1929
  });
1833
1930
  }
1931
+ function gitLocked(cwd, args, lockPath, env) {
1932
+ execFileSync(
1933
+ "flock",
1934
+ ["--exclusive", lockPath, "git", ...args],
1935
+ {
1936
+ cwd,
1937
+ stdio: ["ignore", "ignore", "inherit"],
1938
+ env: env ?? process.env
1939
+ }
1940
+ );
1941
+ }
1834
1942
  function refExists(cwd, ref) {
1835
1943
  try {
1836
1944
  execFileSync("git", ["rev-parse", "--verify", "--quiet", ref], {
@@ -3,6 +3,8 @@
3
3
  // src/bin/claude-acp.ts
4
4
  import { spawn } from "node:child_process";
5
5
  import { randomUUID } from "node:crypto";
6
+ import { readFileSync, realpathSync, statSync } from "node:fs";
7
+ import { isAbsolute, normalize, resolve as pathResolve, sep } from "node:path";
6
8
  import { stdin, stdout, stderr, exit } from "node:process";
7
9
 
8
10
  // src/acp/framing.ts
@@ -99,7 +101,125 @@ function buildClaudeMcpConfig(servers) {
99
101
  return JSON.stringify({ mcpServers });
100
102
  }
101
103
  var sessions = /* @__PURE__ */ new Map();
102
- async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
104
+ var extraClaudeArgs = [];
105
+ var INSTRUCTIONS_FILE_MAX_BYTES = 64 * 1024;
106
+ function resolveInstructionsFile(cwd, relative) {
107
+ if (!relative || typeof relative !== "string" || relative.length === 0) {
108
+ return null;
109
+ }
110
+ if (!isAbsolute(cwd)) {
111
+ stderr.write(
112
+ `[claude-acp] instructionsFile skipped: cwd '${cwd}' is not absolute
113
+ `
114
+ );
115
+ return null;
116
+ }
117
+ if (isAbsolute(relative) || /^[A-Za-z]:[\\/]/.test(relative)) {
118
+ stderr.write(
119
+ `[claude-acp] instructionsFile skipped: '${relative}' must be repo-relative
120
+ `
121
+ );
122
+ return null;
123
+ }
124
+ if (relative.split(/[\\/]/).includes("..")) {
125
+ stderr.write(
126
+ `[claude-acp] instructionsFile skipped: '${relative}' contains '..'
127
+ `
128
+ );
129
+ return null;
130
+ }
131
+ const normalizedCwd = normalize(cwd);
132
+ const candidate = pathResolve(normalizedCwd, relative);
133
+ const cwdWithSep = normalizedCwd.endsWith(sep) ? normalizedCwd : `${normalizedCwd}${sep}`;
134
+ if (!candidate.startsWith(cwdWithSep) && candidate !== normalizedCwd) {
135
+ stderr.write(
136
+ `[claude-acp] instructionsFile skipped: '${candidate}' escapes cwd
137
+ `
138
+ );
139
+ return null;
140
+ }
141
+ let realCwd;
142
+ let realCandidate;
143
+ try {
144
+ realCwd = realpathSync(normalizedCwd);
145
+ } catch (err) {
146
+ stderr.write(
147
+ `[claude-acp] instructionsFile skipped: cwd '${normalizedCwd}' realpath failed: ${err instanceof Error ? err.message : String(err)}
148
+ `
149
+ );
150
+ return null;
151
+ }
152
+ try {
153
+ realCandidate = realpathSync(candidate);
154
+ } catch (err) {
155
+ stderr.write(
156
+ `[claude-acp] instructionsFile skipped: '${relative}' not found in cwd (${err instanceof Error ? err.message : String(err)})
157
+ `
158
+ );
159
+ return null;
160
+ }
161
+ const realCwdWithSep = realCwd.endsWith(sep) ? realCwd : `${realCwd}${sep}`;
162
+ if (!realCandidate.startsWith(realCwdWithSep) && realCandidate !== realCwd) {
163
+ stderr.write(
164
+ `[claude-acp] instructionsFile skipped: '${relative}' resolves to '${realCandidate}' which is outside cwd (symlink escape?)
165
+ `
166
+ );
167
+ return null;
168
+ }
169
+ let stat;
170
+ try {
171
+ stat = statSync(realCandidate);
172
+ } catch (err) {
173
+ stderr.write(
174
+ `[claude-acp] instructionsFile skipped: '${relative}' stat failed: ${err instanceof Error ? err.message : String(err)}
175
+ `
176
+ );
177
+ return null;
178
+ }
179
+ if (!stat.isFile()) {
180
+ stderr.write(
181
+ `[claude-acp] instructionsFile skipped: '${relative}' is not a regular file
182
+ `
183
+ );
184
+ return null;
185
+ }
186
+ if (stat.size > INSTRUCTIONS_FILE_MAX_BYTES) {
187
+ stderr.write(
188
+ `[claude-acp] instructionsFile skipped: '${relative}' is ${stat.size} bytes (> ${INSTRUCTIONS_FILE_MAX_BYTES} cap)
189
+ `
190
+ );
191
+ return null;
192
+ }
193
+ let content;
194
+ try {
195
+ content = readFileSync(realCandidate, "utf8");
196
+ } catch (err) {
197
+ stderr.write(
198
+ `[claude-acp] instructionsFile skipped: read failed for '${relative}': ${err instanceof Error ? err.message : String(err)}
199
+ `
200
+ );
201
+ return null;
202
+ }
203
+ return { path: realCandidate, content };
204
+ }
205
+ function buildStreamJsonInput(promptText, images) {
206
+ const content = [];
207
+ if (promptText.length > 0) {
208
+ content.push({ type: "text", text: promptText });
209
+ }
210
+ for (const img of images) {
211
+ content.push({
212
+ type: "image",
213
+ source: { type: "base64", media_type: img.mimeType, data: img.data }
214
+ });
215
+ }
216
+ return `${JSON.stringify({
217
+ type: "user",
218
+ message: { role: "user", content }
219
+ })}
220
+ `;
221
+ }
222
+ async function runClaudeTurn(sessionId, state, promptText, images, permissionMode) {
103
223
  return new Promise((resolve, reject) => {
104
224
  const idFlag = state.resume ? "--resume" : "--session-id";
105
225
  const args = [
@@ -114,6 +234,10 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
114
234
  idFlag,
115
235
  sessionId
116
236
  ];
237
+ const useStreamJsonInput = images.length > 0;
238
+ if (useStreamJsonInput) {
239
+ args.push("--input-format", "stream-json");
240
+ }
117
241
  if (permissionMode && permissionMode !== "default") {
118
242
  args.push("--permission-mode", permissionMode);
119
243
  } else {
@@ -126,6 +250,16 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
126
250
  "--strict-mcp-config"
127
251
  );
128
252
  }
253
+ const resolvedInstructions = resolveInstructionsFile(
254
+ state.cwd,
255
+ state.instructionsFile
256
+ );
257
+ if (resolvedInstructions) {
258
+ args.push("--bare", "--append-system-prompt", resolvedInstructions.content);
259
+ }
260
+ if (extraClaudeArgs.length > 0) {
261
+ args.push(...extraClaudeArgs);
262
+ }
129
263
  const child = spawn("claude", args, {
130
264
  cwd: state.cwd,
131
265
  env: process.env,
@@ -134,7 +268,9 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
134
268
  state.activeChild = child;
135
269
  child.stdin.on("error", () => {
136
270
  });
137
- child.stdin.end(promptText);
271
+ child.stdin.end(
272
+ useStreamJsonInput ? buildStreamJsonInput(promptText, images) : promptText
273
+ );
138
274
  const decoder = new FrameDecoder();
139
275
  let resolved = false;
140
276
  let stopReason = "end_turn";
@@ -339,7 +475,9 @@ function handleInitialize(_params) {
339
475
  // on each turn (see SessionState.mcpServers / runClaudeTurn).
340
476
  loadSession: true,
341
477
  mcpCapabilities: {},
342
- promptCapabilities: { embeddedContext: false, image: false, audio: false }
478
+ // image: true since #142 image blocks are forwarded to claude via
479
+ // `--input-format stream-json`. embeddedContext / audio still unsupported.
480
+ promptCapabilities: { embeddedContext: false, image: true, audio: false }
343
481
  },
344
482
  authMethods: []
345
483
  };
@@ -347,10 +485,12 @@ function handleInitialize(_params) {
347
485
  function handleNewSession(params) {
348
486
  const sessionId = randomUUID();
349
487
  const mcpServers = normalizeMcpServers(params.mcpServers);
488
+ const instructionsFile = normalizeInstructionsFile(params.instructionsFile);
350
489
  sessions.set(sessionId, {
351
490
  cwd: params.cwd ?? process.cwd(),
352
491
  resume: false,
353
- ...mcpServers.length > 0 ? { mcpServers } : {}
492
+ ...mcpServers.length > 0 ? { mcpServers } : {},
493
+ ...instructionsFile ? { instructionsFile } : {}
354
494
  });
355
495
  return { sessionId };
356
496
  }
@@ -359,26 +499,37 @@ function handleLoadSession(params) {
359
499
  throw new Error("session/load: sessionId required");
360
500
  }
361
501
  const mcpServers = normalizeMcpServers(params.mcpServers);
502
+ const instructionsFile = normalizeInstructionsFile(params.instructionsFile);
362
503
  sessions.set(params.sessionId, {
363
504
  cwd: params.cwd ?? process.cwd(),
364
505
  resume: true,
365
- ...mcpServers.length > 0 ? { mcpServers } : {}
506
+ ...mcpServers.length > 0 ? { mcpServers } : {},
507
+ ...instructionsFile ? { instructionsFile } : {}
366
508
  });
367
509
  return {};
368
510
  }
511
+ function normalizeInstructionsFile(raw) {
512
+ if (typeof raw !== "string") return void 0;
513
+ const trimmed = raw.trim();
514
+ return trimmed.length > 0 ? trimmed : void 0;
515
+ }
369
516
  async function handlePrompt(params) {
370
517
  const state = sessions.get(params.sessionId);
371
518
  if (!state) {
372
519
  throw new Error(`unknown sessionId: ${params.sessionId}`);
373
520
  }
374
521
  const promptText = params.prompt.filter((b) => b.type === "text").map((b) => typeof b.text === "string" ? b.text : "").join("\n\n");
375
- if (promptText.length === 0) {
376
- throw new Error("session/prompt: no text content blocks");
522
+ const images = params.prompt.filter(
523
+ (b) => b.type === "image" && typeof b.data === "string" && b.data.length > 0 && typeof b.mimeType === "string" && b.mimeType.length > 0
524
+ ).map((b) => ({ type: "image", data: b.data, mimeType: b.mimeType }));
525
+ if (promptText.length === 0 && images.length === 0) {
526
+ throw new Error("session/prompt: no text or image content blocks");
377
527
  }
378
528
  const result = await runClaudeTurn(
379
529
  params.sessionId,
380
530
  state,
381
531
  promptText,
532
+ images,
382
533
  params.permissionMode
383
534
  );
384
535
  state.resume = true;
@@ -400,6 +551,8 @@ function handleCancel(params) {
400
551
  }
401
552
  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;
402
553
  if (isMainModule) {
554
+ extraClaudeArgs = process.argv.slice(2);
555
+ Object.freeze(extraClaudeArgs);
403
556
  const decoder = new FrameDecoder();
404
557
  stdin.setEncoding("utf8");
405
558
  stdin.on("data", (chunk) => {
@@ -461,9 +614,12 @@ async function dispatch(msg) {
461
614
  }
462
615
  }
463
616
  export {
617
+ buildStreamJsonInput,
618
+ extraClaudeArgs,
464
619
  handleInitialize,
465
620
  handleLoadSession,
466
621
  handleNewSession,
622
+ resolveInstructionsFile,
467
623
  sessions,
468
624
  translateClaudeEvent
469
625
  };
@@ -123,6 +123,12 @@ var AcpHistoryTurnSchema = z2.object({
123
123
  role: z2.enum(["user", "assistant"]),
124
124
  text: z2.string()
125
125
  });
126
+ var AcpImageInputSchema = z2.object({
127
+ /** Base64-encoded image bytes, without the `data:<mime>;base64,` prefix. */
128
+ data: z2.string(),
129
+ /** IANA image MIME type, e.g. `image/png`, `image/jpeg`, `image/webp`. */
130
+ mimeType: z2.string()
131
+ });
126
132
  var AcpPermissionModeSchema = z2.enum([
127
133
  "default",
128
134
  "acceptEdits",
@@ -135,7 +141,10 @@ var AcpSpecSchema = z2.object({
135
141
  history: z2.array(AcpHistoryTurnSchema).default([]),
136
142
  pageContextJson: z2.string().optional(),
137
143
  priorSessionId: z2.string().optional(),
138
- permissionMode: AcpPermissionModeSchema.optional()
144
+ permissionMode: AcpPermissionModeSchema.optional(),
145
+ instructionsFile: z2.string().optional(),
146
+ images: z2.array(AcpImageInputSchema).default([]),
147
+ model: z2.string().optional()
139
148
  });
140
149
  var AgentSpecSchema = z2.object({
141
150
  kind: z2.string(),
@@ -391,6 +400,9 @@ var IssueDetailSchema = IssueSummarySchema.extend({
391
400
  draftUpdatedAt: z4.string().datetime().nullable()
392
401
  });
393
402
 
403
+ // ../shared/dist/cron.js
404
+ var MAX_STEP_MINUTES = 366 * 24 * 60 + 24 * 60;
405
+
394
406
  // src/mcp/tools.ts
395
407
  var issueBodySetShape = IssueBodySetCallSchema.omit({
396
408
  type: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.109.0",
3
+ "version": "0.110.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": {