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 +119 -11
- package/dist/claude-acp.js +163 -7
- package/dist/opencara-mcp.js +13 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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
|
-
|
|
1758
|
+
gitLocked(cacheDir, ["fetch", "--all", "--prune"], cacheLockPath, gitEnv);
|
|
1667
1759
|
} else {
|
|
1668
1760
|
mkdirSync2(cacheDir, { recursive: true });
|
|
1669
1761
|
try {
|
|
1670
|
-
|
|
1762
|
+
gitLocked(
|
|
1671
1763
|
cacheDir,
|
|
1672
1764
|
["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", cleanUrl, "."],
|
|
1765
|
+
cacheLockPath,
|
|
1673
1766
|
gitEnv
|
|
1674
1767
|
);
|
|
1675
|
-
|
|
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
|
-
|
|
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], {
|
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,125 @@ function buildClaudeMcpConfig(servers) {
|
|
|
99
101
|
return JSON.stringify({ mcpServers });
|
|
100
102
|
}
|
|
101
103
|
var sessions = /* @__PURE__ */ new Map();
|
|
102
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
};
|
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(),
|
|
@@ -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,
|