opencara 0.109.0 → 0.112.1
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 +205 -19
- package/dist/claude-acp.js +240 -14
- package/dist/opencara-mcp.js +31 -3
- package/package.json +2 -2
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(),
|
|
@@ -143,6 +152,8 @@ var PairingStatusResponseSchema = z4.union([
|
|
|
143
152
|
var PairingConfirmRequestSchema = z4.object({
|
|
144
153
|
device_name: z4.string().min(1)
|
|
145
154
|
});
|
|
155
|
+
var HOST_PROTOCOL_VERSION = 1;
|
|
156
|
+
var WS_CLOSE_PROTOCOL_TOO_OLD = 4400;
|
|
146
157
|
var SystemInfoSchema = z4.object({
|
|
147
158
|
os: z4.string(),
|
|
148
159
|
// os.platform()
|
|
@@ -172,6 +183,12 @@ var HelloMessageSchema = z4.object({
|
|
|
172
183
|
type: z4.literal("hello"),
|
|
173
184
|
platform: z4.string(),
|
|
174
185
|
version: z4.string(),
|
|
186
|
+
/**
|
|
187
|
+
* HOST_PROTOCOL_VERSION the device speaks. Absent from CLIs published
|
|
188
|
+
* before versioning existed — the server treats absent as 0 and gates
|
|
189
|
+
* on MIN_HOST_PROTOCOL_VERSION.
|
|
190
|
+
*/
|
|
191
|
+
protocolVersion: z4.number().int().nonnegative().optional(),
|
|
175
192
|
capabilities: z4.array(z4.string()).default([]),
|
|
176
193
|
systemInfo: SystemInfoSchema.optional()
|
|
177
194
|
});
|
|
@@ -203,12 +220,22 @@ var RunDoneSchema = z4.object({
|
|
|
203
220
|
var HelloAckSchema = z4.object({
|
|
204
221
|
type: z4.literal("hello-ack"),
|
|
205
222
|
agentHostId: z4.string(),
|
|
206
|
-
deviceName: z4.string()
|
|
223
|
+
deviceName: z4.string(),
|
|
224
|
+
/**
|
|
225
|
+
* Server's HOST_PROTOCOL_VERSION. Optional so old servers (which don't
|
|
226
|
+
* send it) still satisfy new clients' parse; clients use it only to log
|
|
227
|
+
* a version-skew warning, never to gate.
|
|
228
|
+
*/
|
|
229
|
+
protocolVersion: z4.number().int().nonnegative().optional()
|
|
207
230
|
});
|
|
208
231
|
var CancelJobSchema = z4.object({
|
|
209
232
|
type: z4.literal("cancel"),
|
|
210
233
|
runId: z4.string(),
|
|
211
|
-
|
|
234
|
+
// `.catch()` — not a bare enum — so a future server that grows a new
|
|
235
|
+
// reason (e.g. "timeout") degrades on THIS client to "user_stopped"
|
|
236
|
+
// instead of failing the whole frame's parse and silently dropping the
|
|
237
|
+
// cancel, which left the job unkillable on pre-versioning CLIs.
|
|
238
|
+
reason: z4.enum(["user_stopped", "wave_cancelled"]).catch("user_stopped")
|
|
212
239
|
});
|
|
213
240
|
var PingSchema = z4.object({ type: z4.literal("ping") });
|
|
214
241
|
var PongSchema = z4.object({ type: z4.literal("pong") });
|
|
@@ -309,6 +336,13 @@ var DeviceToServerMessageSchema = z4.union([
|
|
|
309
336
|
PongSchema,
|
|
310
337
|
AgentCallRequestSchema
|
|
311
338
|
]);
|
|
339
|
+
var SERVER_TO_DEVICE_TYPES = /* @__PURE__ */ new Set([
|
|
340
|
+
"job",
|
|
341
|
+
"hello-ack",
|
|
342
|
+
"ping",
|
|
343
|
+
"agent-call-result",
|
|
344
|
+
"cancel"
|
|
345
|
+
]);
|
|
312
346
|
var HostRegisterRequestSchema = z4.object({
|
|
313
347
|
hostId: z4.string(),
|
|
314
348
|
hostName: z4.string(),
|
|
@@ -350,6 +384,9 @@ var IssueDetailSchema = IssueSummarySchema.extend({
|
|
|
350
384
|
draftUpdatedAt: z5.string().datetime().nullable()
|
|
351
385
|
});
|
|
352
386
|
|
|
387
|
+
// ../shared/dist/cron.js
|
|
388
|
+
var MAX_STEP_MINUTES = 366 * 24 * 60 + 24 * 60;
|
|
389
|
+
|
|
353
390
|
// src/commands/register.ts
|
|
354
391
|
var POLL_INTERVAL_MS = 2e3;
|
|
355
392
|
async function register(opts = {}) {
|
|
@@ -422,6 +459,23 @@ function sleep(ms) {
|
|
|
422
459
|
|
|
423
460
|
// src/transport/ws-client.ts
|
|
424
461
|
import WebSocket from "ws";
|
|
462
|
+
function classifyServerFrame(raw) {
|
|
463
|
+
let json;
|
|
464
|
+
try {
|
|
465
|
+
json = JSON.parse(raw);
|
|
466
|
+
} catch {
|
|
467
|
+
return { kind: "malformed", error: "not JSON" };
|
|
468
|
+
}
|
|
469
|
+
const type = typeof json === "object" && json !== null && "type" in json ? json.type : void 0;
|
|
470
|
+
if (typeof type === "string" && !SERVER_TO_DEVICE_TYPES.has(type)) {
|
|
471
|
+
return { kind: "unknown-type", type };
|
|
472
|
+
}
|
|
473
|
+
const parsed = ServerToDeviceMessageSchema.safeParse(json);
|
|
474
|
+
if (!parsed.success) {
|
|
475
|
+
return { kind: "malformed", error: parsed.error.message };
|
|
476
|
+
}
|
|
477
|
+
return { kind: "ok", msg: parsed.data };
|
|
478
|
+
}
|
|
425
479
|
var HEARTBEAT_MS = 3e4;
|
|
426
480
|
var WsClient = class {
|
|
427
481
|
constructor(opts) {
|
|
@@ -433,6 +487,8 @@ var WsClient = class {
|
|
|
433
487
|
backoff;
|
|
434
488
|
heartbeat = null;
|
|
435
489
|
stopped = false;
|
|
490
|
+
/** Unknown frame types already warned about (once per type, not per frame). */
|
|
491
|
+
warnedUnknownTypes = /* @__PURE__ */ new Set();
|
|
436
492
|
start() {
|
|
437
493
|
this.connect();
|
|
438
494
|
}
|
|
@@ -461,18 +517,28 @@ var WsClient = class {
|
|
|
461
517
|
this.opts.onOpen?.();
|
|
462
518
|
});
|
|
463
519
|
ws.on("message", (raw) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
520
|
+
const frame = classifyServerFrame(raw.toString());
|
|
521
|
+
if (frame.kind === "unknown-type") {
|
|
522
|
+
if (!this.warnedUnknownTypes.has(frame.type)) {
|
|
523
|
+
this.warnedUnknownTypes.add(frame.type);
|
|
524
|
+
console.warn(
|
|
525
|
+
`[ws] ignoring unknown frame type "${frame.type}" \u2014 the server is likely newer than this CLI; consider \`npm i -g opencara\``
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (frame.kind === "malformed") {
|
|
531
|
+
console.error("[ws] invalid frame:", frame.error);
|
|
469
532
|
return;
|
|
470
533
|
}
|
|
471
|
-
this.opts.onMessage(
|
|
534
|
+
this.opts.onMessage(frame.msg);
|
|
472
535
|
});
|
|
473
536
|
ws.on("close", (code, reasonBuf) => {
|
|
474
537
|
const reason = reasonBuf.toString();
|
|
475
538
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
539
|
+
if (code === WS_CLOSE_PROTOCOL_TOO_OLD) {
|
|
540
|
+
this.stopped = true;
|
|
541
|
+
}
|
|
476
542
|
this.opts.onClose?.(code, reason);
|
|
477
543
|
if (!this.stopped) this.scheduleReconnect();
|
|
478
544
|
});
|
|
@@ -546,6 +612,7 @@ var ACP_METHODS = {
|
|
|
546
612
|
session_load: "session/load",
|
|
547
613
|
session_prompt: "session/prompt",
|
|
548
614
|
session_cancel: "session/cancel",
|
|
615
|
+
session_set_config_option: "session/set_config_option",
|
|
549
616
|
// Client methods (agent → client) — handled minimally in this spike.
|
|
550
617
|
session_update: "session/update",
|
|
551
618
|
session_request_permission: "session/request_permission",
|
|
@@ -601,6 +668,15 @@ var AcpConnection = class {
|
|
|
601
668
|
prompt(req) {
|
|
602
669
|
return this.request(ACP_METHODS.session_prompt, req);
|
|
603
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Select a session config option (e.g. the model) advertised by the agent in
|
|
673
|
+
* the session/new response. pi-acp maps `configId: "model"` onto its model
|
|
674
|
+
* selector. Resolves on success; rejects with the agent's JSON-RPC error
|
|
675
|
+
* (e.g. "Model not found") — callers log and continue rather than fail the run.
|
|
676
|
+
*/
|
|
677
|
+
setConfigOption(req) {
|
|
678
|
+
return this.request(ACP_METHODS.session_set_config_option, req);
|
|
679
|
+
}
|
|
604
680
|
/** Notification (no reply). Used to cancel a turn mid-flight. */
|
|
605
681
|
cancel(sessionId) {
|
|
606
682
|
this.notify(ACP_METHODS.session_cancel, { sessionId });
|
|
@@ -765,6 +841,9 @@ var AcpClient = class {
|
|
|
765
841
|
prompt(req) {
|
|
766
842
|
return this.must().prompt(req);
|
|
767
843
|
}
|
|
844
|
+
setConfigOption(req) {
|
|
845
|
+
return this.must().setConfigOption(req);
|
|
846
|
+
}
|
|
768
847
|
cancel(sessionId) {
|
|
769
848
|
this.must().cancel(sessionId);
|
|
770
849
|
}
|
|
@@ -1152,21 +1231,35 @@ function runAcpJob(opts) {
|
|
|
1152
1231
|
const cwd = spec.cwd ?? process.cwd();
|
|
1153
1232
|
const mcpServers = [host.acpServerEntry()];
|
|
1154
1233
|
const shimSupportsLoad = initResult.agentCapabilities?.loadSession === true;
|
|
1234
|
+
const instructionsExtra = acpSpec.instructionsFile ? { instructionsFile: acpSpec.instructionsFile } : {};
|
|
1155
1235
|
let sessionId;
|
|
1156
1236
|
let resumed = false;
|
|
1237
|
+
let configOptions;
|
|
1157
1238
|
if (acpSpec.priorSessionId && shimSupportsLoad) {
|
|
1158
|
-
await client.loadSession({
|
|
1239
|
+
const loaded = await client.loadSession({
|
|
1159
1240
|
sessionId: acpSpec.priorSessionId,
|
|
1160
1241
|
cwd,
|
|
1161
|
-
mcpServers
|
|
1242
|
+
mcpServers,
|
|
1243
|
+
...instructionsExtra
|
|
1162
1244
|
});
|
|
1163
1245
|
sessionId = acpSpec.priorSessionId;
|
|
1164
1246
|
resumed = true;
|
|
1247
|
+
configOptions = loaded.configOptions;
|
|
1165
1248
|
} else {
|
|
1166
|
-
const session = await client.newSession({ cwd, mcpServers });
|
|
1249
|
+
const session = await client.newSession({ cwd, mcpServers, ...instructionsExtra });
|
|
1167
1250
|
sessionId = session.sessionId;
|
|
1251
|
+
configOptions = session.configOptions;
|
|
1168
1252
|
}
|
|
1169
1253
|
activeSessionId = sessionId;
|
|
1254
|
+
if (acpSpec.model) {
|
|
1255
|
+
await selectAcpModel(
|
|
1256
|
+
client,
|
|
1257
|
+
sessionId,
|
|
1258
|
+
acpSpec.model,
|
|
1259
|
+
configOptions,
|
|
1260
|
+
handlers.onLog
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1170
1263
|
if (cancelRequested) {
|
|
1171
1264
|
try {
|
|
1172
1265
|
client.cancel(sessionId);
|
|
@@ -1232,7 +1325,68 @@ ${turns}`);
|
|
|
1232
1325
|
parts.push(`# Current message
|
|
1233
1326
|
|
|
1234
1327
|
${acp.userPromptMd}`);
|
|
1235
|
-
|
|
1328
|
+
const blocks = [{ type: "text", text: parts.join("\n\n---\n\n") }];
|
|
1329
|
+
for (const img of acp.images ?? []) {
|
|
1330
|
+
blocks.push({ type: "image", data: img.data, mimeType: img.mimeType });
|
|
1331
|
+
}
|
|
1332
|
+
return blocks;
|
|
1333
|
+
}
|
|
1334
|
+
function matchModelValue(requested, values) {
|
|
1335
|
+
const want = requested.trim();
|
|
1336
|
+
if (!want) return void 0;
|
|
1337
|
+
const exact = values.find((v) => v === want);
|
|
1338
|
+
if (exact) return exact;
|
|
1339
|
+
const ci = values.find((v) => v.toLowerCase() === want.toLowerCase());
|
|
1340
|
+
if (ci) return ci;
|
|
1341
|
+
const suffix = `/${want.toLowerCase()}`;
|
|
1342
|
+
return values.find((v) => v.toLowerCase().endsWith(suffix));
|
|
1343
|
+
}
|
|
1344
|
+
async function selectAcpModel(client, sessionId, requested, configOptions, onLog) {
|
|
1345
|
+
const modelOption = configOptions?.find(
|
|
1346
|
+
(o) => o.id === "model" || o.category === "model"
|
|
1347
|
+
);
|
|
1348
|
+
if (!modelOption) {
|
|
1349
|
+
onLog(
|
|
1350
|
+
"stderr",
|
|
1351
|
+
`[acp] model "${requested}" requested but the agent advertised no model option; using its default
|
|
1352
|
+
`
|
|
1353
|
+
);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const values = (modelOption.options ?? []).map((o) => o.value);
|
|
1357
|
+
const target = matchModelValue(requested, values);
|
|
1358
|
+
if (!target) {
|
|
1359
|
+
try {
|
|
1360
|
+
await client.setConfigOption({
|
|
1361
|
+
sessionId,
|
|
1362
|
+
configId: modelOption.id,
|
|
1363
|
+
value: requested.trim()
|
|
1364
|
+
});
|
|
1365
|
+
onLog("stderr", `[acp] selected model ${requested.trim()} (freeform)
|
|
1366
|
+
`);
|
|
1367
|
+
} catch {
|
|
1368
|
+
onLog(
|
|
1369
|
+
"stderr",
|
|
1370
|
+
`[acp] model "${requested}" not among available models [${values.join(", ")}]; using the default
|
|
1371
|
+
`
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (modelOption.currentValue === target) return;
|
|
1377
|
+
try {
|
|
1378
|
+
await client.setConfigOption({
|
|
1379
|
+
sessionId,
|
|
1380
|
+
configId: modelOption.id,
|
|
1381
|
+
value: target
|
|
1382
|
+
});
|
|
1383
|
+
onLog("stderr", `[acp] selected model ${target}
|
|
1384
|
+
`);
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1387
|
+
onLog("stderr", `[acp] model selection failed (${msg}); using the default
|
|
1388
|
+
`);
|
|
1389
|
+
}
|
|
1236
1390
|
}
|
|
1237
1391
|
function createUpdateTranslator(onLog) {
|
|
1238
1392
|
let inThought = false;
|
|
@@ -1312,7 +1466,7 @@ function resolveLocalAcpAdapter(command, args) {
|
|
|
1312
1466
|
}
|
|
1313
1467
|
|
|
1314
1468
|
// src/commands/run.ts
|
|
1315
|
-
var PKG_VERSION = "0.
|
|
1469
|
+
var PKG_VERSION = "0.112.1";
|
|
1316
1470
|
var LOG_FLUSH_MS = 800;
|
|
1317
1471
|
var MAX_CHUNK_SIZE = 4 * 1024;
|
|
1318
1472
|
async function run(opts = {}) {
|
|
@@ -1332,6 +1486,7 @@ async function run(opts = {}) {
|
|
|
1332
1486
|
type: "hello",
|
|
1333
1487
|
platform: platform(),
|
|
1334
1488
|
version: PKG_VERSION,
|
|
1489
|
+
protocolVersion: HOST_PROTOCOL_VERSION,
|
|
1335
1490
|
// Advertise ACP transport support. Pre-v0.30 devices reported
|
|
1336
1491
|
// "agent-call" (the fenced-stdout-block protocol); since the
|
|
1337
1492
|
// legacy path was removed, this version reports "acp" so the
|
|
@@ -1342,6 +1497,14 @@ async function run(opts = {}) {
|
|
|
1342
1497
|
},
|
|
1343
1498
|
onMessage: (msg) => handleServerMessage(msg, client, cfg),
|
|
1344
1499
|
onClose: (code, reason) => {
|
|
1500
|
+
if (code === WS_CLOSE_PROTOCOL_TOO_OLD) {
|
|
1501
|
+
console.error(
|
|
1502
|
+
`[opencara] this CLI's protocol is too old for the orchestrator: ${reason}`
|
|
1503
|
+
);
|
|
1504
|
+
console.error("[opencara] upgrade with: npm i -g opencara");
|
|
1505
|
+
process.exitCode = 1;
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1345
1508
|
console.log(`[opencara] disconnected (code=${code} reason="${reason}")`);
|
|
1346
1509
|
}
|
|
1347
1510
|
});
|
|
@@ -1352,6 +1515,11 @@ var acpControllers = /* @__PURE__ */ new Map();
|
|
|
1352
1515
|
function handleServerMessage(msg, client, _cfg) {
|
|
1353
1516
|
if (msg.type === "hello-ack") {
|
|
1354
1517
|
console.log(`[opencara] acked as ${msg.deviceName} (${msg.agentHostId})`);
|
|
1518
|
+
if (msg.protocolVersion !== void 0 && msg.protocolVersion !== HOST_PROTOCOL_VERSION) {
|
|
1519
|
+
console.warn(
|
|
1520
|
+
`[opencara] protocol skew: server v${msg.protocolVersion}, CLI v${HOST_PROTOCOL_VERSION}` + (msg.protocolVersion > HOST_PROTOCOL_VERSION ? " \u2014 consider `npm i -g opencara`" : "")
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1355
1523
|
return;
|
|
1356
1524
|
}
|
|
1357
1525
|
if (msg.type === "ping") return;
|
|
@@ -1599,7 +1767,7 @@ import {
|
|
|
1599
1767
|
symlinkSync
|
|
1600
1768
|
} from "node:fs";
|
|
1601
1769
|
import { homedir as homedir2 } from "node:os";
|
|
1602
|
-
import { join as join2, sep } from "node:path";
|
|
1770
|
+
import { dirname as dirname3, join as join2, sep } from "node:path";
|
|
1603
1771
|
var OPENCARA_ROOT = join2(homedir2(), ".opencara");
|
|
1604
1772
|
var WORK_ROOT = join2(OPENCARA_ROOT, "work");
|
|
1605
1773
|
var SESSION_ROOT = join2(OPENCARA_ROOT, "sessions");
|
|
@@ -1662,17 +1830,24 @@ function worktreeCreate(args) {
|
|
|
1662
1830
|
const cleanUrl = `https://github.com/${repo}.git`;
|
|
1663
1831
|
mkdirSync2(sessionDir, { recursive: true });
|
|
1664
1832
|
if (cacheDir) {
|
|
1833
|
+
const cacheLockPath = `${cacheDir}.lock`;
|
|
1834
|
+
mkdirSync2(dirname3(cacheLockPath), { recursive: true });
|
|
1665
1835
|
if (existsSync4(join2(cacheDir, ".git"))) {
|
|
1666
|
-
|
|
1836
|
+
gitLocked(cacheDir, ["fetch", "--all", "--prune"], cacheLockPath, gitEnv);
|
|
1667
1837
|
} else {
|
|
1668
1838
|
mkdirSync2(cacheDir, { recursive: true });
|
|
1669
1839
|
try {
|
|
1670
|
-
|
|
1840
|
+
gitLocked(
|
|
1671
1841
|
cacheDir,
|
|
1672
1842
|
["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", cleanUrl, "."],
|
|
1843
|
+
cacheLockPath,
|
|
1673
1844
|
gitEnv
|
|
1674
1845
|
);
|
|
1675
|
-
|
|
1846
|
+
gitLocked(
|
|
1847
|
+
cacheDir,
|
|
1848
|
+
["config", "credential.helper", HELPER_SNIPPET],
|
|
1849
|
+
cacheLockPath
|
|
1850
|
+
);
|
|
1676
1851
|
} catch (err) {
|
|
1677
1852
|
try {
|
|
1678
1853
|
if (!existsSync4(join2(cacheDir, ".git", "HEAD"))) {
|
|
@@ -1684,7 +1859,7 @@ function worktreeCreate(args) {
|
|
|
1684
1859
|
}
|
|
1685
1860
|
}
|
|
1686
1861
|
if (useLfs) {
|
|
1687
|
-
|
|
1862
|
+
gitLocked(cacheDir, ["lfs", "fetch", "--all"], cacheLockPath, gitEnv);
|
|
1688
1863
|
mkdirSync2(join2(cacheDir, ".git", "lfs", "objects"), { recursive: true });
|
|
1689
1864
|
}
|
|
1690
1865
|
}
|
|
@@ -1831,6 +2006,17 @@ function git(cwd, args, env) {
|
|
|
1831
2006
|
env: env ?? process.env
|
|
1832
2007
|
});
|
|
1833
2008
|
}
|
|
2009
|
+
function gitLocked(cwd, args, lockPath, env) {
|
|
2010
|
+
execFileSync(
|
|
2011
|
+
"flock",
|
|
2012
|
+
["--exclusive", lockPath, "git", ...args],
|
|
2013
|
+
{
|
|
2014
|
+
cwd,
|
|
2015
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
2016
|
+
env: env ?? process.env
|
|
2017
|
+
}
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
1834
2020
|
function refExists(cwd, ref) {
|
|
1835
2021
|
try {
|
|
1836
2022
|
execFileSync("git", ["rev-parse", "--verify", "--quiet", ref], {
|
package/dist/claude-acp.js
CHANGED
|
@@ -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,176 @@ function buildClaudeMcpConfig(servers) {
|
|
|
99
101
|
return JSON.stringify({ mcpServers });
|
|
100
102
|
}
|
|
101
103
|
var sessions = /* @__PURE__ */ new Map();
|
|
102
|
-
|
|
104
|
+
var extraClaudeArgs = [];
|
|
105
|
+
function _setExtraClaudeArgsForTest(args) {
|
|
106
|
+
extraClaudeArgs = args;
|
|
107
|
+
}
|
|
108
|
+
function parseModelFromArgs(args) {
|
|
109
|
+
let model;
|
|
110
|
+
for (let i = 0; i < args.length; i++) {
|
|
111
|
+
const a = args[i];
|
|
112
|
+
const eq = /^(?:--model|-m)=(.*)$/.exec(a);
|
|
113
|
+
if (eq && eq[1]) {
|
|
114
|
+
model = eq[1];
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if ((a === "--model" || a === "-m") && args[i + 1] !== void 0) {
|
|
118
|
+
model = args[i + 1];
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return model;
|
|
123
|
+
}
|
|
124
|
+
function modelConfigOptions(state) {
|
|
125
|
+
const effective = state.modelOverride ?? parseModelFromArgs(extraClaudeArgs);
|
|
126
|
+
if (!effective) return void 0;
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
type: "select",
|
|
130
|
+
id: "model",
|
|
131
|
+
category: "model",
|
|
132
|
+
name: "Model",
|
|
133
|
+
currentValue: effective,
|
|
134
|
+
options: [{ value: effective }]
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
function handleSetConfigOption(params) {
|
|
139
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : "";
|
|
140
|
+
const state = sessions.get(sessionId);
|
|
141
|
+
if (!state) {
|
|
142
|
+
throw new Error(`session/set_config_option: unknown session '${sessionId}'`);
|
|
143
|
+
}
|
|
144
|
+
if (params.configId !== "model") {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`session/set_config_option: unknown configId '${String(params.configId)}'`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const value = typeof params.value === "string" ? params.value.trim() : "";
|
|
150
|
+
if (!value) {
|
|
151
|
+
throw new Error("session/set_config_option: value must be a non-empty string");
|
|
152
|
+
}
|
|
153
|
+
state.modelOverride = value;
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
var INSTRUCTIONS_FILE_MAX_BYTES = 64 * 1024;
|
|
157
|
+
function resolveInstructionsFile(cwd, relative) {
|
|
158
|
+
if (!relative || typeof relative !== "string" || relative.length === 0) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (!isAbsolute(cwd)) {
|
|
162
|
+
stderr.write(
|
|
163
|
+
`[claude-acp] instructionsFile skipped: cwd '${cwd}' is not absolute
|
|
164
|
+
`
|
|
165
|
+
);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
if (isAbsolute(relative) || /^[A-Za-z]:[\\/]/.test(relative)) {
|
|
169
|
+
stderr.write(
|
|
170
|
+
`[claude-acp] instructionsFile skipped: '${relative}' must be repo-relative
|
|
171
|
+
`
|
|
172
|
+
);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (relative.split(/[\\/]/).includes("..")) {
|
|
176
|
+
stderr.write(
|
|
177
|
+
`[claude-acp] instructionsFile skipped: '${relative}' contains '..'
|
|
178
|
+
`
|
|
179
|
+
);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const normalizedCwd = normalize(cwd);
|
|
183
|
+
const candidate = pathResolve(normalizedCwd, relative);
|
|
184
|
+
const cwdWithSep = normalizedCwd.endsWith(sep) ? normalizedCwd : `${normalizedCwd}${sep}`;
|
|
185
|
+
if (!candidate.startsWith(cwdWithSep) && candidate !== normalizedCwd) {
|
|
186
|
+
stderr.write(
|
|
187
|
+
`[claude-acp] instructionsFile skipped: '${candidate}' escapes cwd
|
|
188
|
+
`
|
|
189
|
+
);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
let realCwd;
|
|
193
|
+
let realCandidate;
|
|
194
|
+
try {
|
|
195
|
+
realCwd = realpathSync(normalizedCwd);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
stderr.write(
|
|
198
|
+
`[claude-acp] instructionsFile skipped: cwd '${normalizedCwd}' realpath failed: ${err instanceof Error ? err.message : String(err)}
|
|
199
|
+
`
|
|
200
|
+
);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
realCandidate = realpathSync(candidate);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
stderr.write(
|
|
207
|
+
`[claude-acp] instructionsFile skipped: '${relative}' not found in cwd (${err instanceof Error ? err.message : String(err)})
|
|
208
|
+
`
|
|
209
|
+
);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const realCwdWithSep = realCwd.endsWith(sep) ? realCwd : `${realCwd}${sep}`;
|
|
213
|
+
if (!realCandidate.startsWith(realCwdWithSep) && realCandidate !== realCwd) {
|
|
214
|
+
stderr.write(
|
|
215
|
+
`[claude-acp] instructionsFile skipped: '${relative}' resolves to '${realCandidate}' which is outside cwd (symlink escape?)
|
|
216
|
+
`
|
|
217
|
+
);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
let stat;
|
|
221
|
+
try {
|
|
222
|
+
stat = statSync(realCandidate);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
stderr.write(
|
|
225
|
+
`[claude-acp] instructionsFile skipped: '${relative}' stat failed: ${err instanceof Error ? err.message : String(err)}
|
|
226
|
+
`
|
|
227
|
+
);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
if (!stat.isFile()) {
|
|
231
|
+
stderr.write(
|
|
232
|
+
`[claude-acp] instructionsFile skipped: '${relative}' is not a regular file
|
|
233
|
+
`
|
|
234
|
+
);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (stat.size > INSTRUCTIONS_FILE_MAX_BYTES) {
|
|
238
|
+
stderr.write(
|
|
239
|
+
`[claude-acp] instructionsFile skipped: '${relative}' is ${stat.size} bytes (> ${INSTRUCTIONS_FILE_MAX_BYTES} cap)
|
|
240
|
+
`
|
|
241
|
+
);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
let content;
|
|
245
|
+
try {
|
|
246
|
+
content = readFileSync(realCandidate, "utf8");
|
|
247
|
+
} catch (err) {
|
|
248
|
+
stderr.write(
|
|
249
|
+
`[claude-acp] instructionsFile skipped: read failed for '${relative}': ${err instanceof Error ? err.message : String(err)}
|
|
250
|
+
`
|
|
251
|
+
);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return { path: realCandidate, content };
|
|
255
|
+
}
|
|
256
|
+
function buildStreamJsonInput(promptText, images) {
|
|
257
|
+
const content = [];
|
|
258
|
+
if (promptText.length > 0) {
|
|
259
|
+
content.push({ type: "text", text: promptText });
|
|
260
|
+
}
|
|
261
|
+
for (const img of images) {
|
|
262
|
+
content.push({
|
|
263
|
+
type: "image",
|
|
264
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return `${JSON.stringify({
|
|
268
|
+
type: "user",
|
|
269
|
+
message: { role: "user", content }
|
|
270
|
+
})}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
async function runClaudeTurn(sessionId, state, promptText, images, permissionMode) {
|
|
103
274
|
return new Promise((resolve, reject) => {
|
|
104
275
|
const idFlag = state.resume ? "--resume" : "--session-id";
|
|
105
276
|
const args = [
|
|
@@ -114,6 +285,10 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
|
|
|
114
285
|
idFlag,
|
|
115
286
|
sessionId
|
|
116
287
|
];
|
|
288
|
+
const useStreamJsonInput = images.length > 0;
|
|
289
|
+
if (useStreamJsonInput) {
|
|
290
|
+
args.push("--input-format", "stream-json");
|
|
291
|
+
}
|
|
117
292
|
if (permissionMode && permissionMode !== "default") {
|
|
118
293
|
args.push("--permission-mode", permissionMode);
|
|
119
294
|
} else {
|
|
@@ -126,6 +301,19 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
|
|
|
126
301
|
"--strict-mcp-config"
|
|
127
302
|
);
|
|
128
303
|
}
|
|
304
|
+
const resolvedInstructions = resolveInstructionsFile(
|
|
305
|
+
state.cwd,
|
|
306
|
+
state.instructionsFile
|
|
307
|
+
);
|
|
308
|
+
if (resolvedInstructions) {
|
|
309
|
+
args.push("--bare", "--append-system-prompt", resolvedInstructions.content);
|
|
310
|
+
}
|
|
311
|
+
if (extraClaudeArgs.length > 0) {
|
|
312
|
+
args.push(...extraClaudeArgs);
|
|
313
|
+
}
|
|
314
|
+
if (state.modelOverride) {
|
|
315
|
+
args.push("--model", state.modelOverride);
|
|
316
|
+
}
|
|
129
317
|
const child = spawn("claude", args, {
|
|
130
318
|
cwd: state.cwd,
|
|
131
319
|
env: process.env,
|
|
@@ -134,7 +322,9 @@ async function runClaudeTurn(sessionId, state, promptText, permissionMode) {
|
|
|
134
322
|
state.activeChild = child;
|
|
135
323
|
child.stdin.on("error", () => {
|
|
136
324
|
});
|
|
137
|
-
child.stdin.end(
|
|
325
|
+
child.stdin.end(
|
|
326
|
+
useStreamJsonInput ? buildStreamJsonInput(promptText, images) : promptText
|
|
327
|
+
);
|
|
138
328
|
const decoder = new FrameDecoder();
|
|
139
329
|
let resolved = false;
|
|
140
330
|
let stopReason = "end_turn";
|
|
@@ -339,7 +529,9 @@ function handleInitialize(_params) {
|
|
|
339
529
|
// on each turn (see SessionState.mcpServers / runClaudeTurn).
|
|
340
530
|
loadSession: true,
|
|
341
531
|
mcpCapabilities: {},
|
|
342
|
-
|
|
532
|
+
// image: true since #142 — image blocks are forwarded to claude via
|
|
533
|
+
// `--input-format stream-json`. embeddedContext / audio still unsupported.
|
|
534
|
+
promptCapabilities: { embeddedContext: false, image: true, audio: false }
|
|
343
535
|
},
|
|
344
536
|
authMethods: []
|
|
345
537
|
};
|
|
@@ -347,24 +539,37 @@ function handleInitialize(_params) {
|
|
|
347
539
|
function handleNewSession(params) {
|
|
348
540
|
const sessionId = randomUUID();
|
|
349
541
|
const mcpServers = normalizeMcpServers(params.mcpServers);
|
|
350
|
-
|
|
542
|
+
const instructionsFile = normalizeInstructionsFile(params.instructionsFile);
|
|
543
|
+
const state = {
|
|
351
544
|
cwd: params.cwd ?? process.cwd(),
|
|
352
545
|
resume: false,
|
|
353
|
-
...mcpServers.length > 0 ? { mcpServers } : {}
|
|
354
|
-
|
|
355
|
-
|
|
546
|
+
...mcpServers.length > 0 ? { mcpServers } : {},
|
|
547
|
+
...instructionsFile ? { instructionsFile } : {}
|
|
548
|
+
};
|
|
549
|
+
sessions.set(sessionId, state);
|
|
550
|
+
const configOptions = modelConfigOptions(state);
|
|
551
|
+
return { sessionId, ...configOptions ? { configOptions } : {} };
|
|
356
552
|
}
|
|
357
553
|
function handleLoadSession(params) {
|
|
358
554
|
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
359
555
|
throw new Error("session/load: sessionId required");
|
|
360
556
|
}
|
|
361
557
|
const mcpServers = normalizeMcpServers(params.mcpServers);
|
|
362
|
-
|
|
558
|
+
const instructionsFile = normalizeInstructionsFile(params.instructionsFile);
|
|
559
|
+
const state = {
|
|
363
560
|
cwd: params.cwd ?? process.cwd(),
|
|
364
561
|
resume: true,
|
|
365
|
-
...mcpServers.length > 0 ? { mcpServers } : {}
|
|
366
|
-
|
|
367
|
-
|
|
562
|
+
...mcpServers.length > 0 ? { mcpServers } : {},
|
|
563
|
+
...instructionsFile ? { instructionsFile } : {}
|
|
564
|
+
};
|
|
565
|
+
sessions.set(params.sessionId, state);
|
|
566
|
+
const configOptions = modelConfigOptions(state);
|
|
567
|
+
return configOptions ? { configOptions } : {};
|
|
568
|
+
}
|
|
569
|
+
function normalizeInstructionsFile(raw) {
|
|
570
|
+
if (typeof raw !== "string") return void 0;
|
|
571
|
+
const trimmed = raw.trim();
|
|
572
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
368
573
|
}
|
|
369
574
|
async function handlePrompt(params) {
|
|
370
575
|
const state = sessions.get(params.sessionId);
|
|
@@ -372,13 +577,17 @@ async function handlePrompt(params) {
|
|
|
372
577
|
throw new Error(`unknown sessionId: ${params.sessionId}`);
|
|
373
578
|
}
|
|
374
579
|
const promptText = params.prompt.filter((b) => b.type === "text").map((b) => typeof b.text === "string" ? b.text : "").join("\n\n");
|
|
375
|
-
|
|
376
|
-
|
|
580
|
+
const images = params.prompt.filter(
|
|
581
|
+
(b) => b.type === "image" && typeof b.data === "string" && b.data.length > 0 && typeof b.mimeType === "string" && b.mimeType.length > 0
|
|
582
|
+
).map((b) => ({ type: "image", data: b.data, mimeType: b.mimeType }));
|
|
583
|
+
if (promptText.length === 0 && images.length === 0) {
|
|
584
|
+
throw new Error("session/prompt: no text or image content blocks");
|
|
377
585
|
}
|
|
378
586
|
const result = await runClaudeTurn(
|
|
379
587
|
params.sessionId,
|
|
380
588
|
state,
|
|
381
589
|
promptText,
|
|
590
|
+
images,
|
|
382
591
|
params.permissionMode
|
|
383
592
|
);
|
|
384
593
|
state.resume = true;
|
|
@@ -400,6 +609,8 @@ function handleCancel(params) {
|
|
|
400
609
|
}
|
|
401
610
|
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
611
|
if (isMainModule) {
|
|
612
|
+
extraClaudeArgs = process.argv.slice(2);
|
|
613
|
+
Object.freeze(extraClaudeArgs);
|
|
403
614
|
const decoder = new FrameDecoder();
|
|
404
615
|
stdin.setEncoding("utf8");
|
|
405
616
|
stdin.on("data", (chunk) => {
|
|
@@ -438,6 +649,14 @@ async function dispatch(msg) {
|
|
|
438
649
|
case "session/load":
|
|
439
650
|
reply(req.id, handleLoadSession(req.params));
|
|
440
651
|
return;
|
|
652
|
+
case "session/set_config_option":
|
|
653
|
+
reply(
|
|
654
|
+
req.id,
|
|
655
|
+
handleSetConfigOption(
|
|
656
|
+
req.params
|
|
657
|
+
)
|
|
658
|
+
);
|
|
659
|
+
return;
|
|
441
660
|
case "session/prompt": {
|
|
442
661
|
const result = await handlePrompt(req.params);
|
|
443
662
|
reply(req.id, result);
|
|
@@ -452,7 +671,7 @@ async function dispatch(msg) {
|
|
|
452
671
|
}
|
|
453
672
|
} catch (err) {
|
|
454
673
|
const message = err instanceof Error ? err.message : String(err);
|
|
455
|
-
const isParamsError = err instanceof Error && (message.startsWith("session/prompt:") || message.startsWith("session/load:"));
|
|
674
|
+
const isParamsError = err instanceof Error && (message.startsWith("session/prompt:") || message.startsWith("session/load:") || message.startsWith("session/set_config_option:"));
|
|
456
675
|
replyError(
|
|
457
676
|
req.id,
|
|
458
677
|
isParamsError ? JSON_RPC_ERROR_INVALID_PARAMS : JSON_RPC_ERROR_INTERNAL,
|
|
@@ -461,9 +680,16 @@ async function dispatch(msg) {
|
|
|
461
680
|
}
|
|
462
681
|
}
|
|
463
682
|
export {
|
|
683
|
+
_setExtraClaudeArgsForTest,
|
|
684
|
+
buildStreamJsonInput,
|
|
685
|
+
extraClaudeArgs,
|
|
464
686
|
handleInitialize,
|
|
465
687
|
handleLoadSession,
|
|
466
688
|
handleNewSession,
|
|
689
|
+
handleSetConfigOption,
|
|
690
|
+
modelConfigOptions,
|
|
691
|
+
parseModelFromArgs,
|
|
692
|
+
resolveInstructionsFile,
|
|
467
693
|
sessions,
|
|
468
694
|
translateClaudeEvent
|
|
469
695
|
};
|
package/dist/opencara-mcp.js
CHANGED
|
@@ -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(),
|
|
@@ -213,6 +222,12 @@ var HelloMessageSchema = z3.object({
|
|
|
213
222
|
type: z3.literal("hello"),
|
|
214
223
|
platform: z3.string(),
|
|
215
224
|
version: z3.string(),
|
|
225
|
+
/**
|
|
226
|
+
* HOST_PROTOCOL_VERSION the device speaks. Absent from CLIs published
|
|
227
|
+
* before versioning existed — the server treats absent as 0 and gates
|
|
228
|
+
* on MIN_HOST_PROTOCOL_VERSION.
|
|
229
|
+
*/
|
|
230
|
+
protocolVersion: z3.number().int().nonnegative().optional(),
|
|
216
231
|
capabilities: z3.array(z3.string()).default([]),
|
|
217
232
|
systemInfo: SystemInfoSchema.optional()
|
|
218
233
|
});
|
|
@@ -244,12 +259,22 @@ var RunDoneSchema = z3.object({
|
|
|
244
259
|
var HelloAckSchema = z3.object({
|
|
245
260
|
type: z3.literal("hello-ack"),
|
|
246
261
|
agentHostId: z3.string(),
|
|
247
|
-
deviceName: z3.string()
|
|
262
|
+
deviceName: z3.string(),
|
|
263
|
+
/**
|
|
264
|
+
* Server's HOST_PROTOCOL_VERSION. Optional so old servers (which don't
|
|
265
|
+
* send it) still satisfy new clients' parse; clients use it only to log
|
|
266
|
+
* a version-skew warning, never to gate.
|
|
267
|
+
*/
|
|
268
|
+
protocolVersion: z3.number().int().nonnegative().optional()
|
|
248
269
|
});
|
|
249
270
|
var CancelJobSchema = z3.object({
|
|
250
271
|
type: z3.literal("cancel"),
|
|
251
272
|
runId: z3.string(),
|
|
252
|
-
|
|
273
|
+
// `.catch()` — not a bare enum — so a future server that grows a new
|
|
274
|
+
// reason (e.g. "timeout") degrades on THIS client to "user_stopped"
|
|
275
|
+
// instead of failing the whole frame's parse and silently dropping the
|
|
276
|
+
// cancel, which left the job unkillable on pre-versioning CLIs.
|
|
277
|
+
reason: z3.enum(["user_stopped", "wave_cancelled"]).catch("user_stopped")
|
|
253
278
|
});
|
|
254
279
|
var PingSchema = z3.object({ type: z3.literal("ping") });
|
|
255
280
|
var PongSchema = z3.object({ type: z3.literal("pong") });
|
|
@@ -391,6 +416,9 @@ var IssueDetailSchema = IssueSummarySchema.extend({
|
|
|
391
416
|
draftUpdatedAt: z4.string().datetime().nullable()
|
|
392
417
|
});
|
|
393
418
|
|
|
419
|
+
// ../shared/dist/cron.js
|
|
420
|
+
var MAX_STEP_MINUTES = 366 * 24 * 60 + 24 * 60;
|
|
421
|
+
|
|
394
422
|
// src/mcp/tools.ts
|
|
395
423
|
var issueBodySetShape = IssueBodySetCallSchema.omit({
|
|
396
424
|
type: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencara",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.112.1",
|
|
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": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dev": "tsx watch src/bin.ts",
|
|
39
39
|
"start": "node dist/bin.js",
|
|
40
40
|
"typecheck": "tsc -b",
|
|
41
|
-
"test": "node --import tsx --test --test-reporter=spec src/acp/__tests__/*.test.ts src/mcp/__tests__/*.test.ts src/runner/__tests__/*.test.ts src/bin/__tests__/*.test.ts src/commands/__tests__/*.test.ts",
|
|
41
|
+
"test": "node --import tsx --test --test-reporter=spec src/acp/__tests__/*.test.ts src/mcp/__tests__/*.test.ts src/runner/__tests__/*.test.ts src/bin/__tests__/*.test.ts src/commands/__tests__/*.test.ts src/transport/__tests__/*.test.ts",
|
|
42
42
|
"acp:spike": "tsx src/acp/spike.ts",
|
|
43
43
|
"mcp:smoke": "tsx src/mcp/smoke.ts",
|
|
44
44
|
"clean": "rm -rf dist *.tsbuildinfo"
|