chat-recall 0.7.23 → 0.7.24

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/README.md CHANGED
@@ -248,11 +248,11 @@ receive messages. The installer writes it only for a tool that is installed:
248
248
  | Codex | `~/.codex/hooks.json` | At the next prompt. Approve the hook once with `/hooks` in Codex. |
249
249
  | Antigravity | `~/.gemini/config/hooks.json` | At the next prompt |
250
250
  | Cursor | `~/.cursor/hooks.json` | When the agent stops, as a follow-up prompt |
251
- | opencode | `~/.config/opencode/plugins/chat-recall-messages.js` | At the next prompt |
252
- | pi | `~/.pi/agent/extensions/chat-recall-messages.js` | At the next prompt |
251
+ | opencode | `~/.config/opencode/plugins/chat-recall-messages.js` | Within a few seconds, also when the session is idle |
252
+ | pi | `~/.pi/agent/extensions/chat-recall-messages.js` | Within a few seconds, also when the session is idle |
253
253
 
254
- Only Claude Code wakes an idle session. In the other tools, an idle session
255
- reads its messages when its user types next. `chat-recall doctor` shows the
254
+ Claude Code, opencode and pi wake an idle session. In Codex, Antigravity and Cursor, an
255
+ idle session reads its messages when its user types next. `chat-recall doctor` shows the
256
256
  hook of each tool.
257
257
 
258
258
  ## Sessions that work together
@@ -306,7 +306,7 @@ skips any tool that is not on the machine (`--no-guard` to skip all of them):
306
306
  |---|---|---|
307
307
  | Claude Code | `~/.claude/settings.json`, every profile | `PreToolUse` |
308
308
  | Codex | `~/.codex/hooks.json` | `PreToolUse` |
309
- | Antigravity | `~/.gemini/antigravity-cli/hooks.json` | `PreToolUse` — the one tool with a real `ask` verdict, so it can prompt |
309
+ | Antigravity | `~/.gemini/config/hooks.json` | `PreToolUse` — the one tool with a real `ask` verdict, so it can prompt |
310
310
  | Cursor | `~/.cursor/hooks.json` | `beforeShellExecution` + `afterFileEdit` |
311
311
  | OpenCode | `~/.config/opencode/plugins/chat-recall-guard.js` | `tool.execute.before` |
312
312
 
package/dist/cli.js CHANGED
@@ -9502,19 +9502,19 @@ function mapJsonStrings(line, fn, escapedOnly) {
9502
9502
  const q = line.indexOf('"', i);
9503
9503
  if (q < 0)
9504
9504
  return out + line.slice(i);
9505
- let j = q + 1;
9506
- while (j < line.length) {
9507
- const c = line.charCodeAt(j);
9505
+ let j2 = q + 1;
9506
+ while (j2 < line.length) {
9507
+ const c = line.charCodeAt(j2);
9508
9508
  if (c === 92)
9509
- j += 2;
9509
+ j2 += 2;
9510
9510
  else if (c === 34)
9511
9511
  break;
9512
9512
  else
9513
- j++;
9513
+ j2++;
9514
9514
  }
9515
- const token = line.slice(q, j + 1);
9515
+ const token = line.slice(q, j2 + 1);
9516
9516
  out += line.slice(i, q);
9517
- i = j + 1;
9517
+ i = j2 + 1;
9518
9518
  const body2 = token.slice(1, -1);
9519
9519
  const escaped = body2.includes("\\");
9520
9520
  if (escapedOnly && !escaped) {
@@ -9785,6 +9785,130 @@ var init_sqlite_reader = __esm({
9785
9785
  }
9786
9786
  });
9787
9787
 
9788
+ // ../engine/dist/core/backends/opencode-v2.js
9789
+ function hasTable(db, name) {
9790
+ return !!db.prepare("SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = ?").get(name);
9791
+ }
9792
+ function columnsOf(db, table) {
9793
+ return new Set(db.prepare(`SELECT name FROM pragma_table_info('${table}', 'main')`).all().map((r) => r.name));
9794
+ }
9795
+ function installOpencodeV2Views(db) {
9796
+ if (!hasTable(db, "session_v2") || !hasTable(db, "session_message"))
9797
+ return false;
9798
+ const v2Cols = columnsOf(db, "session_v2");
9799
+ const sessionCols = SESSION_COLUMNS.map((c) => v2Cols.has(c) ? c : `NULL AS ${c}`).join(", ");
9800
+ const hasV1 = hasTable(db, "message") && hasTable(db, "part");
9801
+ const v1Messages = hasV1 ? `SELECT id, session_id, time_created, time_updated, data FROM main.message WHERE session_id NOT IN (${V2_SESSIONS}) UNION ALL ` : "";
9802
+ const v1Parts = hasV1 ? `SELECT id, message_id, session_id, time_created, time_updated, data FROM main.part WHERE session_id NOT IN (${V2_SESSIONS}) UNION ALL ` : "";
9803
+ db.exec(`
9804
+ CREATE TEMP VIEW IF NOT EXISTS session AS SELECT ${sessionCols} FROM main.session_v2;
9805
+
9806
+ CREATE TEMP VIEW IF NOT EXISTS message AS
9807
+ ${v1Messages}
9808
+ SELECT m.id, m.session_id, m.time_created, m.time_updated,
9809
+ CASE m.type
9810
+ WHEN 'user' THEN json_object('role', 'user', 'time', ${j("$.time")})
9811
+ ELSE json_object(
9812
+ 'role', 'assistant',
9813
+ 'agent', json_extract(m.data, '$.agent'),
9814
+ 'modelID', json_extract(m.data, '$.model.id'),
9815
+ 'providerID', json_extract(m.data, '$.model.providerID'),
9816
+ 'cost', coalesce(json_extract(m.data, '$.cost'), 0),
9817
+ 'tokens', ${j("$.tokens")},
9818
+ 'time', ${j("$.time")})
9819
+ END AS data
9820
+ FROM main.session_message m WHERE m.type IN ('user', 'assistant');
9821
+
9822
+ CREATE TEMP VIEW IF NOT EXISTS part AS
9823
+ ${v1Parts}
9824
+ SELECT m.id || ':text', m.id, m.session_id, m.time_created, m.time_updated,
9825
+ json_object('type', 'text', 'text', json_extract(m.data, '$.text'))
9826
+ FROM main.session_message m
9827
+ WHERE m.type = 'user' AND json_extract(m.data, '$.text') IS NOT NULL
9828
+ UNION ALL
9829
+ SELECT m.id || ':' || c.key, m.id, m.session_id, m.time_created + c.key, m.time_updated,
9830
+ CASE json_extract(c.value, '$.type')
9831
+ WHEN 'tool' THEN json_object(
9832
+ 'type', 'tool',
9833
+ 'tool', json_extract(c.value, '$.name'),
9834
+ 'callID', json_extract(c.value, '$.id'),
9835
+ 'state', json_object(
9836
+ 'status', json_extract(c.value, '$.state.status'),
9837
+ 'input', json(coalesce(json_extract(c.value, '$.state.input'), '{}')),
9838
+ 'output', (SELECT group_concat(json_extract(o.value, '$.text'), char(10))
9839
+ FROM json_each(c.value, '$.state.content') o
9840
+ WHERE json_extract(o.value, '$.type') = 'text')))
9841
+ ELSE json_object('type', json_extract(c.value, '$.type'), 'text', json_extract(c.value, '$.text'))
9842
+ END
9843
+ FROM main.session_message m, json_each(m.data, '$.content') c
9844
+ WHERE m.type = 'assistant'
9845
+ UNION ALL
9846
+ SELECT m.id || ':step', m.id, m.session_id,
9847
+ coalesce(json_extract(m.data, '$.time.completed'), m.time_created), m.time_updated,
9848
+ json_object('type', 'step-finish', 'cost', coalesce(json_extract(m.data, '$.cost'), 0), 'tokens', ${j("$.tokens")})
9849
+ FROM main.session_message m WHERE m.type = 'assistant';
9850
+ `);
9851
+ return true;
9852
+ }
9853
+ function openOpencodeDb(path) {
9854
+ const db = openSqliteReadonly(path);
9855
+ if (!db)
9856
+ return null;
9857
+ try {
9858
+ installOpencodeV2Views(db);
9859
+ } catch {
9860
+ }
9861
+ return db;
9862
+ }
9863
+ function openOpencodeDbOrThrow(path) {
9864
+ const db = openSqliteReadonlyOrThrow(path);
9865
+ try {
9866
+ installOpencodeV2Views(db);
9867
+ } catch {
9868
+ }
9869
+ return db;
9870
+ }
9871
+ var SESSION_COLUMNS, j, V2_SESSIONS;
9872
+ var init_opencode_v2 = __esm({
9873
+ "../engine/dist/core/backends/opencode-v2.js"() {
9874
+ "use strict";
9875
+ init_sqlite_reader();
9876
+ SESSION_COLUMNS = [
9877
+ "id",
9878
+ "project_id",
9879
+ "parent_id",
9880
+ "slug",
9881
+ "directory",
9882
+ "title",
9883
+ "version",
9884
+ "share_url",
9885
+ "summary_additions",
9886
+ "summary_deletions",
9887
+ "summary_files",
9888
+ "summary_diffs",
9889
+ "revert",
9890
+ "permission",
9891
+ "time_created",
9892
+ "time_updated",
9893
+ "time_compacting",
9894
+ "time_archived",
9895
+ "workspace_id",
9896
+ "path",
9897
+ "agent",
9898
+ "model",
9899
+ "cost",
9900
+ "tokens_input",
9901
+ "tokens_output",
9902
+ "tokens_reasoning",
9903
+ "tokens_cache_read",
9904
+ "tokens_cache_write",
9905
+ "metadata"
9906
+ ];
9907
+ j = (path, fallback = "'{}'") => `json(coalesce(json_extract(m.data, '${path}'), ${fallback}))`;
9908
+ V2_SESSIONS = "SELECT session_id FROM main.session_message";
9909
+ }
9910
+ });
9911
+
9788
9912
  // ../engine/dist/core/backends/opencode.js
9789
9913
  var opencode_exports = {};
9790
9914
  __export(opencode_exports, {
@@ -9795,13 +9919,13 @@ import { existsSync as existsSync11 } from "fs";
9795
9919
  import { homedir as homedir5 } from "os";
9796
9920
  import { dirname as dirname9, join as join12 } from "path";
9797
9921
  function openReadonly(path) {
9798
- return openSqliteReadonly(path);
9922
+ return openOpencodeDb(path);
9799
9923
  }
9800
9924
  var PREFIX, OpencodeBackend, opencodeBackend;
9801
9925
  var init_opencode = __esm({
9802
9926
  "../engine/dist/core/backends/opencode.js"() {
9803
9927
  "use strict";
9804
- init_sqlite_reader();
9928
+ init_opencode_v2();
9805
9929
  init_tool_paths();
9806
9930
  init_session_outcome();
9807
9931
  init_session_git();
@@ -10092,9 +10216,9 @@ var init_opencode = __esm({
10092
10216
  return [];
10093
10217
  const parts = db.prepare(`
10094
10218
  SELECT p.id, p.message_id, p.time_created, p.data, m.data AS msg_data
10095
- FROM part p JOIN message m ON m.id = p.message_id
10219
+ FROM part p JOIN (SELECT id, data FROM message WHERE session_id = ?) m ON m.id = p.message_id
10096
10220
  WHERE p.session_id = ? ORDER BY p.time_created ASC
10097
- `).all(ocId);
10221
+ `).all(ocId, ocId);
10098
10222
  const events = [];
10099
10223
  let lineNum = 0;
10100
10224
  for (const p of parts) {
@@ -10310,17 +10434,17 @@ var init_opencode = __esm({
10310
10434
  if (!existsSync11(dbPath))
10311
10435
  return null;
10312
10436
  const realId = this.toRawId(id);
10313
- const db = openSqliteReadonlyOrThrow(dbPath);
10437
+ const db = openOpencodeDbOrThrow(dbPath);
10314
10438
  try {
10315
10439
  const session = db.prepare(`SELECT * FROM session WHERE id = ?`).get(realId);
10316
10440
  if (!session)
10317
10441
  return null;
10318
10442
  const rows = db.prepare(`
10319
10443
  SELECT m.id AS message_id, m.data AS message_data, p.id AS part_id, p.data AS part_data, p.time_created
10320
- FROM part p JOIN message m ON p.message_id = m.id
10444
+ FROM part p JOIN (SELECT id, data FROM message WHERE session_id = ?) m ON p.message_id = m.id
10321
10445
  WHERE p.session_id = ?
10322
10446
  ORDER BY p.time_created ASC, p.id ASC
10323
- `).all(realId);
10447
+ `).all(realId, realId);
10324
10448
  const lines = [JSON.stringify({ kind: "session", row: session })];
10325
10449
  for (const r of rows)
10326
10450
  lines.push(JSON.stringify({ kind: "part", row: r }));
@@ -15492,19 +15616,19 @@ function runTrufflehog(binary, filePath, opts = {}) {
15492
15616
  if (!line.trim())
15493
15617
  continue;
15494
15618
  try {
15495
- const j = JSON.parse(line);
15496
- const raw = j.Raw || "";
15619
+ const j2 = JSON.parse(line);
15620
+ const raw = j2.Raw || "";
15497
15621
  if (looksLikeUuid(raw))
15498
15622
  continue;
15499
15623
  out.push({
15500
- rule: j.DetectorName || "unknown",
15501
- line: j.SourceMetadata?.Data?.Filesystem?.line || 0,
15624
+ rule: j2.DetectorName || "unknown",
15625
+ line: j2.SourceMetadata?.Data?.Filesystem?.line || 0,
15502
15626
  preview: mask(raw),
15503
15627
  // trufflehog reports `Verified: true` only when --verification
15504
15628
  // ran AND the call succeeded. With --only-verified set, every
15505
15629
  // returned finding is verified live by definition, so we tag
15506
15630
  // them all true; with --no-verification, leave undefined.
15507
- verified: opts.verifyOnly ? true : j.Verified === true ? true : void 0
15631
+ verified: opts.verifyOnly ? true : j2.Verified === true ? true : void 0
15508
15632
  });
15509
15633
  } catch {
15510
15634
  }
@@ -15557,16 +15681,16 @@ function runTrufflehogDir(binary, dir, opts = {}) {
15557
15681
  if (!line.trim())
15558
15682
  continue;
15559
15683
  try {
15560
- const j = JSON.parse(line);
15561
- const raw = j.Raw || "";
15684
+ const j2 = JSON.parse(line);
15685
+ const raw = j2.Raw || "";
15562
15686
  if (looksLikeUuid(raw))
15563
15687
  continue;
15564
15688
  out.push({
15565
- rule: j.DetectorName || "unknown",
15566
- line: j.SourceMetadata?.Data?.Filesystem?.line || 0,
15689
+ rule: j2.DetectorName || "unknown",
15690
+ line: j2.SourceMetadata?.Data?.Filesystem?.line || 0,
15567
15691
  preview: mask(raw),
15568
- verified: opts.verifyOnly ? true : j.Verified === true ? true : void 0,
15569
- file: j.SourceMetadata?.Data?.Filesystem?.file || ""
15692
+ verified: opts.verifyOnly ? true : j2.Verified === true ? true : void 0,
15693
+ file: j2.SourceMetadata?.Data?.Filesystem?.file || ""
15570
15694
  });
15571
15695
  } catch {
15572
15696
  }
@@ -15795,7 +15919,7 @@ async function parseOpenCodeSubagents(parentSessionId) {
15795
15919
  if (!existsSync61(dbPath))
15796
15920
  return [];
15797
15921
  const realParent = parentSessionId.replace("opencode_", "");
15798
- const db = openSqliteReadonlyOrThrow(dbPath);
15922
+ const db = openOpencodeDbOrThrow(dbPath);
15799
15923
  try {
15800
15924
  const children = db.prepare(`
15801
15925
  SELECT id, title, time_created
@@ -15839,16 +15963,16 @@ async function parseOpenCodeTranscript(sessionId) {
15839
15963
  if (!existsSync61(dbPath))
15840
15964
  return [];
15841
15965
  const realId = sessionId.replace("opencode_", "");
15842
- const db = openSqliteReadonlyOrThrow(dbPath);
15966
+ const db = openOpencodeDbOrThrow(dbPath);
15843
15967
  const messages = [];
15844
15968
  try {
15845
15969
  const parts = db.prepare(`
15846
15970
  SELECT p.data, p.time_created, m.data as msg_data
15847
15971
  FROM part p
15848
- JOIN message m ON p.message_id = m.id
15972
+ JOIN (SELECT id, data FROM message WHERE session_id = ?) m ON p.message_id = m.id
15849
15973
  WHERE p.session_id = ?
15850
15974
  ORDER BY p.time_created ASC
15851
- `).all(realId);
15975
+ `).all(realId, realId);
15852
15976
  let lineNum = 0;
15853
15977
  for (const part of parts) {
15854
15978
  try {
@@ -15879,7 +16003,7 @@ var log5;
15879
16003
  var init_opencode2 = __esm({
15880
16004
  "../engine/dist/transcript/opencode.js"() {
15881
16005
  "use strict";
15882
- init_sqlite_reader();
16006
+ init_opencode_v2();
15883
16007
  init_logger();
15884
16008
  log5 = createLogger("transcript-opencode");
15885
16009
  }
@@ -18209,7 +18333,7 @@ var MAX_CHUNK_CHARS8, OpenCodeSource;
18209
18333
  var init_opencode_source = __esm({
18210
18334
  "../engine/dist/parsers/opencode-source.js"() {
18211
18335
  "use strict";
18212
- init_sqlite_reader();
18336
+ init_opencode_v2();
18213
18337
  init_opencode();
18214
18338
  init_settings();
18215
18339
  MAX_CHUNK_CHARS8 = 2e3;
@@ -18226,7 +18350,7 @@ var init_opencode_source = __esm({
18226
18350
  return;
18227
18351
  let db;
18228
18352
  try {
18229
- db = openSqliteReadonlyOrThrow(this.dbPath);
18353
+ db = openOpencodeDbOrThrow(this.dbPath);
18230
18354
  } catch {
18231
18355
  return;
18232
18356
  }
@@ -18323,17 +18447,17 @@ var init_opencode_source = __esm({
18323
18447
  if (!existsSync30(this.dbPath))
18324
18448
  return [];
18325
18449
  const sessionId = item.id.replace("opencode_", "");
18326
- const db = openSqliteReadonlyOrThrow(this.dbPath);
18450
+ const db = openOpencodeDbOrThrow(this.dbPath);
18327
18451
  const chunks = [];
18328
18452
  try {
18329
18453
  const parts = db.prepare(`
18330
18454
  SELECT p.data, m.data as msg_data
18331
18455
  FROM part p
18332
- JOIN message m ON p.message_id = m.id
18456
+ JOIN (SELECT id, data FROM message WHERE session_id = ?) m ON p.message_id = m.id
18333
18457
  WHERE p.session_id = ?
18334
18458
  AND (p.data LIKE '%"type":"text"%' OR p.data LIKE '%"type":"reasoning"%')
18335
18459
  ORDER BY p.time_created ASC
18336
- `).all(sessionId);
18460
+ `).all(sessionId, sessionId);
18337
18461
  const userTexts = [];
18338
18462
  const assistantTexts = [];
18339
18463
  for (const part of parts) {
@@ -18397,7 +18521,7 @@ var OpenCodeTodoSource;
18397
18521
  var init_opencode_todo_source = __esm({
18398
18522
  "../engine/dist/parsers/opencode-todo-source.js"() {
18399
18523
  "use strict";
18400
- init_sqlite_reader();
18524
+ init_opencode_v2();
18401
18525
  init_opencode();
18402
18526
  init_settings();
18403
18527
  OpenCodeTodoSource = class {
@@ -18413,7 +18537,7 @@ var init_opencode_todo_source = __esm({
18413
18537
  return;
18414
18538
  let db;
18415
18539
  try {
18416
- db = openSqliteReadonlyOrThrow(this.dbPath);
18540
+ db = openOpencodeDbOrThrow(this.dbPath);
18417
18541
  } catch {
18418
18542
  return;
18419
18543
  }
@@ -18461,7 +18585,7 @@ var init_opencode_todo_source = __esm({
18461
18585
  if (!existsSync31(this.dbPath))
18462
18586
  return [];
18463
18587
  const sessionId = item.extra?.sessionId || item.id.replace(`${opencodeBackend.idPrefix}todo_`, "");
18464
- const db = openSqliteReadonlyOrThrow(this.dbPath);
18588
+ const db = openOpencodeDbOrThrow(this.dbPath);
18465
18589
  const chunks = [];
18466
18590
  try {
18467
18591
  const todos = db.prepare(`
@@ -20145,12 +20269,12 @@ var require_foldFlowLines = __commonJS({
20145
20269
  ch = text[i += 1];
20146
20270
  overflow = true;
20147
20271
  }
20148
- const j = i > escEnd + 1 ? i - 2 : escStart - 1;
20149
- if (escapedFolds[j])
20272
+ const j2 = i > escEnd + 1 ? i - 2 : escStart - 1;
20273
+ if (escapedFolds[j2])
20150
20274
  return text;
20151
- folds.push(j);
20152
- escapedFolds[j] = true;
20153
- end = j + endStep;
20275
+ folds.push(j2);
20276
+ escapedFolds[j2] = true;
20277
+ end = j2 + endStep;
20154
20278
  split = void 0;
20155
20279
  } else {
20156
20280
  overflow = true;
@@ -28040,7 +28164,7 @@ var init_http = __esm({
28040
28164
  CLI_VERSION = JSON.parse(readFileSync34(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
28041
28165
  } catch {
28042
28166
  try {
28043
- if (true) CLI_VERSION = "0.7.23";
28167
+ if (true) CLI_VERSION = "0.7.24";
28044
28168
  } catch {
28045
28169
  }
28046
28170
  }
@@ -28538,7 +28662,7 @@ var init_telemetry = __esm({
28538
28662
  init_sync_client();
28539
28663
  cliVersion2 = () => {
28540
28664
  try {
28541
- return true ? "0.7.23" : "dev";
28665
+ return true ? "0.7.24" : "dev";
28542
28666
  } catch {
28543
28667
  return "dev";
28544
28668
  }
@@ -31151,7 +31275,7 @@ var init_sync_client = __esm({
31151
31275
  ownVersion = JSON.parse(readFileSync39(join46(dir, "../package.json"), "utf-8")).version || ownVersion;
31152
31276
  } catch {
31153
31277
  try {
31154
- if (true) ownVersion = "0.7.23";
31278
+ if (true) ownVersion = "0.7.24";
31155
31279
  } catch {
31156
31280
  }
31157
31281
  }
@@ -33738,6 +33862,40 @@ function runHook(sessionId, event, cwd) {
33738
33862
  child.stdin.end(JSON.stringify({ session_id: sessionId, hook_event_name: event, cwd }));
33739
33863
  });
33740
33864
  }
33865
+
33866
+ /**
33867
+ * Wait for a message to an idle session. The hook long-polls the server and
33868
+ * prints the messages when one arrives; onText gets them. One waiter per
33869
+ * session; stopWaiter ends it when the session gets a prompt.
33870
+ */
33871
+ const waiters = new Map();
33872
+
33873
+ function startWaiter(sessionId, cwd, onText) {
33874
+ if (waiters.has(sessionId)) return;
33875
+ let out = "";
33876
+ let child;
33877
+ try {
33878
+ child = spawn(HOOK, ["--wait"], { stdio: ["pipe", "pipe", "ignore"] });
33879
+ } catch {
33880
+ return;
33881
+ }
33882
+ waiters.set(sessionId, child);
33883
+ child.stdout.on("data", (c) => { out += c; });
33884
+ child.on("error", () => { waiters.delete(sessionId); });
33885
+ child.on("close", () => {
33886
+ if (waiters.get(sessionId) === child) waiters.delete(sessionId);
33887
+ const text = out.trim();
33888
+ if (text) Promise.resolve().then(() => onText(text)).catch(() => {});
33889
+ });
33890
+ child.stdin.end(JSON.stringify({ session_id: sessionId, hook_event_name: "Stop", cwd }));
33891
+ }
33892
+
33893
+ function stopWaiter(sessionId) {
33894
+ const child = waiters.get(sessionId);
33895
+ if (!child) return;
33896
+ waiters.delete(sessionId);
33897
+ try { child.kill(); } catch { /* gone */ }
33898
+ }
33741
33899
  `;
33742
33900
  }
33743
33901
  function opencodeMessagesSource(wrapperPath) {
@@ -33763,6 +33921,7 @@ async function setup(ctx) {
33763
33921
  await ctx.session.hook("prompt", async (event) => {
33764
33922
  const id = event?.sessionID;
33765
33923
  if (!id || !event.prompt || (await isChild(id))) return;
33924
+ stopWaiter("opencode_" + id);
33766
33925
  const parts = [];
33767
33926
  if (!started.has(id)) {
33768
33927
  started.add(id);
@@ -33778,15 +33937,23 @@ async function setup(ctx) {
33778
33937
  for await (const ev of ctx.event.subscribe({ signal: controller.signal })) {
33779
33938
  const id = ev?.data?.sessionID ?? ev?.properties?.sessionID;
33780
33939
  if (!id || !started.has(id)) continue;
33781
- if (RUN_END.has(ev.type)) await runHook("opencode_" + id, "Stop", dir);
33782
- else if (ev.type === "session.deleted") {
33940
+ if (RUN_END.has(ev.type)) {
33941
+ await runHook("opencode_" + id, "Stop", dir);
33942
+ // An idle session acts on a message from another session by itself:
33943
+ // the text becomes its next prompt.
33944
+ startWaiter("opencode_" + id, dir, (text) => ctx.session.prompt({ sessionID: id, text }));
33945
+ } else if (ev.type === "session.deleted") {
33783
33946
  started.delete(id);
33947
+ stopWaiter("opencode_" + id);
33784
33948
  await runHook("opencode_" + id, "SessionEnd", dir);
33785
33949
  }
33786
33950
  }
33787
33951
  } catch { /* the subscription ends with the plugin */ }
33788
33952
  })();
33789
- return () => controller.abort();
33953
+ return () => {
33954
+ controller.abort();
33955
+ for (const id of [...waiters.keys()]) stopWaiter(id);
33956
+ };
33790
33957
  }
33791
33958
 
33792
33959
  const pending = new Map();
@@ -33845,6 +34012,7 @@ export default function (pi) {
33845
34012
  pending = await runHook(idOf(ctx), "SessionStart", cwdOf(ctx));
33846
34013
  });
33847
34014
  pi.on("before_agent_start", async (event, ctx) => {
34015
+ stopWaiter(idOf(ctx));
33848
34016
  if (event.messageType !== "user") return;
33849
34017
  const now = await runHook(idOf(ctx), "UserPromptSubmit", cwdOf(ctx));
33850
34018
  const text = [pending, now].filter(Boolean).join("\\n\\n");
@@ -33854,8 +34022,11 @@ export default function (pi) {
33854
34022
  });
33855
34023
  pi.on("agent_end", async (_event, ctx) => {
33856
34024
  await runHook(idOf(ctx), "Stop", cwdOf(ctx));
34025
+ // An idle session acts on a message from another session by itself.
34026
+ startWaiter(idOf(ctx), cwdOf(ctx), (text) => pi.sendUserMessage(text));
33857
34027
  });
33858
34028
  pi.on("session_shutdown", async (_event, ctx) => {
34029
+ stopWaiter(idOf(ctx));
33859
34030
  await runHook(idOf(ctx), "SessionEnd", cwdOf(ctx));
33860
34031
  });
33861
34032
  }
@@ -35456,16 +35627,16 @@ async function collectCode(opts) {
35456
35627
  ...blastTargets.map((f, i) => call(2e3 + i, "plan_change", { file: f, format: "json" }))
35457
35628
  ]);
35458
35629
  blastTargets.forEach((f, i) => {
35459
- const j = parseJson(pc, 2e3 + i, null);
35460
- if (j && typeof j === "object" && typeof j.fan_in === "number") {
35630
+ const j2 = parseJson(pc, 2e3 + i, null);
35631
+ if (j2 && typeof j2 === "object" && typeof j2.fan_in === "number") {
35461
35632
  blast[f] = {
35462
- fileRole: j.file_role ?? "regular",
35463
- fanIn: j.fan_in ?? 0,
35464
- fanOut: j.fan_out ?? 0,
35465
- direct: j.direct ?? 0,
35466
- transitive: j.transitive ?? 0,
35467
- maxDepth: j.max_depth ?? 0,
35468
- directFiles: Array.isArray(j.direct_files) ? j.direct_files.map(rel).slice(0, 20) : void 0
35633
+ fileRole: j2.file_role ?? "regular",
35634
+ fanIn: j2.fan_in ?? 0,
35635
+ fanOut: j2.fan_out ?? 0,
35636
+ direct: j2.direct ?? 0,
35637
+ transitive: j2.transitive ?? 0,
35638
+ maxDepth: j2.max_depth ?? 0,
35639
+ directFiles: Array.isArray(j2.direct_files) ? j2.direct_files.map(rel).slice(0, 20) : void 0
35469
35640
  };
35470
35641
  }
35471
35642
  });
@@ -36209,7 +36380,7 @@ var init_intent_drain = __esm({
36209
36380
  ownVersion2 = JSON.parse(readFileSync49(join62(dir, "../package.json"), "utf-8")).version || ownVersion2;
36210
36381
  } catch {
36211
36382
  try {
36212
- if (true) ownVersion2 = "0.7.23";
36383
+ if (true) ownVersion2 = "0.7.24";
36213
36384
  } catch {
36214
36385
  }
36215
36386
  }
package/dist/mcp-relay.js CHANGED
@@ -299,7 +299,7 @@ function resilientBridge(input, output, first, reconnect, exit, opts = {}) {
299
299
  }
300
300
 
301
301
  // src/mcp-relay.ts
302
- var VERSION = true ? "0.7.23" : "0.0.0";
302
+ var VERSION = true ? "0.7.24" : "0.0.0";
303
303
  var SERVER_URL = new URL("./mcp.js", import.meta.url);
304
304
  var SERVER_PATH = fileURLToPath(SERVER_URL);
305
305
  var START_TIMEOUT_MS = 15e3;