opencara 0.105.3 → 0.106.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
@@ -1070,7 +1070,8 @@ function runAcpJob(opts) {
1070
1070
  env: spec.env,
1071
1071
  cwd: spec.cwd
1072
1072
  });
1073
- client.onSessionUpdate((p) => translateUpdate(p.update, handlers.onLog));
1073
+ const translator = createUpdateTranslator(handlers.onLog);
1074
+ client.onSessionUpdate((p) => translator.handle(p.update));
1074
1075
  client.onStderr((chunk) => handlers.onLog("stderr", chunk));
1075
1076
  const controller = {
1076
1077
  onAgentCallResult(msg) {
@@ -1110,6 +1111,7 @@ function runAcpJob(opts) {
1110
1111
  };
1111
1112
  return result;
1112
1113
  } finally {
1114
+ translator.flush();
1113
1115
  bridge.shutdown("acp run ended");
1114
1116
  await Promise.race([
1115
1117
  host.stop().catch(() => void 0),
@@ -1152,37 +1154,61 @@ ${turns}`);
1152
1154
  ${acp.userPromptMd}`);
1153
1155
  return [{ type: "text", text: parts.join("\n\n---\n\n") }];
1154
1156
  }
1155
- function translateUpdate(update, onLog) {
1156
- if (isMessageChunk(update)) {
1157
- if (update.sessionUpdate === "user_message_chunk") {
1158
- return;
1157
+ function createUpdateTranslator(onLog) {
1158
+ let inThought = false;
1159
+ const enterThought = () => {
1160
+ if (!inThought) {
1161
+ onLog("stdout", "\n[think]\n");
1162
+ inThought = true;
1159
1163
  }
1160
- const text = textOfContent(update.content);
1161
- if (!text) return;
1162
- if (update.sessionUpdate === "agent_thought_chunk") {
1163
- onLog("stdout", `[think] ${text}`);
1164
- return;
1164
+ };
1165
+ const leaveThought = () => {
1166
+ if (inThought) {
1167
+ onLog("stdout", "\n[/think]\n");
1168
+ inThought = false;
1165
1169
  }
1166
- onLog("stdout", text);
1167
- return;
1168
- }
1169
- if (isToolCallStart(update)) {
1170
- const status2 = update.status ?? "?";
1171
- onLog("stdout", `
1170
+ };
1171
+ return {
1172
+ handle(update) {
1173
+ if (isMessageChunk(update)) {
1174
+ if (update.sessionUpdate === "user_message_chunk") {
1175
+ return;
1176
+ }
1177
+ const text = textOfContent(update.content);
1178
+ if (!text) return;
1179
+ if (update.sessionUpdate === "agent_thought_chunk") {
1180
+ enterThought();
1181
+ onLog("stdout", text);
1182
+ return;
1183
+ }
1184
+ leaveThought();
1185
+ onLog("stdout", text);
1186
+ return;
1187
+ }
1188
+ if (isToolCallStart(update)) {
1189
+ leaveThought();
1190
+ const status2 = update.status ?? "?";
1191
+ onLog("stdout", `
1172
1192
  [tool] ${update.title} (${status2})
1173
1193
  `);
1174
- return;
1175
- }
1176
- if (isToolCallProgress(update)) {
1177
- const status2 = update.status ?? "?";
1178
- const title = update.title ?? "(tool)";
1179
- onLog("stdout", `
1194
+ return;
1195
+ }
1196
+ if (isToolCallProgress(update)) {
1197
+ leaveThought();
1198
+ const status2 = update.status ?? "?";
1199
+ const title = update.title ?? "(tool)";
1200
+ onLog("stdout", `
1180
1201
  [tool] ${title} \u2192 ${status2}
1181
1202
  `);
1182
- return;
1183
- }
1184
- onLog("stderr", `[acp] unmodeled update: ${update.sessionUpdate}
1203
+ return;
1204
+ }
1205
+ onLog("stderr", `[acp] unmodeled update: ${update.sessionUpdate}
1185
1206
  `);
1207
+ },
1208
+ flush() {
1209
+ leaveThought();
1210
+ }
1211
+ };
1186
1212
  }
1187
1213
  function textOfContent(content) {
1188
1214
  if (content.type !== "text") return "";
@@ -1206,7 +1232,7 @@ function resolveLocalAcpAdapter(command, args) {
1206
1232
  }
1207
1233
 
1208
1234
  // src/commands/run.ts
1209
- var PKG_VERSION = "0.105.3";
1235
+ var PKG_VERSION = "0.106.0";
1210
1236
  var LOG_FLUSH_MS = 800;
1211
1237
  var MAX_CHUNK_SIZE = 4 * 1024;
1212
1238
  async function run(opts = {}) {
@@ -1479,13 +1505,15 @@ import {
1479
1505
  existsSync as existsSync4,
1480
1506
  realpathSync,
1481
1507
  writeFileSync as writeFileSync2,
1482
- renameSync
1508
+ renameSync,
1509
+ symlinkSync
1483
1510
  } from "node:fs";
1484
1511
  import { homedir as homedir2 } from "node:os";
1485
1512
  import { join as join2, sep } from "node:path";
1486
1513
  var OPENCARA_ROOT = join2(homedir2(), ".opencara");
1487
1514
  var WORK_ROOT = join2(OPENCARA_ROOT, "work");
1488
1515
  var SESSION_ROOT = join2(OPENCARA_ROOT, "sessions");
1516
+ var CACHE_ROOT = join2(OPENCARA_ROOT, "cache");
1489
1517
  async function internal(argv) {
1490
1518
  const sub = argv[0];
1491
1519
  const rest = argv.slice(1);
@@ -1528,17 +1556,51 @@ function worktreeCreate(args) {
1528
1556
  if (!key) fail(`invalid --key '${rawKey}'`);
1529
1557
  const sessionDir = join2(SESSION_ROOT, key);
1530
1558
  const checkoutDir = join2(WORK_ROOT, key, "checkout");
1559
+ const useCache = hasFlag(args, "--cache-repo");
1560
+ const useLfs = hasFlag(args, "--lfs");
1561
+ if (useLfs && !useCache) {
1562
+ fail("--lfs requires --cache-repo");
1563
+ }
1564
+ const cacheDir = useCache ? join2(CACHE_ROOT, safeKey(repo)) : null;
1565
+ const gitEnv = useCache && !useLfs ? { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" } : void 0;
1531
1566
  const HELPER_SNIPPET = '!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f';
1532
1567
  const cleanUrl = `https://github.com/${repo}.git`;
1533
1568
  mkdirSync2(sessionDir, { recursive: true });
1569
+ if (cacheDir) {
1570
+ if (existsSync4(join2(cacheDir, ".git"))) {
1571
+ git(cacheDir, ["fetch", "--all", "--prune"], gitEnv);
1572
+ } else {
1573
+ mkdirSync2(cacheDir, { recursive: true });
1574
+ try {
1575
+ git(
1576
+ cacheDir,
1577
+ ["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", cleanUrl, "."],
1578
+ gitEnv
1579
+ );
1580
+ git(cacheDir, ["config", "credential.helper", HELPER_SNIPPET]);
1581
+ } catch (err) {
1582
+ try {
1583
+ if (!existsSync4(join2(cacheDir, ".git", "HEAD"))) {
1584
+ rmSync(cacheDir, { recursive: true, force: true });
1585
+ }
1586
+ } catch {
1587
+ }
1588
+ throw err;
1589
+ }
1590
+ }
1591
+ if (useLfs) {
1592
+ git(cacheDir, ["lfs", "fetch", "--all"], gitEnv);
1593
+ mkdirSync2(join2(cacheDir, ".git", "lfs", "objects"), { recursive: true });
1594
+ }
1595
+ }
1534
1596
  if (existsSync4(join2(checkoutDir, ".git"))) {
1535
- git(checkoutDir, ["fetch", "origin"]);
1597
+ git(checkoutDir, ["fetch", "origin"], gitEnv);
1536
1598
  if (refExists(checkoutDir, `refs/remotes/origin/${branch}`)) {
1537
- git(checkoutDir, ["checkout", "-B", branch, `origin/${branch}`]);
1599
+ git(checkoutDir, ["checkout", "-B", branch, `origin/${branch}`], gitEnv);
1538
1600
  } else if (refExists(checkoutDir, `refs/heads/${branch}`)) {
1539
- git(checkoutDir, ["checkout", branch]);
1601
+ git(checkoutDir, ["checkout", branch], gitEnv);
1540
1602
  } else if (fromBranch) {
1541
- git(checkoutDir, ["checkout", "-B", branch, `origin/${fromBranch}`]);
1603
+ git(checkoutDir, ["checkout", "-B", branch, `origin/${fromBranch}`], gitEnv);
1542
1604
  } else {
1543
1605
  fail(
1544
1606
  `worktree create: '${branch}' missing locally and on origin/, no --from-branch to fall back to`
@@ -1547,18 +1609,29 @@ function worktreeCreate(args) {
1547
1609
  } else {
1548
1610
  mkdirSync2(checkoutDir, { recursive: true });
1549
1611
  const cloneArgs = ["-c", `credential.helper=${HELPER_SNIPPET}`, "clone"];
1612
+ if (cacheDir) {
1613
+ cloneArgs.push("--no-checkout", "--reference", cacheDir);
1614
+ }
1550
1615
  if (fromBranch) {
1551
1616
  cloneArgs.push("--branch", fromBranch);
1552
1617
  }
1553
1618
  cloneArgs.push(cleanUrl, ".");
1554
1619
  try {
1555
- git(checkoutDir, cloneArgs);
1620
+ git(checkoutDir, cloneArgs, gitEnv);
1621
+ if (cacheDir && useLfs) {
1622
+ const checkoutLfsDir = join2(checkoutDir, ".git", "lfs");
1623
+ mkdirSync2(checkoutLfsDir, { recursive: true });
1624
+ symlinkSync(
1625
+ join2(cacheDir, ".git", "lfs", "objects"),
1626
+ join2(checkoutLfsDir, "objects")
1627
+ );
1628
+ }
1556
1629
  if (fromBranch && branch === fromBranch) {
1557
- git(checkoutDir, ["checkout", branch]);
1630
+ git(checkoutDir, ["checkout", branch], gitEnv);
1558
1631
  } else {
1559
- git(checkoutDir, ["checkout", "-b", branch]);
1632
+ git(checkoutDir, ["checkout", "-b", branch], gitEnv);
1560
1633
  }
1561
- git(checkoutDir, ["config", "credential.helper", HELPER_SNIPPET]);
1634
+ git(checkoutDir, ["config", "credential.helper", HELPER_SNIPPET], gitEnv);
1562
1635
  } catch (err) {
1563
1636
  try {
1564
1637
  rmSync(checkoutDir, { recursive: true, force: true });
@@ -1642,8 +1715,12 @@ function worktreeRemove(args) {
1642
1715
  rmSync(resolved, { recursive: true, force: true });
1643
1716
  }
1644
1717
  }
1645
- function git(cwd, args) {
1646
- execFileSync("git", args, { cwd, stdio: ["ignore", "ignore", "inherit"] });
1718
+ function git(cwd, args, env) {
1719
+ execFileSync("git", args, {
1720
+ cwd,
1721
+ stdio: ["ignore", "ignore", "inherit"],
1722
+ env: env ?? process.env
1723
+ });
1647
1724
  }
1648
1725
  function refExists(cwd, ref) {
1649
1726
  try {
@@ -1661,6 +1738,9 @@ function pickFlag(argv, name) {
1661
1738
  if (i === -1) return void 0;
1662
1739
  return argv[i + 1];
1663
1740
  }
1741
+ function hasFlag(argv, name) {
1742
+ return argv.indexOf(name) !== -1;
1743
+ }
1664
1744
  function fail(msg) {
1665
1745
  console.error(msg);
1666
1746
  process.exit(1);
@@ -64,6 +64,7 @@ function notify(method, params) {
64
64
  var sessions = /* @__PURE__ */ new Map();
65
65
  async function runClaudeTurn(sessionId, state, promptText) {
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,7 +74,7 @@ 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
+ idFlag,
77
78
  sessionId,
78
79
  // Headless: no human in the loop to approve tool use. Matches the
79
80
  // legacy `claudeAdapter` posture in agents/kinds.ts.
@@ -205,14 +206,14 @@ function handleInitialize(_params) {
205
206
  }
206
207
  function handleNewSession(params) {
207
208
  const sessionId = randomUUID();
208
- sessions.set(sessionId, { cwd: params.cwd ?? process.cwd() });
209
+ sessions.set(sessionId, { cwd: params.cwd ?? process.cwd(), resume: false });
209
210
  return { sessionId };
210
211
  }
211
212
  function handleLoadSession(params) {
212
213
  if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
213
214
  throw new Error("session/load: sessionId required");
214
215
  }
215
- sessions.set(params.sessionId, { cwd: params.cwd ?? process.cwd() });
216
+ sessions.set(params.sessionId, { cwd: params.cwd ?? process.cwd(), resume: true });
216
217
  return {};
217
218
  }
218
219
  async function handlePrompt(params) {
@@ -225,6 +226,7 @@ async function handlePrompt(params) {
225
226
  throw new Error("session/prompt: no text content blocks");
226
227
  }
227
228
  const result = await runClaudeTurn(params.sessionId, state, promptText);
229
+ state.resume = true;
228
230
  return { stopReason: result.stopReason };
229
231
  }
230
232
  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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.105.3",
3
+ "version": "0.106.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": {