chatroom-cli 1.0.85 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +82 -10
  2. package/dist/index.js +2085 -1336
  3. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ var __export = (target, all) => {
29
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
30
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
31
31
 
32
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/error.js
32
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/error.js
33
33
  var require_error = __commonJS((exports) => {
34
34
  class CommanderError extends Error {
35
35
  constructor(exitCode, code, message) {
@@ -53,7 +53,7 @@ var require_error = __commonJS((exports) => {
53
53
  exports.InvalidArgumentError = InvalidArgumentError;
54
54
  });
55
55
 
56
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/argument.js
56
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/argument.js
57
57
  var require_argument = __commonJS((exports) => {
58
58
  var { InvalidArgumentError } = require_error();
59
59
 
@@ -133,7 +133,7 @@ var require_argument = __commonJS((exports) => {
133
133
  exports.humanReadableArgName = humanReadableArgName;
134
134
  });
135
135
 
136
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/help.js
136
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/help.js
137
137
  var require_help = __commonJS((exports) => {
138
138
  var { humanReadableArgName } = require_argument();
139
139
 
@@ -490,7 +490,7 @@ ${itemIndentStr}`);
490
490
  exports.stripColor = stripColor;
491
491
  });
492
492
 
493
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/option.js
493
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/option.js
494
494
  var require_option = __commonJS((exports) => {
495
495
  var { InvalidArgumentError } = require_error();
496
496
 
@@ -674,7 +674,7 @@ var require_option = __commonJS((exports) => {
674
674
  exports.DualOptions = DualOptions;
675
675
  });
676
676
 
677
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/suggestSimilar.js
677
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/suggestSimilar.js
678
678
  var require_suggestSimilar = __commonJS((exports) => {
679
679
  var maxDistance = 3;
680
680
  function editDistance(a, b) {
@@ -747,7 +747,7 @@ var require_suggestSimilar = __commonJS((exports) => {
747
747
  exports.suggestSimilar = suggestSimilar;
748
748
  });
749
749
 
750
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/command.js
750
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/command.js
751
751
  var require_command = __commonJS((exports) => {
752
752
  var EventEmitter = __require("node:events").EventEmitter;
753
753
  var childProcess = __require("node:child_process");
@@ -2102,7 +2102,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2102
2102
  exports.useColor = useColor;
2103
2103
  });
2104
2104
 
2105
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/index.js
2105
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/index.js
2106
2106
  var require_commander = __commonJS((exports) => {
2107
2107
  var { Argument } = require_argument();
2108
2108
  var { Command } = require_command();
@@ -9343,6 +9343,15 @@ var init_index_node = __esm(() => {
9343
9343
  });
9344
9344
 
9345
9345
  // src/infrastructure/convex/client.ts
9346
+ var exports_client = {};
9347
+ __export(exports_client, {
9348
+ resetConvexClient: () => resetConvexClient,
9349
+ isClientLoggingEnabled: () => isClientLoggingEnabled,
9350
+ getConvexWsClient: () => getConvexWsClient,
9351
+ getConvexUrl: () => getConvexUrl,
9352
+ getConvexClient: () => getConvexClient,
9353
+ CONVEX_URL: () => CONVEX_URL
9354
+ });
9346
9355
  function getConvexUrl() {
9347
9356
  return process.env.CHATROOM_CONVEX_URL || DEFAULT_CONVEX_URL;
9348
9357
  }
@@ -9378,18 +9387,40 @@ async function getConvexWsClient() {
9378
9387
  }
9379
9388
  return wsClient;
9380
9389
  }
9381
- var DEFAULT_CONVEX_URL = "https://chatroom-cloud.duskfare.com", client = null, wsClient = null, cachedUrl = null;
9390
+ function resetConvexClient() {
9391
+ client = null;
9392
+ if (wsClient) {
9393
+ wsClient.close();
9394
+ wsClient = null;
9395
+ }
9396
+ cachedUrl = null;
9397
+ }
9398
+ var DEFAULT_CONVEX_URL = "https://chatroom-cloud.duskfare.com", CONVEX_URL, client = null, wsClient = null, cachedUrl = null;
9382
9399
  var init_client2 = __esm(() => {
9383
9400
  init_index_node();
9401
+ CONVEX_URL = DEFAULT_CONVEX_URL;
9384
9402
  });
9385
9403
 
9386
9404
  // src/infrastructure/auth/storage.ts
9405
+ var exports_storage = {};
9406
+ __export(exports_storage, {
9407
+ saveAuthData: () => saveAuthData,
9408
+ loadAuthData: () => loadAuthData,
9409
+ isAuthenticated: () => isAuthenticated,
9410
+ getSessionId: () => getSessionId,
9411
+ getOtherSessionUrls: () => getOtherSessionUrls,
9412
+ getDeviceName: () => getDeviceName,
9413
+ getCliVersion: () => getVersion,
9414
+ getAuthFilePath: () => getAuthFilePath,
9415
+ getAllSessions: () => getAllSessions,
9416
+ clearAuthData: () => clearAuthData
9417
+ });
9387
9418
  import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "node:fs";
9388
9419
  import { homedir, hostname } from "node:os";
9389
9420
  import { join as join2 } from "node:path";
9390
9421
  function ensureConfigDir() {
9391
9422
  if (!existsSync(CHATROOM_DIR)) {
9392
- mkdirSync(CHATROOM_DIR, { recursive: true });
9423
+ mkdirSync(CHATROOM_DIR, { recursive: true, mode: 448 });
9393
9424
  }
9394
9425
  }
9395
9426
  function getAuthFilePath() {
@@ -9466,7 +9497,7 @@ function saveAuthData(data) {
9466
9497
  // To logout, run: chatroom auth logout
9467
9498
  ${JSON.stringify(multiEnvData, null, 2)}
9468
9499
  `;
9469
- writeFileSync(authPath, content, "utf-8");
9500
+ writeFileSync(authPath, content, { encoding: "utf-8", mode: 384 });
9470
9501
  }
9471
9502
  function clearAuthData() {
9472
9503
  const authPath = getAuthFilePath();
@@ -9495,7 +9526,7 @@ function clearAuthData() {
9495
9526
  // To logout, run: chatroom auth logout
9496
9527
  ${JSON.stringify(rawData, null, 2)}
9497
9528
  `;
9498
- writeFileSync(authPath, content, "utf-8");
9529
+ writeFileSync(authPath, content, { encoding: "utf-8", mode: 384 });
9499
9530
  return true;
9500
9531
  }
9501
9532
  try {
@@ -9617,6 +9648,17 @@ var init_api3 = __esm(() => {
9617
9648
  init_api2();
9618
9649
  });
9619
9650
 
9651
+ // src/utils/terminal-safety.ts
9652
+ function sanitizeForTerminal(input) {
9653
+ return input.replace(/\u001B\][^\u0007]*(?:\u0007|\u001B\\)/g, "").replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "");
9654
+ }
9655
+ function sanitizeUnknownForTerminal(value) {
9656
+ if (typeof value === "string") {
9657
+ return sanitizeForTerminal(value);
9658
+ }
9659
+ return sanitizeForTerminal(String(value));
9660
+ }
9661
+
9620
9662
  // src/utils/error-formatting.ts
9621
9663
  function formatError(message, suggestions) {
9622
9664
  console.error(`❌ ${message}`);
@@ -9659,24 +9701,26 @@ function formatChatroomIdError(chatroomId) {
9659
9701
  }
9660
9702
  function isNetworkError(error) {
9661
9703
  const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
9662
- const code2 = error?.code;
9704
+ const code2 = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
9663
9705
  return msg.includes("fetch failed") || msg.includes("failed to fetch") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("etimedout") || msg.includes("network") || msg.includes("connection refused") || msg.includes("socket hang up") || msg.includes("dns") || code2 === "ECONNREFUSED" || code2 === "ENOTFOUND" || code2 === "ETIMEDOUT" || code2 === "ECONNRESET";
9664
9706
  }
9665
9707
  function formatConnectivityError(error, backendUrl) {
9666
9708
  const err = error instanceof Error ? error : new Error(String(error));
9709
+ const safeBackendUrl = backendUrl ? sanitizeForTerminal(backendUrl) : undefined;
9667
9710
  console.error(`
9668
- ❌ Could not reach the backend${backendUrl ? ` at ${backendUrl}` : ""}`);
9669
- console.error(` ${err.message}`);
9711
+ ❌ Could not reach the backend${safeBackendUrl ? ` at ${safeBackendUrl}` : ""}`);
9712
+ console.error(` ${sanitizeForTerminal(err.message)}`);
9670
9713
  console.error(`
9671
9714
  Your session may still be valid. Please check:`);
9672
9715
  console.error(` • Network connectivity`);
9673
9716
  console.error(` • Whether the backend service is running`);
9674
- if (backendUrl) {
9675
- console.error(` • CHATROOM_CONVEX_URL is correct (currently: ${backendUrl})`);
9717
+ if (safeBackendUrl) {
9718
+ console.error(` • CHATROOM_CONVEX_URL is correct (currently: ${safeBackendUrl})`);
9676
9719
  }
9677
9720
  console.error(`
9678
9721
  Try again once the backend is reachable.`);
9679
9722
  }
9723
+ var init_error_formatting = () => {};
9680
9724
 
9681
9725
  // src/infrastructure/auth/middleware.ts
9682
9726
  var exports_middleware = {};
@@ -9784,20 +9828,78 @@ async function checkAuth() {
9784
9828
  var init_middleware = __esm(() => {
9785
9829
  init_storage();
9786
9830
  init_api3();
9831
+ init_error_formatting();
9787
9832
  init_client2();
9788
9833
  });
9789
9834
 
9790
- // src/commands/auth-login.ts
9835
+ // src/commands/auth-login/index.ts
9791
9836
  var exports_auth_login = {};
9792
9837
  __export(exports_auth_login, {
9838
+ getWebAppUrl: () => getWebAppUrl,
9839
+ createDefaultDeps: () => createDefaultDeps,
9793
9840
  authLogin: () => authLogin
9794
9841
  });
9795
- function getWebAppUrl() {
9842
+ async function openBrowser(url) {
9843
+ const { spawn } = await import("node:child_process");
9844
+ const platform = process.platform;
9845
+ try {
9846
+ if (platform === "darwin") {
9847
+ const child = spawn("open", [url], { detached: true, stdio: "ignore" });
9848
+ child.unref();
9849
+ } else if (platform === "win32") {
9850
+ const child = spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
9851
+ child.unref();
9852
+ } else {
9853
+ const child = spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
9854
+ child.unref();
9855
+ }
9856
+ } catch {
9857
+ console.log(`
9858
+ ⚠️ Could not open browser automatically.`);
9859
+ console.log(` Please visit the URL manually.`);
9860
+ }
9861
+ }
9862
+ function createDefaultDeps() {
9863
+ return {
9864
+ backend: {
9865
+ mutation: async (endpoint, args) => {
9866
+ const client2 = await getConvexClient();
9867
+ return client2.mutation(endpoint, args);
9868
+ },
9869
+ query: async (endpoint, args) => {
9870
+ const client2 = await getConvexClient();
9871
+ return client2.query(endpoint, args);
9872
+ }
9873
+ },
9874
+ auth: {
9875
+ isAuthenticated,
9876
+ getAuthFilePath,
9877
+ saveAuthData,
9878
+ getDeviceName,
9879
+ getCliVersion: getVersion,
9880
+ getSessionId
9881
+ },
9882
+ browser: {
9883
+ open: openBrowser
9884
+ },
9885
+ clock: {
9886
+ now: () => Date.now(),
9887
+ delay: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
9888
+ },
9889
+ process: {
9890
+ env: process.env,
9891
+ platform: process.platform,
9892
+ exit: (code2) => process.exit(code2),
9893
+ stdoutWrite: (text) => process.stdout.write(text)
9894
+ }
9895
+ };
9896
+ }
9897
+ function getWebAppUrl(d) {
9796
9898
  const convexUrl = getConvexUrl();
9797
9899
  if (convexUrl === PRODUCTION_CONVEX_URL2) {
9798
9900
  return PRODUCTION_WEBAPP_URL;
9799
9901
  }
9800
- const webAppUrlOverride = process.env.CHATROOM_WEB_URL;
9902
+ const webAppUrlOverride = d.process.env.CHATROOM_WEB_URL;
9801
9903
  if (webAppUrlOverride) {
9802
9904
  return webAppUrlOverride;
9803
9905
  }
@@ -9818,38 +9920,42 @@ To authenticate with a local/dev backend, you must also set`);
9818
9920
  console.error(`
9819
9921
  ${"═".repeat(50)}
9820
9922
  `);
9821
- process.exit(1);
9822
- }
9823
- async function openBrowser(url) {
9824
- const { exec } = await import("node:child_process");
9825
- const { promisify } = await import("node:util");
9826
- const execAsync = promisify(exec);
9827
- const platform = process.platform;
9828
- try {
9829
- if (platform === "darwin") {
9830
- await execAsync(`open "${url}"`);
9831
- } else if (platform === "win32") {
9832
- await execAsync(`start "" "${url}"`);
9833
- } else {
9834
- await execAsync(`xdg-open "${url}"`);
9835
- }
9836
- } catch {
9837
- console.log(`
9838
- ⚠️ Could not open browser automatically.`);
9839
- console.log(` Please visit the URL manually.`);
9840
- }
9841
- }
9842
- async function authLogin(options) {
9843
- if (isAuthenticated() && !options.force) {
9844
- console.log(`✅ Already authenticated.`);
9845
- console.log(` Auth file: ${getAuthFilePath()}`);
9846
- console.log(`
9923
+ d.process.exit(1);
9924
+ return "";
9925
+ }
9926
+ async function authLogin(options, deps) {
9927
+ const d = deps ?? createDefaultDeps();
9928
+ if (d.auth.isAuthenticated() && !options.force) {
9929
+ const sessionId = d.auth.getSessionId();
9930
+ if (sessionId) {
9931
+ try {
9932
+ const validation = await d.backend.query(api.cliAuth.validateSession, { sessionId });
9933
+ if (validation.valid) {
9934
+ console.log(`✅ Already authenticated.`);
9935
+ console.log(` Auth file: ${d.auth.getAuthFilePath()}`);
9936
+ console.log(`
9847
9937
  Use --force to re-authenticate.`);
9848
- return;
9938
+ return;
9939
+ }
9940
+ const reason = validation.reason;
9941
+ if (reason === "Session expired") {
9942
+ console.log(`
9943
+ ⚠️ Your session has expired. Re-authenticating automatically...`);
9944
+ } else {
9945
+ console.log(`
9946
+ ⚠️ Session is no longer valid (${reason}). Re-authenticating...`);
9947
+ }
9948
+ } catch {
9949
+ console.log(`✅ Already authenticated (could not verify with backend).`);
9950
+ console.log(` Auth file: ${d.auth.getAuthFilePath()}`);
9951
+ console.log(`
9952
+ Use --force to re-authenticate.`);
9953
+ return;
9954
+ }
9955
+ }
9849
9956
  }
9850
- const client2 = await getConvexClient();
9851
- const deviceName = getDeviceName();
9852
- const cliVersion = getVersion();
9957
+ const deviceName = d.auth.getDeviceName();
9958
+ const cliVersion = d.auth.getCliVersion();
9853
9959
  const convexUrl = getConvexUrl();
9854
9960
  console.log(`
9855
9961
  ${"═".repeat(50)}`);
@@ -9865,17 +9971,17 @@ Device: ${deviceName}`);
9865
9971
  }
9866
9972
  console.log(`
9867
9973
  ⏳ Creating authentication request...`);
9868
- const result = await client2.mutation(api.cliAuth.createAuthRequest, {
9974
+ const result = await d.backend.mutation(api.cliAuth.createAuthRequest, {
9869
9975
  deviceName,
9870
9976
  cliVersion
9871
9977
  });
9872
9978
  const { requestId, expiresAt } = result;
9873
- const expiresInSeconds = Math.round((expiresAt - Date.now()) / 1000);
9979
+ const expiresInSeconds = Math.round((expiresAt - d.clock.now()) / 1000);
9874
9980
  console.log(`
9875
9981
  ✅ Auth request created`);
9876
9982
  console.log(` Request ID: ${requestId.substring(0, 8)}...`);
9877
9983
  console.log(` Expires in: ${expiresInSeconds} seconds`);
9878
- const webAppUrl = getWebAppUrl();
9984
+ const webAppUrl = getWebAppUrl(d);
9879
9985
  const authUrl = `${webAppUrl}/cli-auth?request=${requestId}`;
9880
9986
  console.log(`
9881
9987
  ${"─".repeat(50)}`);
@@ -9889,21 +9995,21 @@ If the browser doesn't open, visit this URL:`);
9889
9995
  ${authUrl}`);
9890
9996
  console.log(`
9891
9997
  ${"─".repeat(50)}`);
9892
- await openBrowser(authUrl);
9998
+ await d.browser.open(authUrl);
9893
9999
  console.log(`
9894
10000
  ⏳ Waiting for authorization...`);
9895
10001
  console.log(` (Press Ctrl+C to cancel)
9896
10002
  `);
9897
10003
  let pollCount = 0;
9898
- const maxPolls = Math.ceil((expiresAt - Date.now()) / AUTH_POLL_INTERVAL_MS);
10004
+ const maxPolls = Math.ceil((expiresAt - d.clock.now()) / AUTH_POLL_INTERVAL_MS);
9899
10005
  const poll = async () => {
9900
10006
  pollCount++;
9901
10007
  try {
9902
- const status = await client2.query(api.cliAuth.getAuthRequestStatus, {
10008
+ const status = await d.backend.query(api.cliAuth.getAuthRequestStatus, {
9903
10009
  requestId
9904
10010
  });
9905
10011
  if (status.status === "approved" && status.sessionId) {
9906
- saveAuthData({
10012
+ d.auth.saveAuthData({
9907
10013
  sessionId: status.sessionId,
9908
10014
  createdAt: new Date().toISOString(),
9909
10015
  deviceName,
@@ -9914,45 +10020,48 @@ ${"═".repeat(50)}`);
9914
10020
  console.log(`✅ AUTHENTICATION SUCCESSFUL`);
9915
10021
  console.log(`${"═".repeat(50)}`);
9916
10022
  console.log(`
9917
- Session stored at: ${getAuthFilePath()}`);
10023
+ Session stored at: ${d.auth.getAuthFilePath()}`);
9918
10024
  console.log(`
9919
10025
  You can now use chatroom commands.`);
9920
- return true;
10026
+ return "done";
9921
10027
  }
9922
10028
  if (status.status === "denied") {
9923
10029
  console.log(`
9924
10030
  ❌ Authorization denied by user.`);
9925
- process.exit(1);
10031
+ d.process.exit(1);
10032
+ return "exit";
9926
10033
  }
9927
10034
  if (status.status === "expired" || status.status === "not_found") {
9928
10035
  console.log(`
9929
10036
  ❌ Authorization request expired.`);
9930
10037
  console.log(` Please try again: chatroom auth login`);
9931
- process.exit(1);
10038
+ d.process.exit(1);
10039
+ return "exit";
9932
10040
  }
9933
10041
  if (pollCount % 5 === 0) {
9934
- const remainingSeconds = Math.round((expiresAt - Date.now()) / 1000);
9935
- process.stdout.write(`\r Waiting... (${remainingSeconds}s remaining) `);
10042
+ const remainingSeconds = Math.round((expiresAt - d.clock.now()) / 1000);
10043
+ d.process.stdoutWrite(`\r Waiting... (${remainingSeconds}s remaining) `);
9936
10044
  }
9937
10045
  if (pollCount >= maxPolls) {
9938
10046
  console.log(`
9939
10047
  ❌ Authorization request expired.`);
9940
10048
  console.log(` Please try again: chatroom auth login`);
9941
- process.exit(1);
10049
+ d.process.exit(1);
10050
+ return "exit";
9942
10051
  }
9943
- return false;
10052
+ return "continue";
9944
10053
  } catch (error) {
9945
10054
  const err = error;
9946
10055
  console.error(`
9947
10056
  ⚠️ Error polling for authorization: ${err.message}`);
9948
- return false;
10057
+ return "continue";
9949
10058
  }
9950
10059
  };
9951
10060
  while (true) {
9952
- const success = await poll();
9953
- if (success)
10061
+ const result2 = await poll();
10062
+ if (result2 === "done" || result2 === "exit")
9954
10063
  break;
9955
- await new Promise((resolve) => setTimeout(resolve, AUTH_POLL_INTERVAL_MS));
10064
+ await d.clock.delay(AUTH_POLL_INTERVAL_MS);
9956
10065
  }
9957
10066
  }
9958
10067
  var AUTH_POLL_INTERVAL_MS = 2000, PRODUCTION_CONVEX_URL2 = "https://chatroom-cloud.duskfare.com", PRODUCTION_WEBAPP_URL = "https://chatroom.duskfare.com";
@@ -9962,23 +10071,34 @@ var init_auth_login = __esm(() => {
9962
10071
  init_client2();
9963
10072
  });
9964
10073
 
9965
- // src/commands/auth-logout.ts
10074
+ // src/commands/auth-logout/index.ts
9966
10075
  var exports_auth_logout = {};
9967
10076
  __export(exports_auth_logout, {
9968
10077
  authLogout: () => authLogout
9969
10078
  });
9970
- async function authLogout() {
9971
- if (!isAuthenticated()) {
10079
+ function createDefaultDeps2() {
10080
+ return {
10081
+ session: {
10082
+ isAuthenticated,
10083
+ clearAuthData,
10084
+ getAuthFilePath
10085
+ }
10086
+ };
10087
+ }
10088
+ async function authLogout(deps) {
10089
+ const d = deps ?? createDefaultDeps2();
10090
+ if (!d.session.isAuthenticated()) {
9972
10091
  console.log(`ℹ️ Not currently authenticated.`);
9973
10092
  return;
9974
10093
  }
9975
- const cleared = clearAuthData();
10094
+ const cleared = d.session.clearAuthData();
9976
10095
  if (cleared) {
9977
10096
  console.log(`✅ Logged out successfully.`);
9978
- console.log(` Removed: ${getAuthFilePath()}`);
10097
+ console.log(` Removed: ${d.session.getAuthFilePath()}`);
9979
10098
  } else {
9980
10099
  console.error(`❌ Failed to clear authentication data.`);
9981
10100
  process.exit(1);
10101
+ return;
9982
10102
  }
9983
10103
  }
9984
10104
  var init_auth_logout = __esm(() => {
@@ -10117,8 +10237,7 @@ function createNewEndpointConfig() {
10117
10237
  registeredAt: now,
10118
10238
  lastSyncedAt: now,
10119
10239
  availableHarnesses,
10120
- harnessVersions: detectHarnessVersions(availableHarnesses),
10121
- chatroomAgents: {}
10240
+ harnessVersions: detectHarnessVersions(availableHarnesses)
10122
10241
  };
10123
10242
  }
10124
10243
  function ensureMachineRegistered() {
@@ -10145,29 +10264,6 @@ function getMachineId() {
10145
10264
  const config = loadMachineConfig();
10146
10265
  return config?.machineId ?? null;
10147
10266
  }
10148
- function updateAgentContext(chatroomId, role, agentType, workingDir) {
10149
- const config = loadMachineConfig();
10150
- if (!config) {
10151
- throw new Error("Machine not registered. Run ensureMachineRegistered() first.");
10152
- }
10153
- const now = new Date().toISOString();
10154
- if (!config.chatroomAgents[chatroomId]) {
10155
- config.chatroomAgents[chatroomId] = {};
10156
- }
10157
- config.chatroomAgents[chatroomId][role] = {
10158
- agentType,
10159
- workingDir,
10160
- lastStartedAt: now
10161
- };
10162
- saveMachineConfig(config);
10163
- }
10164
- function getAgentContext(chatroomId, role) {
10165
- const config = loadMachineConfig();
10166
- if (!config) {
10167
- return null;
10168
- }
10169
- return config.chatroomAgents[chatroomId]?.[role] ?? null;
10170
- }
10171
10267
  var CHATROOM_DIR2, MACHINE_FILE = "machine.json";
10172
10268
  var init_storage2 = __esm(() => {
10173
10269
  init_detection();
@@ -10261,248 +10357,236 @@ var init_daemon_state = __esm(() => {
10261
10357
  STATE_DIR = join4(CHATROOM_DIR3, "machines", "state");
10262
10358
  });
10263
10359
 
10360
+ // src/infrastructure/machine/intentional-stops.ts
10361
+ function agentKey2(chatroomId, role) {
10362
+ return `${chatroomId}:${role.toLowerCase()}`;
10363
+ }
10364
+ function markIntentionalStop(chatroomId, role) {
10365
+ intentionalStops.add(agentKey2(chatroomId, role));
10366
+ }
10367
+ function consumeIntentionalStop(chatroomId, role) {
10368
+ const key = agentKey2(chatroomId, role);
10369
+ if (intentionalStops.has(key)) {
10370
+ intentionalStops.delete(key);
10371
+ return true;
10372
+ }
10373
+ return false;
10374
+ }
10375
+ function clearIntentionalStop(chatroomId, role) {
10376
+ intentionalStops.delete(agentKey2(chatroomId, role));
10377
+ }
10378
+ var intentionalStops;
10379
+ var init_intentional_stops = __esm(() => {
10380
+ intentionalStops = new Set;
10381
+ });
10382
+
10264
10383
  // src/infrastructure/machine/index.ts
10265
10384
  var init_machine = __esm(() => {
10266
10385
  init_types();
10267
10386
  init_storage2();
10268
10387
  init_daemon_state();
10388
+ init_intentional_stops();
10269
10389
  });
10270
10390
 
10271
- // src/infrastructure/agent-drivers/process-driver.ts
10272
- import { spawn } from "node:child_process";
10273
- import { randomUUID as randomUUID2 } from "node:crypto";
10274
- import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "node:fs";
10275
- import { tmpdir } from "node:os";
10276
- import { join as join5 } from "node:path";
10277
- function writeTempPromptFile(prompt) {
10278
- const tempPath = join5(tmpdir(), `chatroom-prompt-${randomUUID2()}.txt`);
10279
- writeFileSync4(tempPath, prompt, { encoding: "utf-8", mode: 384 });
10280
- return tempPath;
10281
- }
10282
- function scheduleCleanup(filePath, delayMs = 5000) {
10283
- setTimeout(() => {
10284
- try {
10285
- unlinkSync2(filePath);
10286
- } catch {}
10287
- }, delayMs);
10288
- }
10289
- function buildCombinedPrompt(rolePrompt, initialMessage) {
10290
- return `${rolePrompt}
10291
-
10292
- ${initialMessage}`;
10391
+ // src/infrastructure/services/remote-agents/opencode/opencode-agent-service.ts
10392
+ import { spawn, execSync as execSync2 } from "node:child_process";
10393
+ function defaultDeps() {
10394
+ return {
10395
+ execSync: execSync2,
10396
+ spawn,
10397
+ kill: (pid, signal) => process.kill(pid, signal)
10398
+ };
10293
10399
  }
10294
10400
 
10295
- class ProcessDriver {
10296
- async start(options) {
10297
- const config = this.buildSpawnConfig(options);
10298
- console.log(` Spawning ${this.harness} agent...`);
10299
- console.log(` Working dir: ${options.workingDir}`);
10300
- if (options.harnessVersion) {
10301
- console.log(` Harness version: v${options.harnessVersion.version} (major: ${options.harnessVersion.major})`);
10302
- }
10303
- if (options.model) {
10304
- console.log(` Model: ${options.model}`);
10401
+ class OpenCodeAgentService {
10402
+ deps;
10403
+ processes = new Map;
10404
+ constructor(deps) {
10405
+ this.deps = { ...defaultDeps(), ...deps };
10406
+ }
10407
+ isInstalled() {
10408
+ try {
10409
+ const checkCmd = process.platform === "win32" ? `where ${OPENCODE_COMMAND}` : `which ${OPENCODE_COMMAND}`;
10410
+ this.deps.execSync(checkCmd, { stdio: "ignore" });
10411
+ return true;
10412
+ } catch {
10413
+ return false;
10305
10414
  }
10415
+ }
10416
+ getVersion() {
10306
10417
  try {
10307
- const childProcess = spawn(config.command, config.args, {
10308
- cwd: options.workingDir,
10309
- stdio: config.stdio,
10310
- detached: true,
10311
- shell: false
10312
- });
10313
- if (config.writePromptToStdin && config.stdinPrompt) {
10314
- childProcess.stdin?.write(config.stdinPrompt);
10315
- childProcess.stdin?.end();
10316
- }
10317
- config.afterSpawn?.(childProcess);
10318
- childProcess.unref();
10319
- await new Promise((resolve) => setTimeout(resolve, 500));
10320
- if (childProcess.killed || childProcess.exitCode !== null) {
10321
- return {
10322
- success: false,
10323
- message: `Agent process exited immediately (exit code: ${childProcess.exitCode})`
10324
- };
10325
- }
10326
- const handle = {
10327
- harness: this.harness,
10328
- type: "process",
10329
- pid: childProcess.pid,
10330
- workingDir: options.workingDir
10331
- };
10332
- return {
10333
- success: true,
10334
- message: "Agent spawned successfully",
10335
- handle,
10336
- onExit: (callback) => {
10337
- childProcess.on("exit", (code2, signal) => {
10338
- callback(code2, signal);
10339
- });
10340
- }
10341
- };
10342
- } catch (error) {
10418
+ const output = this.deps.execSync(`${OPENCODE_COMMAND} --version`, {
10419
+ stdio: ["pipe", "pipe", "pipe"],
10420
+ timeout: 5000
10421
+ }).toString().trim();
10422
+ const match = output.match(/v?(\d+)\.(\d+)\.(\d+)/);
10423
+ if (!match)
10424
+ return null;
10343
10425
  return {
10344
- success: false,
10345
- message: `Failed to spawn agent: ${error.message}`
10426
+ version: `${match[1]}.${match[2]}.${match[3]}`,
10427
+ major: parseInt(match[1], 10)
10346
10428
  };
10429
+ } catch {
10430
+ return null;
10431
+ }
10432
+ }
10433
+ async listModels() {
10434
+ try {
10435
+ const output = this.deps.execSync(`${OPENCODE_COMMAND} models`, {
10436
+ stdio: ["pipe", "pipe", "pipe"],
10437
+ timeout: 1e4
10438
+ }).toString().trim();
10439
+ if (!output)
10440
+ return [];
10441
+ return output.split(`
10442
+ `).map((line) => line.trim()).filter((line) => line.length > 0);
10443
+ } catch {
10444
+ return [];
10347
10445
  }
10348
10446
  }
10349
- async stop(handle) {
10350
- if (handle.type !== "process" || !handle.pid) {
10351
- throw new Error(`Cannot stop: handle has no PID (type=${handle.type})`);
10447
+ async spawn(options) {
10448
+ const args = ["run"];
10449
+ if (options.model) {
10450
+ args.push("--model", options.model);
10451
+ }
10452
+ const childProcess = this.deps.spawn(OPENCODE_COMMAND, args, {
10453
+ cwd: options.workingDir,
10454
+ stdio: ["pipe", "pipe", "pipe"],
10455
+ shell: false,
10456
+ detached: true
10457
+ });
10458
+ childProcess.stdin?.write(options.prompt);
10459
+ childProcess.stdin?.end();
10460
+ await new Promise((resolve) => setTimeout(resolve, 500));
10461
+ if (childProcess.killed || childProcess.exitCode !== null) {
10462
+ throw new Error(`Agent process exited immediately (exit code: ${childProcess.exitCode})`);
10463
+ }
10464
+ if (!childProcess.pid) {
10465
+ throw new Error("Agent process started but has no PID");
10466
+ }
10467
+ const pid = childProcess.pid;
10468
+ const context = options.context;
10469
+ const entry = { context, lastOutputAt: Date.now() };
10470
+ this.processes.set(pid, entry);
10471
+ const outputCallbacks = [];
10472
+ if (childProcess.stdout) {
10473
+ childProcess.stdout.pipe(process.stdout, { end: false });
10474
+ childProcess.stdout.on("data", () => {
10475
+ entry.lastOutputAt = Date.now();
10476
+ for (const cb of outputCallbacks)
10477
+ cb();
10478
+ });
10479
+ }
10480
+ if (childProcess.stderr) {
10481
+ childProcess.stderr.pipe(process.stderr, { end: false });
10482
+ childProcess.stderr.on("data", () => {
10483
+ entry.lastOutputAt = Date.now();
10484
+ for (const cb of outputCallbacks)
10485
+ cb();
10486
+ });
10352
10487
  }
10353
- const pid = handle.pid;
10488
+ return {
10489
+ pid,
10490
+ onExit: (cb) => {
10491
+ childProcess.on("exit", (code2, signal) => {
10492
+ this.processes.delete(pid);
10493
+ cb({ code: code2, signal, context });
10494
+ });
10495
+ },
10496
+ onOutput: (cb) => {
10497
+ outputCallbacks.push(cb);
10498
+ }
10499
+ };
10500
+ }
10501
+ async stop(pid) {
10354
10502
  try {
10355
- process.kill(pid, "SIGTERM");
10503
+ this.deps.kill(-pid, "SIGTERM");
10356
10504
  } catch {
10357
10505
  return;
10358
10506
  }
10359
- const KILL_TIMEOUT_MS = 5000;
10360
- const POLL_INTERVAL_MS = 200;
10361
10507
  const deadline = Date.now() + KILL_TIMEOUT_MS;
10362
10508
  while (Date.now() < deadline) {
10363
10509
  try {
10364
- process.kill(pid, 0);
10510
+ this.deps.kill(pid, 0);
10365
10511
  } catch {
10366
10512
  return;
10367
10513
  }
10368
10514
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
10369
10515
  }
10370
10516
  try {
10371
- process.kill(pid, "SIGKILL");
10517
+ this.deps.kill(-pid, "SIGKILL");
10372
10518
  } catch {}
10373
10519
  }
10374
- async isAlive(handle) {
10375
- if (handle.type !== "process" || !handle.pid) {
10376
- return false;
10377
- }
10520
+ isAlive(pid) {
10378
10521
  try {
10379
- process.kill(handle.pid, 0);
10522
+ this.deps.kill(pid, 0);
10380
10523
  return true;
10381
10524
  } catch {
10382
10525
  return false;
10383
10526
  }
10384
10527
  }
10385
- async recover(_workingDir) {
10386
- return [];
10387
- }
10388
- async listModels() {
10389
- return [];
10390
- }
10391
- }
10392
- var init_process_driver = () => {};
10393
-
10394
- // src/infrastructure/agent-drivers/opencode-process-driver.ts
10395
- import { execSync as execSync2 } from "node:child_process";
10396
- var OpenCodeProcessDriver;
10397
- var init_opencode_process_driver = __esm(() => {
10398
- init_process_driver();
10399
- init_types();
10400
- OpenCodeProcessDriver = class OpenCodeProcessDriver extends ProcessDriver {
10401
- harness = "opencode";
10402
- capabilities = {
10403
- sessionPersistence: false,
10404
- abort: false,
10405
- modelSelection: true,
10406
- compaction: false,
10407
- eventStreaming: false,
10408
- messageInjection: false,
10409
- dynamicModelDiscovery: true
10410
- };
10411
- buildSpawnConfig(options) {
10412
- const command = AGENT_HARNESS_COMMANDS[this.harness];
10413
- const combinedPrompt = buildCombinedPrompt(options.rolePrompt, options.initialMessage);
10414
- const args = ["run"];
10415
- if (options.model) {
10416
- args.push("--model", options.model);
10417
- }
10418
- return {
10419
- command,
10420
- args,
10421
- stdio: ["pipe", "inherit", "inherit"],
10422
- writePromptToStdin: true,
10423
- stdinPrompt: combinedPrompt
10424
- };
10425
- }
10426
- async listModels() {
10427
- try {
10428
- const output = execSync2("opencode models", {
10429
- stdio: ["pipe", "pipe", "pipe"],
10430
- timeout: 1e4
10431
- }).toString().trim();
10432
- if (!output)
10433
- return [];
10434
- return output.split(`
10435
- `).map((line) => line.trim()).filter((line) => line.length > 0);
10436
- } catch {
10437
- return [];
10438
- }
10439
- }
10440
- };
10441
- });
10442
-
10443
- // src/infrastructure/agent-drivers/registry.ts
10444
- class DefaultDriverRegistry {
10445
- drivers;
10446
- constructor(drivers) {
10447
- this.drivers = new Map;
10448
- for (const driver of drivers) {
10449
- this.drivers.set(driver.harness, driver);
10450
- }
10451
- }
10452
- get(harness) {
10453
- const driver = this.drivers.get(harness);
10454
- if (!driver) {
10455
- throw new Error(`No driver registered for harness: ${harness}`);
10456
- }
10457
- return driver;
10458
- }
10459
- all() {
10460
- return Array.from(this.drivers.values());
10461
- }
10462
- capabilities(harness) {
10463
- return this.get(harness).capabilities;
10528
+ getTrackedProcesses() {
10529
+ return Array.from(this.processes.entries()).map(([pid, entry]) => ({
10530
+ pid,
10531
+ context: entry.context,
10532
+ lastOutputAt: entry.lastOutputAt
10533
+ }));
10464
10534
  }
10465
- }
10466
- function getDriverRegistry() {
10467
- if (!registryInstance) {
10468
- registryInstance = new DefaultDriverRegistry([new OpenCodeProcessDriver]);
10535
+ untrack(pid) {
10536
+ this.processes.delete(pid);
10469
10537
  }
10470
- return registryInstance;
10471
10538
  }
10472
- var registryInstance = null;
10473
- var init_registry = __esm(() => {
10474
- init_opencode_process_driver();
10475
- });
10539
+ var OPENCODE_COMMAND = "opencode", KILL_TIMEOUT_MS = 5000, POLL_INTERVAL_MS = 200;
10540
+ var init_opencode_agent_service = () => {};
10476
10541
 
10477
- // src/infrastructure/agent-drivers/index.ts
10478
- var exports_agent_drivers = {};
10479
- __export(exports_agent_drivers, {
10480
- writeTempPromptFile: () => writeTempPromptFile,
10481
- scheduleCleanup: () => scheduleCleanup,
10482
- getDriverRegistry: () => getDriverRegistry,
10483
- buildCombinedPrompt: () => buildCombinedPrompt,
10484
- ProcessDriver: () => ProcessDriver,
10485
- OpenCodeProcessDriver: () => OpenCodeProcessDriver
10542
+ // src/infrastructure/services/remote-agents/opencode/index.ts
10543
+ var exports_opencode = {};
10544
+ __export(exports_opencode, {
10545
+ OpenCodeAgentService: () => OpenCodeAgentService
10486
10546
  });
10487
- var init_agent_drivers = __esm(() => {
10488
- init_registry();
10489
- init_process_driver();
10490
- init_process_driver();
10491
- init_opencode_process_driver();
10547
+ var init_opencode = __esm(() => {
10548
+ init_opencode_agent_service();
10492
10549
  });
10493
10550
 
10494
- // src/commands/auth-status.ts
10551
+ // src/commands/auth-status/index.ts
10495
10552
  var exports_auth_status = {};
10496
10553
  __export(exports_auth_status, {
10497
10554
  authStatus: () => authStatus
10498
10555
  });
10499
- async function authStatus() {
10556
+ async function listAvailableModelsDefault() {
10557
+ try {
10558
+ const { OpenCodeAgentService: OpenCodeAgentService2 } = await Promise.resolve().then(() => (init_opencode(), exports_opencode));
10559
+ const agentService = new OpenCodeAgentService2;
10560
+ return await agentService.listModels();
10561
+ } catch {
10562
+ return [];
10563
+ }
10564
+ }
10565
+ async function createDefaultDeps3() {
10566
+ const client2 = await getConvexClient();
10567
+ return {
10568
+ backend: {
10569
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
10570
+ query: (endpoint, args) => client2.query(endpoint, args)
10571
+ },
10572
+ session: {
10573
+ loadAuthData,
10574
+ getAuthFilePath,
10575
+ isAuthenticated
10576
+ },
10577
+ getVersion,
10578
+ ensureMachineRegistered,
10579
+ listAvailableModels: listAvailableModelsDefault
10580
+ };
10581
+ }
10582
+ async function authStatus(deps) {
10583
+ const d = deps ?? await createDefaultDeps3();
10500
10584
  console.log(`
10501
10585
  ${"═".repeat(50)}`);
10502
10586
  console.log(`\uD83D\uDD10 AUTHENTICATION STATUS`);
10503
10587
  console.log(`${"═".repeat(50)}`);
10504
- const authData = loadAuthData();
10505
- if (!isAuthenticated() || !authData) {
10588
+ const authData = d.session.loadAuthData();
10589
+ if (!d.session.isAuthenticated() || !authData) {
10506
10590
  console.log(`
10507
10591
  ❌ Not authenticated`);
10508
10592
  console.log(`
@@ -10510,17 +10594,16 @@ ${"═".repeat(50)}`);
10510
10594
  return;
10511
10595
  }
10512
10596
  console.log(`
10513
- \uD83D\uDCC1 Auth file: ${getAuthFilePath()}`);
10597
+ \uD83D\uDCC1 Auth file: ${d.session.getAuthFilePath()}`);
10514
10598
  console.log(`\uD83D\uDCC5 Created: ${authData.createdAt}`);
10515
10599
  if (authData.deviceName) {
10516
10600
  console.log(`\uD83D\uDCBB Device: ${authData.deviceName}`);
10517
10601
  }
10518
- console.log(`\uD83D\uDCE6 CLI Version: ${getVersion()}`);
10602
+ console.log(`\uD83D\uDCE6 CLI Version: ${d.getVersion()}`);
10519
10603
  console.log(`
10520
10604
  ⏳ Validating session...`);
10521
10605
  try {
10522
- const client2 = await getConvexClient();
10523
- const validation = await client2.query(api.cliAuth.validateSession, {
10606
+ const validation = await d.backend.query(api.cliAuth.validateSession, {
10524
10607
  sessionId: authData.sessionId
10525
10608
  });
10526
10609
  if (validation.valid) {
@@ -10530,19 +10613,9 @@ ${"═".repeat(50)}`);
10530
10613
  console.log(`\uD83D\uDC64 User: ${validation.userName}`);
10531
10614
  }
10532
10615
  try {
10533
- const machineInfo = ensureMachineRegistered();
10534
- let availableModels = [];
10535
- try {
10536
- const { getDriverRegistry: getDriverRegistry2 } = await Promise.resolve().then(() => (init_agent_drivers(), exports_agent_drivers));
10537
- const registry = getDriverRegistry2();
10538
- for (const driver of registry.all()) {
10539
- if (driver.capabilities.dynamicModelDiscovery) {
10540
- const models = await driver.listModels();
10541
- availableModels = availableModels.concat(models);
10542
- }
10543
- }
10544
- } catch {}
10545
- await client2.mutation(api.machines.register, {
10616
+ const machineInfo = d.ensureMachineRegistered();
10617
+ const availableModels = await d.listAvailableModels();
10618
+ await d.backend.mutation(api.machines.register, {
10546
10619
  sessionId: authData.sessionId,
10547
10620
  machineId: machineInfo.machineId,
10548
10621
  hostname: machineInfo.hostname,
@@ -10586,47 +10659,46 @@ var init_auth_status = __esm(() => {
10586
10659
  init_version();
10587
10660
  });
10588
10661
 
10589
- // src/commands/update.ts
10662
+ // src/commands/update/index.ts
10590
10663
  var exports_update = {};
10591
10664
  __export(exports_update, {
10592
10665
  update: () => update
10593
10666
  });
10594
10667
  import { exec } from "node:child_process";
10595
10668
  import { promisify } from "node:util";
10596
- function getCurrentVersion() {
10597
- return getVersion();
10598
- }
10599
- async function isNpmAvailable() {
10600
- try {
10601
- await execAsync("npm --version");
10602
- return true;
10603
- } catch {
10604
- return false;
10605
- }
10606
- }
10607
- async function getLatestVersion() {
10608
- try {
10609
- const { stdout } = await execAsync("npm view chatroom-cli version");
10610
- return stdout.trim();
10611
- } catch {
10612
- return null;
10613
- }
10669
+ function createDefaultDeps4() {
10670
+ return {
10671
+ getVersion,
10672
+ exec: (cmd) => execAsync(cmd).then((r) => ({ stdout: r.stdout ?? "", stderr: r.stderr }))
10673
+ };
10614
10674
  }
10615
- async function update() {
10675
+ async function update(deps) {
10676
+ const d = deps ?? createDefaultDeps4();
10677
+ const log = console.log.bind(console);
10616
10678
  log(`
10617
10679
  \uD83D\uDD04 Checking for updates...
10618
10680
  `);
10619
- if (!await isNpmAvailable()) {
10681
+ try {
10682
+ await d.exec("npm --version");
10683
+ } catch {
10620
10684
  console.error("❌ npm is not available. Please install npm to update.");
10621
10685
  process.exit(1);
10686
+ return;
10622
10687
  }
10623
- const currentVersion = getCurrentVersion();
10688
+ const currentVersion = d.getVersion();
10624
10689
  log(` Current version: ${currentVersion}`);
10625
- const latestVersion = await getLatestVersion();
10690
+ let latestVersion = null;
10691
+ try {
10692
+ const { stdout } = await d.exec("npm view chatroom-cli version");
10693
+ latestVersion = stdout.trim() || null;
10694
+ } catch {
10695
+ latestVersion = null;
10696
+ }
10626
10697
  if (!latestVersion) {
10627
10698
  console.error("❌ Could not check for latest version.");
10628
10699
  console.error(" You can manually update with: npm install -g chatroom-cli@latest");
10629
10700
  process.exit(1);
10701
+ return;
10630
10702
  }
10631
10703
  log(` Latest version: ${latestVersion}`);
10632
10704
  if (currentVersion === latestVersion) {
@@ -10638,7 +10710,7 @@ async function update() {
10638
10710
  \uD83D\uDCE6 Updating to latest version...
10639
10711
  `);
10640
10712
  try {
10641
- const { stdout } = await execAsync("npm install -g chatroom-cli@latest");
10713
+ const { stdout } = await d.exec("npm install -g chatroom-cli@latest");
10642
10714
  if (stdout) {
10643
10715
  log(stdout);
10644
10716
  }
@@ -10653,52 +10725,231 @@ async function update() {
10653
10725
  Try running manually with sudo:`);
10654
10726
  console.error(" sudo npm install -g chatroom-cli@latest");
10655
10727
  process.exit(1);
10728
+ return;
10656
10729
  }
10657
10730
  }
10658
- var execAsync, log;
10731
+ var execAsync;
10659
10732
  var init_update = __esm(() => {
10660
10733
  init_version();
10661
10734
  execAsync = promisify(exec);
10662
- log = console.log.bind(console);
10663
10735
  });
10664
10736
 
10665
- // src/commands/register-agent.ts
10666
- var exports_register_agent = {};
10667
- __export(exports_register_agent, {
10668
- registerAgent: () => registerAgent
10737
+ // src/commands/init/index.ts
10738
+ var exports_init = {};
10739
+ __export(exports_init, {
10740
+ init: () => init
10669
10741
  });
10670
- async function registerAgent(chatroomId, options) {
10671
- const client2 = await getConvexClient();
10672
- const { role, type } = options;
10673
- const sessionId = getSessionId();
10674
- if (!sessionId) {
10675
- const otherUrls = getOtherSessionUrls();
10676
- const currentUrl = getConvexUrl();
10677
- console.error(`❌ Not authenticated for: ${currentUrl}`);
10678
- if (otherUrls.length > 0) {
10679
- console.error(`
10680
- \uD83D\uDCA1 You have sessions for other environments:`);
10681
- for (const url of otherUrls) {
10682
- console.error(` • ${url}`);
10742
+ import path from "path";
10743
+ async function createDefaultDeps5() {
10744
+ const fs = await import("fs/promises");
10745
+ return {
10746
+ fs: {
10747
+ access: async (p) => {
10748
+ await fs.access(p);
10749
+ },
10750
+ readFile: async (p, encoding) => {
10751
+ return fs.readFile(p, encoding);
10752
+ },
10753
+ writeFile: async (p, content, encoding) => {
10754
+ await fs.writeFile(p, content, encoding);
10683
10755
  }
10684
- console.error(`
10685
- To use a different environment, set CHATROOM_CONVEX_URL:`);
10686
- console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom register-agent ...`);
10687
- console.error(`
10688
- Or to authenticate for the current environment:`);
10689
10756
  }
10690
- console.error(` chatroom auth login`);
10691
- process.exit(1);
10692
- }
10693
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
10694
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
10695
- process.exit(1);
10757
+ };
10758
+ }
10759
+ async function fileExists(fsOps, filePath) {
10760
+ try {
10761
+ await fsOps.access(filePath);
10762
+ return true;
10763
+ } catch {
10764
+ return false;
10696
10765
  }
10697
- if (!/^[a-zA-Z0-9_]+$/.test(chatroomId)) {
10766
+ }
10767
+ function hasIntegrationSection(content) {
10768
+ return content.includes("<chatroom>");
10769
+ }
10770
+ function replaceIntegrationSection(content, newSection) {
10771
+ const start = content.indexOf("<chatroom>");
10772
+ const end = content.indexOf("</chatroom>");
10773
+ if (start === -1 || end === -1) {
10774
+ return content + `
10775
+
10776
+ ` + newSection;
10777
+ }
10778
+ const before = content.slice(0, start).replace(/\s+$/, "");
10779
+ return before + `
10780
+
10781
+ ` + newSection;
10782
+ }
10783
+ async function init(options = {}, deps) {
10784
+ const d = deps ?? await createDefaultDeps5();
10785
+ const targetDir = options.dir ?? process.cwd();
10786
+ const result = {
10787
+ filesModified: [],
10788
+ filesSkipped: [],
10789
+ filesCreated: []
10790
+ };
10791
+ const foundFiles = [];
10792
+ for (const filename of SUPPORTED_FILES) {
10793
+ const filePath = path.join(targetDir, filename);
10794
+ if (await fileExists(d.fs, filePath)) {
10795
+ foundFiles.push(filename);
10796
+ }
10797
+ }
10798
+ if (foundFiles.length === 0) {
10799
+ const agentsPath = path.join(targetDir, "AGENTS.md");
10800
+ try {
10801
+ await d.fs.writeFile(agentsPath, SECTION_6_TEXT, "utf-8");
10802
+ result.filesCreated.push("AGENTS.md");
10803
+ console.log("✅ Created AGENTS.md with CHATROOM INTEGRATION section");
10804
+ } catch (err) {
10805
+ console.error(`❌ Failed to create AGENTS.md: ${err}`);
10806
+ }
10807
+ } else {
10808
+ for (const filename of foundFiles) {
10809
+ const filePath = path.join(targetDir, filename);
10810
+ let content;
10811
+ try {
10812
+ content = await d.fs.readFile(filePath, "utf-8");
10813
+ } catch (err) {
10814
+ console.error(`❌ Failed to read ${filename}: ${err}`);
10815
+ continue;
10816
+ }
10817
+ if (hasIntegrationSection(content)) {
10818
+ try {
10819
+ const updatedContent = replaceIntegrationSection(content, SECTION_6_TEXT);
10820
+ await d.fs.writeFile(filePath, updatedContent, "utf-8");
10821
+ result.filesModified.push(filename);
10822
+ console.log(`✅ Updated CHATROOM INTEGRATION section in ${filename}`);
10823
+ } catch (err) {
10824
+ console.error(`❌ Failed to update ${filename}: ${err}`);
10825
+ }
10826
+ } else {
10827
+ try {
10828
+ const appendText = `
10829
+
10830
+ ---
10831
+
10832
+ ` + SECTION_6_TEXT;
10833
+ await d.fs.writeFile(filePath, content + appendText, "utf-8");
10834
+ result.filesModified.push(filename);
10835
+ console.log(`✅ Added CHATROOM INTEGRATION section to ${filename}`);
10836
+ } catch (err) {
10837
+ console.error(`❌ Failed to update ${filename}: ${err}`);
10838
+ }
10839
+ }
10840
+ }
10841
+ }
10842
+ console.log("");
10843
+ console.log("Summary:");
10844
+ if (result.filesCreated.length > 0) {
10845
+ console.log(` Created : ${result.filesCreated.join(", ")}`);
10846
+ }
10847
+ if (result.filesModified.length > 0) {
10848
+ console.log(` Updated : ${result.filesModified.join(", ")}`);
10849
+ }
10850
+ if (result.filesSkipped.length > 0) {
10851
+ console.log(` Skipped : ${result.filesSkipped.join(", ")} (already has integration section)`);
10852
+ }
10853
+ const totalActions = result.filesCreated.length + result.filesModified.length + result.filesSkipped.length;
10854
+ if (totalActions === 0) {
10855
+ console.log(" Nothing to do.");
10856
+ }
10857
+ return result;
10858
+ }
10859
+ var SUPPORTED_FILES, SECTION_6_TEXT = `<chatroom>
10860
+ ## CHATROOM INTEGRATION
10861
+
10862
+ ### Workflow Loop
10863
+
10864
+ \`\`\`
10865
+ get-next-task → do work → handoff → get-next-task → repeat
10866
+ \`\`\`
10867
+
10868
+ 1. **Run \`get-next-task\`** immediately via \`bash\` — block until task arrives
10869
+ 2. **Process** the task
10870
+ 3. **Run \`handoff\`** to signal completion
10871
+ 4. **Run \`get-next-task\`** again to wait for next task
10872
+
10873
+ ### Reliability
10874
+
10875
+ Always add these as the **last two todo items** for every task:
10876
+
10877
+ 1. **Handoff item** — contains the full handoff command (see Command Reference)
10878
+ 2. **Get-next-task item** — contains the full get-next-task command (see Command Reference)
10879
+
10880
+ This ensures you never forget to pass control back — just copy the command from the todo item and run it via \`bash\`.
10881
+
10882
+ ### Command Reference
10883
+
10884
+ | Action | Command |
10885
+ |--------|---------|
10886
+ | Fetch next task | \`CHATROOM_CONVEX_URL=<endpoint> chatroom get-next-task --chatroom-id=<id> --role=<role>\` |
10887
+ | Pass control | \`CHATROOM_CONVEX_URL=<endpoint> chatroom handoff --chatroom-id=<id> --role=<role> --next-role=<role>\` |
10888
+ | Refresh system prompt (after compaction) | \`CHATROOM_CONVEX_URL=<endpoint> chatroom get-system-prompt --chatroom-id=<id> --role=<role>\` |
10889
+
10890
+ ### Context Recovery
10891
+
10892
+ If you suspect compaction (context starts with "Summary of:", or you're unsure of your role):
10893
+
10894
+ 1. Run \`get-system-prompt\` to reload full instructions
10895
+ 2. Check todo list for last known step
10896
+ 3. Resume with \`get-next-task\` or \`handoff\`
10897
+ </chatroom>`;
10898
+ var init_init = __esm(() => {
10899
+ SUPPORTED_FILES = ["AGENTS.md", "CLAUDE.md"];
10900
+ });
10901
+
10902
+ // src/commands/register-agent/index.ts
10903
+ var exports_register_agent = {};
10904
+ __export(exports_register_agent, {
10905
+ registerAgent: () => registerAgent
10906
+ });
10907
+ async function createDefaultDeps6() {
10908
+ const client2 = await getConvexClient();
10909
+ return {
10910
+ backend: {
10911
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
10912
+ query: (endpoint, args) => client2.query(endpoint, args)
10913
+ },
10914
+ session: {
10915
+ getSessionId,
10916
+ getConvexUrl,
10917
+ getOtherSessionUrls
10918
+ }
10919
+ };
10920
+ }
10921
+ async function registerAgent(chatroomId, options, deps) {
10922
+ const d = deps ?? await createDefaultDeps6();
10923
+ const { role, type } = options;
10924
+ const sessionId = d.session.getSessionId();
10925
+ if (!sessionId) {
10926
+ const otherUrls = d.session.getOtherSessionUrls();
10927
+ const currentUrl = d.session.getConvexUrl();
10928
+ console.error(`❌ Not authenticated for: ${currentUrl}`);
10929
+ if (otherUrls.length > 0) {
10930
+ console.error(`
10931
+ \uD83D\uDCA1 You have sessions for other environments:`);
10932
+ for (const url of otherUrls) {
10933
+ console.error(` • ${url}`);
10934
+ }
10935
+ console.error(`
10936
+ To use a different environment, set CHATROOM_CONVEX_URL:`);
10937
+ console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom register-agent ...`);
10938
+ console.error(`
10939
+ Or to authenticate for the current environment:`);
10940
+ }
10941
+ console.error(` chatroom auth login`);
10942
+ process.exit(1);
10943
+ }
10944
+ if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
10945
+ console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
10946
+ process.exit(1);
10947
+ }
10948
+ if (!/^[a-zA-Z0-9_]+$/.test(chatroomId)) {
10698
10949
  console.error(`❌ Invalid chatroom ID format: ID must contain only alphanumeric characters and underscores`);
10699
10950
  process.exit(1);
10700
10951
  }
10701
- const chatroom = await client2.query(api.chatrooms.get, {
10952
+ const chatroom = await d.backend.query(api.chatrooms.get, {
10702
10953
  sessionId,
10703
10954
  chatroomId
10704
10955
  });
@@ -10711,15 +10962,10 @@ async function registerAgent(chatroomId, options) {
10711
10962
  const machineInfo = ensureMachineRegistered();
10712
10963
  let availableModels = [];
10713
10964
  try {
10714
- const registry = getDriverRegistry();
10715
- for (const driver of registry.all()) {
10716
- if (driver.capabilities.dynamicModelDiscovery) {
10717
- const models = await driver.listModels();
10718
- availableModels = availableModels.concat(models);
10719
- }
10720
- }
10965
+ const agentService = new OpenCodeAgentService;
10966
+ availableModels = await agentService.listModels();
10721
10967
  } catch {}
10722
- await client2.mutation(api.machines.register, {
10968
+ await d.backend.mutation(api.machines.register, {
10723
10969
  sessionId,
10724
10970
  machineId: machineInfo.machineId,
10725
10971
  hostname: machineInfo.hostname,
@@ -10729,7 +10975,7 @@ async function registerAgent(chatroomId, options) {
10729
10975
  availableModels
10730
10976
  });
10731
10977
  const agentHarness = machineInfo.availableHarnesses.length > 0 ? machineInfo.availableHarnesses[0] : undefined;
10732
- await client2.mutation(api.machines.saveTeamAgentConfig, {
10978
+ await d.backend.mutation(api.machines.saveTeamAgentConfig, {
10733
10979
  sessionId,
10734
10980
  chatroomId,
10735
10981
  role,
@@ -10750,7 +10996,7 @@ async function registerAgent(chatroomId, options) {
10750
10996
  }
10751
10997
  } else {
10752
10998
  try {
10753
- await client2.mutation(api.machines.saveTeamAgentConfig, {
10999
+ await d.backend.mutation(api.machines.saveTeamAgentConfig, {
10754
11000
  sessionId,
10755
11001
  chatroomId,
10756
11002
  role,
@@ -10765,14 +11011,11 @@ async function registerAgent(chatroomId, options) {
10765
11011
  }
10766
11012
  var init_register_agent = __esm(() => {
10767
11013
  init_api3();
10768
- init_agent_drivers();
10769
11014
  init_storage();
10770
11015
  init_client2();
10771
11016
  init_machine();
11017
+ init_opencode();
10772
11018
  });
10773
-
10774
- // ../../services/backend/config/reliability.ts
10775
- var HEARTBEAT_INTERVAL_MS = 30000, HEARTBEAT_TTL_MS = 90000, DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
10776
11019
  // ../../services/backend/prompts/base/cli/task-started/command.ts
10777
11020
  function taskStartedCommand(params) {
10778
11021
  const prefix = params.cliEnvPrefix || "";
@@ -10780,7 +11023,7 @@ function taskStartedCommand(params) {
10780
11023
  const role = params.role || "<role>";
10781
11024
  const taskId = params.taskId || "<task-id>";
10782
11025
  const classification = params.classification || "<question|new_feature|follow_up>";
10783
- const baseCmd = `${prefix}chatroom task-started --chatroom-id=${chatroomId} --role=${role} --task-id=${taskId} --origin-message-classification=${classification}`;
11026
+ const baseCmd = `${prefix}chatroom task-started --chatroom-id="${chatroomId}" --role="${role}" --task-id="${taskId}" --origin-message-classification=${classification}`;
10784
11027
  if (params.classification === "new_feature" || classification === "new_feature") {
10785
11028
  const title = params.title || "[Feature title]";
10786
11029
  const description = params.description || "[Feature description]";
@@ -10812,13 +11055,13 @@ var init_task_started = __esm(() => {
10812
11055
  init_main_prompt();
10813
11056
  });
10814
11057
 
10815
- // ../../services/backend/prompts/base/cli/wait-for-task/reminder.ts
10816
- function getWaitForTaskGuidance() {
11058
+ // ../../services/backend/prompts/base/cli/get-next-task/reminder.ts
11059
+ function getNextTaskGuidance() {
10817
11060
  return `\uD83D\uDD17 STAYING CONNECTED TO YOUR TEAM
10818
11061
 
10819
11062
  Your primary directive: Stay available to respond to user and team requests.
10820
11063
 
10821
- ⚠️ CRITICAL: Run wait-for-task in the FOREGROUND
11064
+ ⚠️ CRITICAL: Run get-next-task in the FOREGROUND
10822
11065
 
10823
11066
  Two requirements:
10824
11067
 
@@ -10831,16 +11074,27 @@ Two requirements:
10831
11074
  • Only proceed after the command exits (signal or task received)
10832
11075
 
10833
11076
  ⚠️ WHEN THE PROCESS IS TERMINATED OR TIMED OUT
10834
- • Your harness may kill long-running commands after a set duration
10835
- • When the command terminates unexpectedly:
10836
- 1. Do you have urgent pending work?
10837
- 2. Without wait-for-task, your team cannot reach you
10838
- 3. If no urgent work, reconnect immediately
11077
+
11078
+ \`\`\`
11079
+ @startuml
11080
+ start
11081
+ :Command terminated unexpectedly;
11082
+ if (Urgent pending work?) then (yes)
11083
+ :Finish urgent work;
11084
+ :Reconnect with get-next-task;
11085
+ else (no)
11086
+ :Reconnect immediately;
11087
+ note right: Team cannot reach you without it
11088
+ endif
11089
+ stop
11090
+ @enduml
11091
+ \`\`\`
10839
11092
 
10840
11093
  \uD83D\uDCCB BACKLOG TASKS
10841
11094
  chatroom backlog list --chatroom-id=<chatroomId> --role=<role> --status=backlog
10842
11095
  chatroom backlog --help`;
10843
11096
  }
11097
+ var init_reminder = () => {};
10844
11098
  // ../../services/backend/prompts/config/index.ts
10845
11099
  class PromptConfigImpl {
10846
11100
  getConvexURL() {
@@ -10893,30 +11147,262 @@ var init_getting_started_content = __esm(() => {
10893
11147
  // ../../services/backend/prompts/base/cli/index.ts
10894
11148
  var init_cli = __esm(() => {
10895
11149
  init_task_started();
11150
+ init_reminder();
10896
11151
  init_getting_started_content();
10897
11152
  });
10898
11153
 
10899
- // ../../services/backend/prompts/base/cli/wait-for-task/command.ts
10900
- function waitForTaskCommand(params) {
11154
+ // ../../services/backend/config/errorCodes.ts
11155
+ var BACKEND_ERROR_CODES, FATAL_ERROR_CODES;
11156
+ var init_errorCodes = __esm(() => {
11157
+ BACKEND_ERROR_CODES = {
11158
+ PARTICIPANT_NOT_FOUND: "PARTICIPANT_NOT_FOUND",
11159
+ CHATROOM_NOT_FOUND: "CHATROOM_NOT_FOUND",
11160
+ SESSION_INVALID: "SESSION_INVALID"
11161
+ };
11162
+ FATAL_ERROR_CODES = [
11163
+ BACKEND_ERROR_CODES.PARTICIPANT_NOT_FOUND,
11164
+ BACKEND_ERROR_CODES.CHATROOM_NOT_FOUND,
11165
+ BACKEND_ERROR_CODES.SESSION_INVALID
11166
+ ];
11167
+ });
11168
+
11169
+ // ../../services/backend/prompts/base/cli/get-next-task/command.ts
11170
+ function getNextTaskCommand(params) {
10901
11171
  const prefix = params.cliEnvPrefix || "";
10902
11172
  const chatroomId = params.chatroomId || "<chatroom-id>";
10903
11173
  const role = params.role || "<role>";
10904
- return `${prefix}chatroom wait-for-task --chatroom-id=${chatroomId} --role=${role}`;
10905
- }
11174
+ return `${prefix}chatroom get-next-task --chatroom-id="${chatroomId}" --role="${role}"`;
11175
+ }
11176
+ var init_command = () => {};
11177
+
11178
+ // src/commands/get-next-task/session.ts
11179
+ class GetNextTaskSession {
11180
+ unsubscribe = null;
11181
+ cleanedUp = false;
11182
+ taskProcessed = false;
11183
+ fatalExitTriggered = false;
11184
+ chatroomId;
11185
+ role;
11186
+ silent;
11187
+ sessionId;
11188
+ connectionId;
11189
+ cliEnvPrefix;
11190
+ client;
11191
+ constructor(params) {
11192
+ this.chatroomId = params.chatroomId;
11193
+ this.role = params.role;
11194
+ this.silent = params.silent;
11195
+ this.sessionId = params.sessionId;
11196
+ this.connectionId = params.connectionId;
11197
+ this.cliEnvPrefix = params.cliEnvPrefix;
11198
+ this.client = params.client;
11199
+ }
11200
+ async start() {
11201
+ this.registerSignalHandlers();
11202
+ await this.subscribe();
11203
+ }
11204
+ logAndExit(exitCode, event, message, guidance) {
11205
+ const timestamp = new Date().toISOString().replace("T", " ").substring(0, 19);
11206
+ const safeEvent = sanitizeForTerminal(event);
11207
+ const safeMessage = sanitizeForTerminal(message);
11208
+ const safeGuidance = sanitizeForTerminal(guidance);
11209
+ console.log(`
11210
+ ${"─".repeat(50)}`);
11211
+ console.log(`[EVENT: ${safeEvent}]
11212
+ `);
11213
+ console.log(`[${timestamp}] ${safeMessage}
11214
+ `);
11215
+ if (safeGuidance) {
11216
+ console.log(safeGuidance);
11217
+ }
11218
+ console.log(`
11219
+ To reconnect, run:`);
11220
+ console.log(getNextTaskCommand({
11221
+ chatroomId: this.chatroomId,
11222
+ role: this.role,
11223
+ cliEnvPrefix: this.cliEnvPrefix
11224
+ }));
11225
+ console.log(`${"─".repeat(50)}`);
11226
+ this.cleanup();
11227
+ process.exit(exitCode);
11228
+ }
11229
+ async cleanup() {
11230
+ if (this.cleanedUp)
11231
+ return;
11232
+ this.cleanedUp = true;
11233
+ if (this.unsubscribe) {
11234
+ this.unsubscribe();
11235
+ }
11236
+ try {
11237
+ await this.client.mutation(api.participants.join, {
11238
+ sessionId: this.sessionId,
11239
+ chatroomId: this.chatroomId,
11240
+ role: this.role,
11241
+ action: "get-next-task:stopped"
11242
+ });
11243
+ } catch {}
11244
+ try {
11245
+ await this.client.mutation(api.participants.leave, {
11246
+ sessionId: this.sessionId,
11247
+ chatroomId: this.chatroomId,
11248
+ role: this.role
11249
+ });
11250
+ } catch {}
11251
+ }
11252
+ async subscribe() {
11253
+ const wsClient2 = await getConvexWsClient();
11254
+ this.unsubscribe = wsClient2.onUpdate(api.tasks.getPendingTasksForRole, {
11255
+ sessionId: this.sessionId,
11256
+ chatroomId: this.chatroomId,
11257
+ role: this.role,
11258
+ connectionId: this.connectionId
11259
+ }, (response) => {
11260
+ this.handleSubscriptionResponse(response).catch((error) => {
11261
+ console.error(`❌ Error processing task: ${error.message}`);
11262
+ });
11263
+ }, (error) => {
11264
+ this.handleSubscriptionError(error, "task subscription");
11265
+ });
11266
+ }
11267
+ async handleSubscriptionResponse(response) {
11268
+ if (this.taskProcessed)
11269
+ return;
11270
+ switch (response.type) {
11271
+ case "no_tasks":
11272
+ return;
11273
+ case "superseded":
11274
+ this.handleSuperseded();
11275
+ return;
11276
+ case "grace_period":
11277
+ this.handleGracePeriod(response);
11278
+ return;
11279
+ case "error":
11280
+ this.handleError(response);
11281
+ return;
11282
+ case "reconnect":
11283
+ this.handleReconnect(response);
11284
+ return;
11285
+ case "tasks":
11286
+ await this.handleTasks(response);
11287
+ return;
11288
+ }
11289
+ }
11290
+ handleSuperseded() {
11291
+ this.logAndExit(0, "superseded", "Another get-next-task process started for this role.", `Impact: This process is being replaced by the newer connection.
11292
+ ` + "Action: This is expected if you started a new get-next-task session.");
11293
+ }
11294
+ handleGracePeriod(response) {
11295
+ const remainingSec = Math.ceil(response.remainingMs / 1000);
11296
+ this.logAndExit(0, "grace_period", `Task ${response.taskId} was recently acknowledged (${remainingSec}s grace remaining).`, `Impact: Another agent may still be processing this task.
11297
+ ` + "Action: Wait for the grace period to expire, then reconnect.");
11298
+ }
11299
+ handleReconnect(response) {
11300
+ this.logAndExit(0, "reconnect", `Backend requested reconnect: ${response.reason}`, "Action: Reconnect to resume listening for tasks.");
11301
+ }
11302
+ handleError(response) {
11303
+ if (response.fatal) {
11304
+ if (this.fatalExitTriggered)
11305
+ return;
11306
+ this.fatalExitTriggered = true;
11307
+ this.logAndExit(1, "error (fatal)", `❌ FATAL ERROR — [${response.code}] ${response.message}`, "This is an unrecoverable error. The process will now exit.");
11308
+ }
11309
+ this.logAndExit(0, "error (non-fatal)", `⚠️ Non-fatal error: [${response.code}] ${response.message}`, "Action: Reconnect to resume listening for tasks.");
11310
+ }
11311
+ handleSubscriptionError(error, source) {
11312
+ if (error instanceof ConvexError) {
11313
+ const data = error.data;
11314
+ if (data?.code && FATAL_ERROR_CODES.includes(data.code)) {
11315
+ if (this.fatalExitTriggered)
11316
+ return;
11317
+ this.fatalExitTriggered = true;
11318
+ this.logAndExit(1, "subscription_error (fatal)", `❌ FATAL ERROR in ${source} — [${data.code}] ${data.message}`, "This is an unrecoverable error. The process will now exit.");
11319
+ return;
11320
+ }
11321
+ this.logAndExit(0, "subscription_error (non-fatal)", `⚠️ Non-fatal error in ${source}: [${data?.code}] ${data?.message ?? error.message}`, "Action: Reconnect to resume listening for tasks.");
11322
+ return;
11323
+ }
11324
+ this.logAndExit(0, "subscription_error (transient)", `⚠️ Transient error in ${source}: ${error.message}`, "Action: Reconnect to resume listening for tasks.");
11325
+ }
11326
+ async handleTasks(response) {
11327
+ const pendingTasks = response.tasks;
11328
+ const taskWithMessage = pendingTasks.length > 0 ? pendingTasks[0] : null;
11329
+ if (!taskWithMessage) {
11330
+ return;
11331
+ }
11332
+ const { task, message } = taskWithMessage;
11333
+ if (task.status === "pending") {
11334
+ try {
11335
+ await this.client.mutation(api.tasks.claimTask, {
11336
+ sessionId: this.sessionId,
11337
+ chatroomId: this.chatroomId,
11338
+ role: this.role,
11339
+ taskId: task._id
11340
+ });
11341
+ } catch (_claimError) {
11342
+ console.log(`\uD83D\uDD04 Task already claimed by another agent, continuing to wait...`);
11343
+ return;
11344
+ }
11345
+ }
11346
+ this.taskProcessed = true;
11347
+ if (this.unsubscribe)
11348
+ this.unsubscribe();
11349
+ this.cleanedUp = true;
11350
+ try {
11351
+ if (message) {
11352
+ await this.client.mutation(api.messages.claimMessage, {
11353
+ sessionId: this.sessionId,
11354
+ messageId: message._id,
11355
+ role: this.role
11356
+ });
11357
+ }
11358
+ const taskDeliveryPrompt = await this.client.query(api.messages.getTaskDeliveryPrompt, {
11359
+ sessionId: this.sessionId,
11360
+ chatroomId: this.chatroomId,
11361
+ role: this.role,
11362
+ taskId: task._id,
11363
+ messageId: message?._id,
11364
+ convexUrl: getConvexUrl()
11365
+ });
11366
+ const taskReceivedTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11367
+ console.log(`
11368
+ [${taskReceivedTime}] \uD83D\uDCE8 Task received!
11369
+ `);
11370
+ console.log(sanitizeForTerminal(taskDeliveryPrompt.fullCliOutput));
11371
+ process.exit(0);
11372
+ } catch (deliveryError) {
11373
+ this.logAndExit(1, "task_delivery_failed", `⚠️ TASK CLAIMED BUT DELIVERY FAILED — ${sanitizeUnknownForTerminal(deliveryError.message)}`, `Task ID: ${task._id}
11374
+ ` + `The task has been claimed for your role.
11375
+ ` + `Use context read to see your current task:
10906
11376
 
10907
- // src/config.ts
10908
- var DEFAULT_ACTIVE_TIMEOUT_MS, WEB_SERVER_PORT;
10909
- var init_config2 = __esm(() => {
10910
- DEFAULT_ACTIVE_TIMEOUT_MS = 60 * 60 * 1000;
10911
- WEB_SERVER_PORT = parseInt(process.env.WEB_PORT || "3456", 10);
11377
+ ` + ` ${this.cliEnvPrefix} chatroom context read --chatroom-id=${this.chatroomId} --role=${this.role}`);
11378
+ }
11379
+ }
11380
+ registerSignalHandlers() {
11381
+ const handleSignal = (_signal) => {
11382
+ this.logAndExit(0, `signal (${_signal})`, `Process interrupted (${_signal}).`, `Impact: You are no longer listening for tasks.
11383
+ ` + "Action: Run the reconnect command immediately to resume availability.");
11384
+ };
11385
+ process.on("SIGINT", () => handleSignal("SIGINT"));
11386
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
11387
+ process.on("SIGHUP", () => handleSignal("SIGHUP"));
11388
+ }
11389
+ }
11390
+ var init_session = __esm(() => {
11391
+ init_errorCodes();
11392
+ init_command();
11393
+ init_values();
11394
+ init_api3();
11395
+ init_client2();
10912
11396
  });
10913
11397
 
10914
- // src/commands/wait-for-task.ts
10915
- var exports_wait_for_task = {};
10916
- __export(exports_wait_for_task, {
10917
- waitForTask: () => waitForTask
11398
+ // src/commands/get-next-task/index.ts
11399
+ var exports_get_next_task = {};
11400
+ __export(exports_get_next_task, {
11401
+ getNextTask: () => getNextTask,
11402
+ WaitForTaskSession: () => GetNextTaskSession,
11403
+ GetNextTaskSession: () => GetNextTaskSession
10918
11404
  });
10919
- async function waitForTask(chatroomId, options) {
11405
+ async function getNextTask(chatroomId, options) {
10920
11406
  const client2 = await getConvexClient();
10921
11407
  const { role, silent } = options;
10922
11408
  const convexUrl = getConvexUrl();
@@ -10933,7 +11419,7 @@ async function waitForTask(chatroomId, options) {
10933
11419
  }
10934
11420
  console.error(`
10935
11421
  To use a different environment, set CHATROOM_CONVEX_URL:`);
10936
- console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom wait-for-task ...`);
11422
+ console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom get-next-task ...`);
10937
11423
  console.error(`
10938
11424
  Or to authenticate for the current environment:`);
10939
11425
  }
@@ -10969,13 +11455,8 @@ async function waitForTask(chatroomId, options) {
10969
11455
  const machineInfo = ensureMachineRegistered();
10970
11456
  let availableModels = [];
10971
11457
  try {
10972
- const registry = getDriverRegistry();
10973
- for (const driver of registry.all()) {
10974
- if (driver.capabilities.dynamicModelDiscovery) {
10975
- const models = await driver.listModels();
10976
- availableModels = availableModels.concat(models);
10977
- }
10978
- }
11458
+ const agentService = new OpenCodeAgentService;
11459
+ availableModels = await agentService.listModels();
10979
11460
  } catch {}
10980
11461
  await client2.mutation(api.machines.register, {
10981
11462
  sessionId,
@@ -10989,7 +11470,6 @@ async function waitForTask(chatroomId, options) {
10989
11470
  const agentType = options.agentType ?? (machineInfo.availableHarnesses.length > 0 ? machineInfo.availableHarnesses[0] : undefined);
10990
11471
  if (agentType) {
10991
11472
  const workingDir = process.cwd();
10992
- updateAgentContext(chatroomId, role, agentType, workingDir);
10993
11473
  await client2.mutation(api.machines.updateAgentConfig, {
10994
11474
  sessionId,
10995
11475
  machineId: machineInfo.machineId,
@@ -11001,16 +11481,26 @@ async function waitForTask(chatroomId, options) {
11001
11481
  }
11002
11482
  } catch (machineError) {
11003
11483
  if (!silent) {
11004
- console.warn(`⚠️ Machine registration failed: ${machineError.message}`);
11484
+ console.warn(`⚠️ Machine registration failed: ${sanitizeUnknownForTerminal(machineError.message)}`);
11005
11485
  }
11006
11486
  }
11007
11487
  const connectionId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
11488
+ let participantAgentType;
11489
+ try {
11490
+ const teamConfigs = await client2.query(api.machines.getTeamAgentConfigs, {
11491
+ sessionId,
11492
+ chatroomId
11493
+ });
11494
+ const roleConfig = teamConfigs?.find((c) => c.role.toLowerCase() === role.toLowerCase());
11495
+ participantAgentType = roleConfig?.type;
11496
+ } catch {}
11008
11497
  await client2.mutation(api.participants.join, {
11009
11498
  sessionId,
11010
11499
  chatroomId,
11011
11500
  role,
11012
- readyUntil: Date.now() + HEARTBEAT_TTL_MS,
11013
- connectionId
11501
+ action: "get-next-task:started",
11502
+ connectionId,
11503
+ agentType: participantAgentType
11014
11504
  });
11015
11505
  const connectionTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11016
11506
  if (!silent) {
@@ -11034,7 +11524,7 @@ async function waitForTask(chatroomId, options) {
11034
11524
  console.log("\uD83D\uDCCB AGENT INITIALIZATION PROMPT");
11035
11525
  console.log("═".repeat(50));
11036
11526
  console.log("");
11037
- console.log(getWaitForTaskGuidance());
11527
+ console.log(getNextTaskGuidance());
11038
11528
  console.log("");
11039
11529
  console.log("═".repeat(50));
11040
11530
  console.log("");
@@ -11046,192 +11536,58 @@ async function waitForTask(chatroomId, options) {
11046
11536
  }
11047
11537
  }
11048
11538
  } catch {}
11049
- const heartbeatTimer = setInterval(() => {
11050
- client2.mutation(api.participants.heartbeat, {
11051
- sessionId,
11052
- chatroomId,
11053
- role,
11054
- connectionId
11055
- }).then((result) => {
11056
- if (result?.status === "rejoin_required") {
11057
- if (!silent) {
11058
- console.warn(`⚠️ Participant record missing — re-joining chatroom`);
11059
- }
11060
- return client2.mutation(api.participants.join, {
11061
- sessionId,
11062
- chatroomId,
11063
- role,
11064
- readyUntil: Date.now() + HEARTBEAT_TTL_MS,
11065
- connectionId
11066
- });
11067
- }
11068
- }).catch((err) => {
11069
- if (!silent) {
11070
- console.warn(`⚠️ Heartbeat failed: ${err.message}`);
11071
- }
11072
- });
11073
- }, HEARTBEAT_INTERVAL_MS);
11074
- heartbeatTimer.unref();
11075
- let cleanedUp = false;
11076
- const cleanup = async () => {
11077
- if (cleanedUp)
11078
- return;
11079
- cleanedUp = true;
11080
- clearInterval(heartbeatTimer);
11081
- try {
11082
- await client2.mutation(api.participants.leave, {
11083
- sessionId,
11084
- chatroomId,
11085
- role
11086
- });
11087
- } catch {}
11088
- };
11089
- let taskProcessed = false;
11090
- let unsubscribe = null;
11091
- const handlePendingTasks = async (pendingTasks) => {
11092
- if (taskProcessed)
11093
- return;
11094
- const currentConnectionId = await client2.query(api.participants.getConnectionId, {
11095
- sessionId,
11096
- chatroomId,
11097
- role
11098
- });
11099
- if (currentConnectionId && currentConnectionId !== connectionId) {
11100
- if (unsubscribe)
11101
- unsubscribe();
11102
- clearInterval(heartbeatTimer);
11103
- cleanedUp = true;
11104
- const takeoverTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11105
- console.log(`
11106
- ${"─".repeat(50)}`);
11107
- console.log(`⚠️ CONNECTION SUPERSEDED
11108
- `);
11109
- console.log(`[${takeoverTime}] Why: Another wait-for-task process started for this role`);
11110
- console.log(`Impact: This process is being replaced by the newer connection`);
11111
- console.log(`Action: This is expected if you started a new wait-for-task session
11112
- `);
11113
- console.log(`If you meant to use THIS terminal, run:`);
11114
- console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
11115
- console.log(`${"─".repeat(50)}`);
11116
- process.exit(0);
11117
- }
11118
- const taskWithMessage = pendingTasks.length > 0 ? pendingTasks[0] : null;
11119
- if (!taskWithMessage) {
11120
- return;
11121
- }
11122
- const { task, message } = taskWithMessage;
11123
- if (task.status === "acknowledged") {
11124
- const acknowledgedAt = task.acknowledgedAt || task.updatedAt;
11125
- const elapsedMs = Date.now() - acknowledgedAt;
11126
- const RECOVERY_GRACE_PERIOD_MS = 60 * 1000;
11127
- if (elapsedMs < RECOVERY_GRACE_PERIOD_MS) {
11128
- const remainingSec = Math.ceil((RECOVERY_GRACE_PERIOD_MS - elapsedMs) / 1000);
11129
- console.log(`\uD83D\uDD04 Task was recently acknowledged (${remainingSec}s remaining). ` + `Re-run wait-for-task in 1 minute to recover it if the other agent is unresponsive.`);
11130
- return;
11131
- }
11132
- } else {
11133
- try {
11134
- await client2.mutation(api.tasks.claimTask, {
11135
- sessionId,
11136
- chatroomId,
11137
- role
11138
- });
11139
- } catch (_claimError) {
11140
- console.log(`\uD83D\uDD04 Task already claimed by another agent, continuing to wait...`);
11141
- return;
11142
- }
11143
- }
11144
- taskProcessed = true;
11145
- if (message) {
11146
- await client2.mutation(api.messages.claimMessage, {
11147
- sessionId,
11148
- messageId: message._id,
11149
- role
11150
- });
11151
- }
11152
- if (unsubscribe)
11153
- unsubscribe();
11154
- clearInterval(heartbeatTimer);
11155
- cleanedUp = true;
11156
- const activeUntil = Date.now() + DEFAULT_ACTIVE_TIMEOUT_MS;
11157
- await client2.mutation(api.participants.updateStatus, {
11158
- sessionId,
11159
- chatroomId,
11160
- role,
11161
- status: "active",
11162
- expiresAt: activeUntil
11163
- });
11164
- const taskDeliveryPrompt = await client2.query(api.messages.getTaskDeliveryPrompt, {
11165
- sessionId,
11166
- chatroomId,
11167
- role,
11168
- taskId: task._id,
11169
- messageId: message?._id,
11170
- convexUrl
11171
- });
11172
- const taskReceivedTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11173
- console.log(`
11174
- [${taskReceivedTime}] \uD83D\uDCE8 Task received!
11175
- `);
11176
- console.log(taskDeliveryPrompt.fullCliOutput);
11177
- process.exit(0);
11178
- };
11179
- const wsClient2 = await getConvexWsClient();
11180
- unsubscribe = wsClient2.onUpdate(api.tasks.getPendingTasksForRole, {
11181
- sessionId,
11539
+ const session = new GetNextTaskSession({
11182
11540
  chatroomId,
11183
- role
11184
- }, (pendingTasks) => {
11185
- handlePendingTasks(pendingTasks).catch((error) => {
11186
- console.error(`❌ Error processing task: ${error.message}`);
11187
- });
11541
+ role,
11542
+ silent: !!silent,
11543
+ sessionId,
11544
+ connectionId,
11545
+ cliEnvPrefix,
11546
+ client: client2
11188
11547
  });
11189
- const handleSignal = (_signal) => {
11190
- if (unsubscribe)
11191
- unsubscribe();
11192
- cleanup().finally(() => {
11193
- const signalTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11194
- console.log(`
11195
- ${"─".repeat(50)}`);
11196
- console.log(`⚠️ RECONNECTION REQUIRED
11197
- `);
11198
- console.log(`[${signalTime}] Why: Process interrupted (unexpected termination)`);
11199
- console.log(`Impact: You are no longer listening for tasks`);
11200
- console.log(`Action: Run this command immediately to resume availability
11201
- `);
11202
- console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
11203
- console.log(`${"─".repeat(50)}`);
11204
- process.exit(0);
11205
- });
11206
- };
11207
- process.on("SIGINT", () => handleSignal("SIGINT"));
11208
- process.on("SIGTERM", () => handleSignal("SIGTERM"));
11209
- process.on("SIGHUP", () => handleSignal("SIGHUP"));
11548
+ await session.start();
11210
11549
  }
11211
- var init_wait_for_task = __esm(() => {
11550
+ var init_get_next_task = __esm(() => {
11212
11551
  init_cli();
11213
11552
  init_env();
11553
+ init_session();
11214
11554
  init_api3();
11215
- init_config2();
11216
- init_agent_drivers();
11217
11555
  init_storage();
11218
11556
  init_client2();
11219
11557
  init_machine();
11558
+ init_opencode();
11559
+ init_error_formatting();
11560
+ init_session();
11561
+ init_session();
11220
11562
  });
11221
11563
 
11222
- // src/commands/task-started.ts
11564
+ // src/commands/task-started/index.ts
11223
11565
  var exports_task_started2 = {};
11224
11566
  __export(exports_task_started2, {
11225
11567
  taskStarted: () => taskStarted
11226
11568
  });
11227
- async function taskStarted(chatroomId, options) {
11569
+ async function createDefaultDeps7() {
11228
11570
  const client2 = await getConvexClient();
11229
- const { role, originMessageClassification, rawStdin, taskId, noClassify } = options;
11230
- const convexUrl = getConvexUrl();
11231
- const cliEnvPrefix = getCliEnvPrefix(convexUrl);
11232
- const sessionId = getSessionId();
11571
+ return {
11572
+ backend: {
11573
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
11574
+ query: (endpoint, args) => client2.query(endpoint, args)
11575
+ },
11576
+ session: {
11577
+ getSessionId,
11578
+ getConvexUrl,
11579
+ getOtherSessionUrls
11580
+ }
11581
+ };
11582
+ }
11583
+ async function taskStarted(chatroomId, options, deps) {
11584
+ const d = deps ?? await createDefaultDeps7();
11585
+ const { role, originMessageClassification, rawStdin, taskId, noClassify } = options;
11586
+ const convexUrl = d.session.getConvexUrl();
11587
+ const cliEnvPrefix = getCliEnvPrefix(convexUrl);
11588
+ const sessionId = d.session.getSessionId();
11233
11589
  if (!sessionId) {
11234
- const otherUrls = getOtherSessionUrls();
11590
+ const otherUrls = d.session.getOtherSessionUrls();
11235
11591
  console.error(`❌ Not authenticated for: ${convexUrl}`);
11236
11592
  if (otherUrls.length > 0) {
11237
11593
  console.error(`
@@ -11306,7 +11662,7 @@ How to implement it' | ${taskStartedCommand({
11306
11662
  })}`);
11307
11663
  process.exit(1);
11308
11664
  }
11309
- targetTask = await client2.query(api.tasks.getTask, {
11665
+ targetTask = await d.backend.query(api.tasks.getTask, {
11310
11666
  sessionId,
11311
11667
  chatroomId,
11312
11668
  taskId
@@ -11317,7 +11673,7 @@ How to implement it' | ${taskStartedCommand({
11317
11673
  process.exit(1);
11318
11674
  }
11319
11675
  try {
11320
- await client2.mutation(api.tasks.startTask, {
11676
+ await d.backend.mutation(api.tasks.startTask, {
11321
11677
  sessionId,
11322
11678
  chatroomId,
11323
11679
  role,
@@ -11337,13 +11693,13 @@ How to implement it' | ${taskStartedCommand({
11337
11693
  return;
11338
11694
  }
11339
11695
  try {
11340
- const result = await client2.mutation(api.messages.taskStarted, {
11696
+ const result = await d.backend.mutation(api.messages.taskStarted, {
11341
11697
  sessionId,
11342
11698
  chatroomId,
11343
11699
  role,
11344
11700
  taskId,
11345
11701
  originMessageClassification,
11346
- convexUrl: getConvexUrl(),
11702
+ convexUrl: d.session.getConvexUrl(),
11347
11703
  ...rawStdin && { rawStdin }
11348
11704
  });
11349
11705
  console.log(`✅ Task acknowledged and classified`);
@@ -11363,10 +11719,10 @@ How to implement it' | ${taskStartedCommand({
11363
11719
  console.error(` Stack trace:`);
11364
11720
  stackLines.forEach((line) => console.error(` ${line}`));
11365
11721
  }
11366
- if (typeof error === "object" && error !== null) {
11367
- const errObj = error;
11368
- if (errObj.data) {
11369
- console.error(` Server details:`, JSON.stringify(errObj.data, null, 2));
11722
+ if (typeof error === "object" && error !== null && "data" in error) {
11723
+ const errData = error.data;
11724
+ if (errData) {
11725
+ console.error(` Server details:`, JSON.stringify(errData, null, 2));
11370
11726
  }
11371
11727
  }
11372
11728
  process.exit(1);
@@ -11379,90 +11735,6 @@ var init_task_started2 = __esm(() => {
11379
11735
  init_client2();
11380
11736
  });
11381
11737
 
11382
- // src/commands/task-complete.ts
11383
- var exports_task_complete = {};
11384
- __export(exports_task_complete, {
11385
- taskComplete: () => taskComplete
11386
- });
11387
- async function taskComplete(chatroomId, options) {
11388
- const client2 = await getConvexClient();
11389
- const { role } = options;
11390
- const sessionId = getSessionId();
11391
- if (!sessionId) {
11392
- const otherUrls = getOtherSessionUrls();
11393
- const currentUrl = getConvexUrl();
11394
- formatAuthError(currentUrl, otherUrls);
11395
- process.exit(1);
11396
- }
11397
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
11398
- formatChatroomIdError(chatroomId);
11399
- process.exit(1);
11400
- }
11401
- let result;
11402
- try {
11403
- result = await client2.mutation(api.tasks.completeTask, {
11404
- sessionId,
11405
- chatroomId,
11406
- role
11407
- });
11408
- } catch (error) {
11409
- console.error(`
11410
- ❌ ERROR: Task completion failed`);
11411
- if (error instanceof ConvexError) {
11412
- const errorData = error.data;
11413
- console.error(`
11414
- ${errorData.message || "An unexpected error occurred"}`);
11415
- if (process.env.CHATROOM_DEBUG === "true") {
11416
- console.error(`
11417
- \uD83D\uDD0D Debug Info:`);
11418
- console.error(JSON.stringify(errorData, null, 2));
11419
- }
11420
- const convexUrl2 = getConvexUrl();
11421
- if (errorData.code === "AUTH_FAILED") {
11422
- console.error(`
11423
- \uD83D\uDCA1 Try authenticating again:`);
11424
- console.error(` chatroom auth ${convexUrl2}`);
11425
- }
11426
- } else {
11427
- console.error(`
11428
- ${error instanceof Error ? error.message : String(error)}`);
11429
- if (process.env.CHATROOM_DEBUG === "true") {
11430
- console.error(`
11431
- \uD83D\uDD0D Debug Info:`);
11432
- console.error(error);
11433
- }
11434
- }
11435
- console.error(`
11436
- \uD83D\uDCDA Need help? Check the docs or run:`);
11437
- console.error(` chatroom task-complete --help`);
11438
- process.exit(1);
11439
- }
11440
- if (!result.completed) {
11441
- formatError("No task to complete", [
11442
- "Make sure you have an in_progress task before completing.",
11443
- "Run `chatroom wait-for-task` to receive and start a task first."
11444
- ]);
11445
- process.exit(1);
11446
- }
11447
- console.log(`✅ Task completed successfully`);
11448
- console.log(` Tasks completed: ${result.completedCount}`);
11449
- if (result.promoted) {
11450
- console.log(` Promoted next task: ${result.promoted}`);
11451
- }
11452
- const convexUrl = getConvexUrl();
11453
- const cliEnvPrefix = getCliEnvPrefix(convexUrl);
11454
- console.log(`
11455
- ⏳ Now run wait-for-task to wait for your next assignment:`);
11456
- console.log(` ${waitForTaskCommand({ chatroomId, role, cliEnvPrefix })}`);
11457
- }
11458
- var init_task_complete = __esm(() => {
11459
- init_env();
11460
- init_values();
11461
- init_api3();
11462
- init_storage();
11463
- init_client2();
11464
- });
11465
-
11466
11738
  // src/utils/serialization/decode/index.ts
11467
11739
  var exports_decode = {};
11468
11740
  __export(exports_decode, {
@@ -11473,7 +11745,10 @@ __export(exports_decode, {
11473
11745
  function decode(input, options = {}) {
11474
11746
  const { singleParam, expectedParams, requiredParams } = options;
11475
11747
  if (singleParam) {
11476
- return { [singleParam]: input.trim() };
11748
+ const trimmed = input.trim();
11749
+ const singleParamDelimiter = `---${singleParam.toUpperCase()}---`;
11750
+ const stripped = trimmed.startsWith(singleParamDelimiter) ? trimmed.slice(singleParamDelimiter.length).trim() : trimmed;
11751
+ return { [singleParam]: stripped };
11477
11752
  }
11478
11753
  return decodeMultiParam(input, expectedParams, requiredParams);
11479
11754
  }
@@ -11589,23 +11864,38 @@ function handoffCommand(params) {
11589
11864
  const chatroomId = params.chatroomId || "<chatroom-id>";
11590
11865
  const role = params.role || "<role>";
11591
11866
  const nextRole = params.nextRole || "<target>";
11592
- return `${prefix}chatroom handoff --chatroom-id=${chatroomId} --role=${role} --next-role=${nextRole} << 'EOF'
11867
+ return `${prefix}chatroom handoff --chatroom-id="${chatroomId}" --role="${role}" --next-role="${nextRole}" << 'EOF'
11868
+ ---MESSAGE---
11593
11869
  [Your message here]
11594
11870
  EOF`;
11595
11871
  }
11596
11872
 
11597
- // src/commands/handoff.ts
11873
+ // src/commands/handoff/index.ts
11598
11874
  var exports_handoff = {};
11599
11875
  __export(exports_handoff, {
11600
11876
  handoff: () => handoff
11601
11877
  });
11602
- async function handoff(chatroomId, options) {
11878
+ async function createDefaultDeps8() {
11603
11879
  const client2 = await getConvexClient();
11880
+ return {
11881
+ backend: {
11882
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
11883
+ query: (endpoint, args) => client2.query(endpoint, args)
11884
+ },
11885
+ session: {
11886
+ getSessionId,
11887
+ getConvexUrl,
11888
+ getOtherSessionUrls
11889
+ }
11890
+ };
11891
+ }
11892
+ async function handoff(chatroomId, options, deps) {
11893
+ const d = deps ?? await createDefaultDeps8();
11604
11894
  const { role, message, nextRole, attachedArtifactIds = [] } = options;
11605
- const sessionId = getSessionId();
11895
+ const sessionId = d.session.getSessionId();
11606
11896
  if (!sessionId) {
11607
- const otherUrls = getOtherSessionUrls();
11608
- const currentUrl = getConvexUrl();
11897
+ const otherUrls = d.session.getOtherSessionUrls();
11898
+ const currentUrl = d.session.getConvexUrl();
11609
11899
  formatAuthError(currentUrl, otherUrls);
11610
11900
  process.exit(1);
11611
11901
  }
@@ -11615,7 +11905,7 @@ async function handoff(chatroomId, options) {
11615
11905
  }
11616
11906
  if (attachedArtifactIds.length > 0) {
11617
11907
  try {
11618
- const areValid = await client2.query(api.artifacts.validateArtifactIds, {
11908
+ const areValid = await d.backend.query(api.artifacts.validateArtifactIds, {
11619
11909
  sessionId,
11620
11910
  artifactIds: attachedArtifactIds
11621
11911
  });
@@ -11633,12 +11923,15 @@ async function handoff(chatroomId, options) {
11633
11923
  }
11634
11924
  let result;
11635
11925
  try {
11636
- result = await client2.mutation(api.messages.sendHandoff, {
11926
+ result = await d.backend.mutation(api.messages.sendHandoff, {
11637
11927
  sessionId,
11638
11928
  chatroomId,
11639
11929
  senderRole: role,
11640
11930
  content: message,
11641
- targetRole: nextRole
11931
+ targetRole: nextRole,
11932
+ ...attachedArtifactIds.length > 0 && {
11933
+ attachedArtifactIds
11934
+ }
11642
11935
  });
11643
11936
  } catch (error) {
11644
11937
  console.error(`
@@ -11652,7 +11945,7 @@ ${errorData.message || "An unexpected error occurred"}`);
11652
11945
  \uD83D\uDD0D Debug Info:`);
11653
11946
  console.error(JSON.stringify(errorData, null, 2));
11654
11947
  }
11655
- const convexUrl2 = getConvexUrl();
11948
+ const convexUrl2 = d.session.getConvexUrl();
11656
11949
  if (errorData.code === "AUTH_FAILED") {
11657
11950
  console.error(`
11658
11951
  \uD83D\uDCA1 Try authenticating again:`);
@@ -11674,9 +11967,10 @@ ${error instanceof Error ? error.message : String(error)}`);
11674
11967
  \uD83D\uDCDA Need help? Check the docs or run:`);
11675
11968
  console.error(` chatroom handoff --help`);
11676
11969
  process.exit(1);
11970
+ return;
11677
11971
  }
11678
11972
  if (!result.success && result.error) {
11679
- const convexUrl2 = getConvexUrl();
11973
+ const convexUrl2 = d.session.getConvexUrl();
11680
11974
  const cliEnvPrefix2 = getCliEnvPrefix(convexUrl2);
11681
11975
  console.error(`
11682
11976
  ❌ ERROR: ${result.error.message}`);
@@ -11702,36 +11996,47 @@ ${error instanceof Error ? error.message : String(error)}`);
11702
11996
  console.log(` • ${id}`);
11703
11997
  });
11704
11998
  }
11705
- if (nextRole.toLowerCase() === "user") {
11706
- console.log(`
11707
- \uD83C\uDF89 Workflow complete! Control returned to user.`);
11708
- }
11709
- const convexUrl = getConvexUrl();
11999
+ const convexUrl = d.session.getConvexUrl();
11710
12000
  const cliEnvPrefix = getCliEnvPrefix(convexUrl);
11711
12001
  console.log(`
11712
- Now run wait-for-task to wait for your next assignment:`);
11713
- console.log(` ${waitForTaskCommand({ chatroomId, role, cliEnvPrefix })}`);
12002
+ Next \`${getNextTaskCommand({ chatroomId, role, cliEnvPrefix })}\``);
11714
12003
  }
11715
12004
  var init_handoff = __esm(() => {
12005
+ init_command();
11716
12006
  init_env();
11717
12007
  init_values();
11718
12008
  init_api3();
11719
12009
  init_storage();
11720
12010
  init_client2();
12011
+ init_error_formatting();
11721
12012
  });
11722
12013
 
11723
- // src/commands/report-progress.ts
12014
+ // src/commands/report-progress/index.ts
11724
12015
  var exports_report_progress = {};
11725
12016
  __export(exports_report_progress, {
11726
12017
  reportProgress: () => reportProgress
11727
12018
  });
11728
- async function reportProgress(chatroomId, options) {
12019
+ async function createDefaultDeps9() {
11729
12020
  const client2 = await getConvexClient();
12021
+ return {
12022
+ backend: {
12023
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
12024
+ query: (endpoint, args) => client2.query(endpoint, args)
12025
+ },
12026
+ session: {
12027
+ getSessionId,
12028
+ getConvexUrl,
12029
+ getOtherSessionUrls
12030
+ }
12031
+ };
12032
+ }
12033
+ async function reportProgress(chatroomId, options, deps) {
12034
+ const d = deps ?? await createDefaultDeps9();
11730
12035
  const { role, message } = options;
11731
- const sessionId = getSessionId();
12036
+ const sessionId = d.session.getSessionId();
11732
12037
  if (!sessionId) {
11733
- const otherUrls = getOtherSessionUrls();
11734
- const currentUrl = getConvexUrl();
12038
+ const otherUrls = d.session.getOtherSessionUrls();
12039
+ const currentUrl = d.session.getConvexUrl();
11735
12040
  formatAuthError(currentUrl, otherUrls);
11736
12041
  process.exit(1);
11737
12042
  }
@@ -11749,7 +12054,7 @@ async function reportProgress(chatroomId, options) {
11749
12054
  process.exit(1);
11750
12055
  }
11751
12056
  try {
11752
- const result = await client2.mutation(api.messages.reportProgress, {
12057
+ const result = await d.backend.mutation(api.messages.reportProgress, {
11753
12058
  sessionId,
11754
12059
  chatroomId,
11755
12060
  senderRole: role,
@@ -11771,7 +12076,7 @@ ${errorData.message || "An unexpected error occurred"}`);
11771
12076
  \uD83D\uDD0D Debug Info:`);
11772
12077
  console.error(JSON.stringify(errorData, null, 2));
11773
12078
  }
11774
- const convexUrl = getConvexUrl();
12079
+ const convexUrl = d.session.getConvexUrl();
11775
12080
  if (errorData.code === "AUTH_FAILED") {
11776
12081
  console.error(`
11777
12082
  \uD83D\uDCA1 Try authenticating again:`);
@@ -11796,6 +12101,7 @@ ${error instanceof Error ? error.message : String(error)}`);
11796
12101
  \uD83D\uDCDA Need help? Check the docs or run:`);
11797
12102
  console.error(` chatroom report-progress --help`);
11798
12103
  process.exit(1);
12104
+ return;
11799
12105
  }
11800
12106
  }
11801
12107
  var init_report_progress = __esm(() => {
@@ -11803,13 +12109,13 @@ var init_report_progress = __esm(() => {
11803
12109
  init_api3();
11804
12110
  init_storage();
11805
12111
  init_client2();
12112
+ init_error_formatting();
11806
12113
  });
11807
12114
 
11808
- // src/commands/backlog.ts
12115
+ // src/commands/backlog/index.ts
11809
12116
  var exports_backlog = {};
11810
12117
  __export(exports_backlog, {
11811
12118
  scoreBacklog: () => scoreBacklog,
11812
- resetBacklog: () => resetBacklog,
11813
12119
  reopenBacklog: () => reopenBacklog,
11814
12120
  patchBacklog: () => patchBacklog,
11815
12121
  markForReviewBacklog: () => markForReviewBacklog,
@@ -11817,24 +12123,47 @@ __export(exports_backlog, {
11817
12123
  completeBacklog: () => completeBacklog,
11818
12124
  addBacklog: () => addBacklog
11819
12125
  });
11820
- async function listBacklog(chatroomId, options) {
12126
+ async function createDefaultDeps10() {
11821
12127
  const client2 = await getConvexClient();
11822
- const sessionId = getSessionId();
12128
+ return {
12129
+ backend: {
12130
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
12131
+ query: (endpoint, args) => client2.query(endpoint, args)
12132
+ },
12133
+ session: {
12134
+ getSessionId,
12135
+ getConvexUrl,
12136
+ getOtherSessionUrls
12137
+ }
12138
+ };
12139
+ }
12140
+ function requireAuth2(d) {
12141
+ const sessionId = d.session.getSessionId();
11823
12142
  if (!sessionId) {
11824
12143
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
11825
12144
  process.exit(1);
11826
12145
  }
12146
+ return sessionId;
12147
+ }
12148
+ function validateChatroomId(chatroomId) {
11827
12149
  if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
11828
12150
  console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
11829
12151
  process.exit(1);
11830
12152
  }
12153
+ }
12154
+ async function listBacklog(chatroomId, options, deps) {
12155
+ const d = deps ?? await createDefaultDeps10();
12156
+ const sessionId = requireAuth2(d);
12157
+ validateChatroomId(chatroomId);
11831
12158
  const validStatuses = [
11832
12159
  "pending",
12160
+ "acknowledged",
11833
12161
  "in_progress",
11834
12162
  "queued",
11835
12163
  "backlog",
12164
+ "backlog_acknowledged",
11836
12165
  "completed",
11837
- "cancelled",
12166
+ "closed",
11838
12167
  "active",
11839
12168
  "pending_review",
11840
12169
  "archived",
@@ -11844,27 +12173,28 @@ async function listBacklog(chatroomId, options) {
11844
12173
  if (!statusFilter || !validStatuses.includes(statusFilter)) {
11845
12174
  console.error(`❌ Invalid or missing status: ${statusFilter || "(none)"}. Must be one of: ${validStatuses.join(", ")}`);
11846
12175
  process.exit(1);
12176
+ return;
11847
12177
  }
11848
12178
  try {
11849
- const counts = await client2.query(api.tasks.getTaskCounts, {
12179
+ const counts = await d.backend.query(api.tasks.getTaskCounts, {
11850
12180
  sessionId,
11851
12181
  chatroomId
11852
12182
  });
11853
12183
  let tasks;
11854
12184
  if (statusFilter === "active") {
11855
- tasks = await client2.query(api.tasks.listActiveTasks, {
12185
+ tasks = await d.backend.query(api.tasks.listActiveTasks, {
11856
12186
  sessionId,
11857
12187
  chatroomId,
11858
12188
  limit: options.limit || 100
11859
12189
  });
11860
12190
  } else if (statusFilter === "archived") {
11861
- tasks = await client2.query(api.tasks.listArchivedTasks, {
12191
+ tasks = await d.backend.query(api.tasks.listArchivedTasks, {
11862
12192
  sessionId,
11863
12193
  chatroomId,
11864
12194
  limit: options.limit || 100
11865
12195
  });
11866
12196
  } else {
11867
- tasks = await client2.query(api.tasks.listTasks, {
12197
+ tasks = await d.backend.query(api.tasks.listTasks, {
11868
12198
  sessionId,
11869
12199
  chatroomId,
11870
12200
  statusFilter: statusFilter === "all" ? undefined : statusFilter,
@@ -11937,25 +12267,20 @@ async function listBacklog(chatroomId, options) {
11937
12267
  } catch (error) {
11938
12268
  console.error(`❌ Failed to list tasks: ${error.message}`);
11939
12269
  process.exit(1);
12270
+ return;
11940
12271
  }
11941
12272
  }
11942
- async function addBacklog(chatroomId, options) {
11943
- const client2 = await getConvexClient();
11944
- const sessionId = getSessionId();
11945
- if (!sessionId) {
11946
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
11947
- process.exit(1);
11948
- }
11949
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
11950
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
11951
- process.exit(1);
11952
- }
12273
+ async function addBacklog(chatroomId, options, deps) {
12274
+ const d = deps ?? await createDefaultDeps10();
12275
+ const sessionId = requireAuth2(d);
12276
+ validateChatroomId(chatroomId);
11953
12277
  if (!options.content || options.content.trim().length === 0) {
11954
12278
  console.error(`❌ Task content cannot be empty`);
11955
12279
  process.exit(1);
12280
+ return;
11956
12281
  }
11957
12282
  try {
11958
- const result = await client2.mutation(api.tasks.createTask, {
12283
+ const result = await d.backend.mutation(api.tasks.createTask, {
11959
12284
  sessionId,
11960
12285
  chatroomId,
11961
12286
  content: options.content.trim(),
@@ -11971,25 +12296,20 @@ async function addBacklog(chatroomId, options) {
11971
12296
  } catch (error) {
11972
12297
  console.error(`❌ Failed to add task: ${error.message}`);
11973
12298
  process.exit(1);
12299
+ return;
11974
12300
  }
11975
12301
  }
11976
- async function completeBacklog(chatroomId, options) {
11977
- const client2 = await getConvexClient();
11978
- const sessionId = getSessionId();
11979
- if (!sessionId) {
11980
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
11981
- process.exit(1);
11982
- }
11983
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
11984
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
11985
- process.exit(1);
11986
- }
12302
+ async function completeBacklog(chatroomId, options, deps) {
12303
+ const d = deps ?? await createDefaultDeps10();
12304
+ const sessionId = requireAuth2(d);
12305
+ validateChatroomId(chatroomId);
11987
12306
  if (!options.taskId || options.taskId.trim().length === 0) {
11988
12307
  console.error(`❌ Task ID is required`);
11989
12308
  process.exit(1);
12309
+ return;
11990
12310
  }
11991
12311
  try {
11992
- const result = await client2.mutation(api.tasks.completeTaskById, {
12312
+ const result = await d.backend.mutation(api.tasks.completeTaskById, {
11993
12313
  sessionId,
11994
12314
  taskId: options.taskId,
11995
12315
  force: options.force
@@ -12010,25 +12330,20 @@ async function completeBacklog(chatroomId, options) {
12010
12330
  } catch (error) {
12011
12331
  console.error(`❌ Failed to complete task: ${error.message}`);
12012
12332
  process.exit(1);
12333
+ return;
12013
12334
  }
12014
12335
  }
12015
- async function reopenBacklog(chatroomId, options) {
12016
- const client2 = await getConvexClient();
12017
- const sessionId = getSessionId();
12018
- if (!sessionId) {
12019
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12020
- process.exit(1);
12021
- }
12022
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12023
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12024
- process.exit(1);
12025
- }
12336
+ async function reopenBacklog(chatroomId, options, deps) {
12337
+ const d = deps ?? await createDefaultDeps10();
12338
+ const sessionId = requireAuth2(d);
12339
+ validateChatroomId(chatroomId);
12026
12340
  if (!options.taskId || options.taskId.trim().length === 0) {
12027
12341
  console.error(`❌ Task ID is required`);
12028
12342
  process.exit(1);
12343
+ return;
12029
12344
  }
12030
12345
  try {
12031
- await client2.mutation(api.tasks.reopenBacklogTask, {
12346
+ await d.backend.mutation(api.tasks.reopenBacklogTask, {
12032
12347
  sessionId,
12033
12348
  taskId: options.taskId
12034
12349
  });
@@ -12042,36 +12357,34 @@ async function reopenBacklog(chatroomId, options) {
12042
12357
  } catch (error) {
12043
12358
  console.error(`❌ Failed to reopen task: ${error.message}`);
12044
12359
  process.exit(1);
12360
+ return;
12045
12361
  }
12046
12362
  }
12047
- async function patchBacklog(chatroomId, options) {
12048
- const client2 = await getConvexClient();
12049
- const sessionId = getSessionId();
12050
- if (!sessionId) {
12051
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12052
- process.exit(1);
12053
- }
12054
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12055
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12056
- process.exit(1);
12057
- }
12363
+ async function patchBacklog(chatroomId, options, deps) {
12364
+ const d = deps ?? await createDefaultDeps10();
12365
+ const sessionId = requireAuth2(d);
12366
+ validateChatroomId(chatroomId);
12058
12367
  if (!options.taskId || options.taskId.trim().length === 0) {
12059
12368
  console.error(`❌ Task ID is required`);
12060
12369
  process.exit(1);
12370
+ return;
12061
12371
  }
12062
12372
  if (options.complexity === undefined && options.value === undefined && options.priority === undefined) {
12063
12373
  console.error(`❌ At least one of --complexity, --value, or --priority is required`);
12064
12374
  process.exit(1);
12375
+ return;
12065
12376
  }
12066
12377
  const validComplexity = ["low", "medium", "high"];
12067
12378
  if (options.complexity !== undefined && !validComplexity.includes(options.complexity)) {
12068
12379
  console.error(`❌ Invalid complexity: ${options.complexity}. Must be one of: ${validComplexity.join(", ")}`);
12069
12380
  process.exit(1);
12381
+ return;
12070
12382
  }
12071
12383
  const validValue = ["low", "medium", "high"];
12072
12384
  if (options.value !== undefined && !validValue.includes(options.value)) {
12073
12385
  console.error(`❌ Invalid value: ${options.value}. Must be one of: ${validValue.join(", ")}`);
12074
12386
  process.exit(1);
12387
+ return;
12075
12388
  }
12076
12389
  let priorityNum;
12077
12390
  if (options.priority !== undefined) {
@@ -12079,10 +12392,11 @@ async function patchBacklog(chatroomId, options) {
12079
12392
  if (isNaN(priorityNum)) {
12080
12393
  console.error(`❌ Invalid priority: ${options.priority}. Must be a number.`);
12081
12394
  process.exit(1);
12395
+ return;
12082
12396
  }
12083
12397
  }
12084
12398
  try {
12085
- await client2.mutation(api.tasks.patchTask, {
12399
+ await d.backend.mutation(api.tasks.patchTask, {
12086
12400
  sessionId,
12087
12401
  taskId: options.taskId,
12088
12402
  complexity: options.complexity,
@@ -12105,37 +12419,35 @@ async function patchBacklog(chatroomId, options) {
12105
12419
  } catch (error) {
12106
12420
  console.error(`❌ Failed to patch task: ${error.message}`);
12107
12421
  process.exit(1);
12422
+ return;
12108
12423
  }
12109
12424
  }
12110
- async function scoreBacklog(chatroomId, options) {
12111
- const client2 = await getConvexClient();
12112
- const sessionId = getSessionId();
12113
- if (!sessionId) {
12114
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12115
- process.exit(1);
12116
- }
12117
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12118
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12119
- process.exit(1);
12120
- }
12425
+ async function scoreBacklog(chatroomId, options, deps) {
12426
+ const d = deps ?? await createDefaultDeps10();
12427
+ const sessionId = requireAuth2(d);
12428
+ validateChatroomId(chatroomId);
12121
12429
  if (!options.taskId || options.taskId.trim().length === 0) {
12122
12430
  console.error(`❌ Task ID is required`);
12123
12431
  process.exit(1);
12432
+ return;
12124
12433
  }
12125
12434
  if (options.complexity === undefined && options.value === undefined && options.priority === undefined) {
12126
12435
  console.error(`❌ At least one of --complexity, --value, or --priority is required`);
12127
12436
  console.error(` Example: chatroom backlog score --task-id=... --complexity=medium --value=high`);
12128
12437
  process.exit(1);
12438
+ return;
12129
12439
  }
12130
12440
  const validComplexity = ["low", "medium", "high"];
12131
12441
  if (options.complexity !== undefined && !validComplexity.includes(options.complexity)) {
12132
12442
  console.error(`❌ Invalid complexity: ${options.complexity}. Must be one of: ${validComplexity.join(", ")}`);
12133
12443
  process.exit(1);
12444
+ return;
12134
12445
  }
12135
12446
  const validValue = ["low", "medium", "high"];
12136
12447
  if (options.value !== undefined && !validValue.includes(options.value)) {
12137
12448
  console.error(`❌ Invalid value: ${options.value}. Must be one of: ${validValue.join(", ")}`);
12138
12449
  process.exit(1);
12450
+ return;
12139
12451
  }
12140
12452
  let priorityNum;
12141
12453
  if (options.priority !== undefined) {
@@ -12143,10 +12455,11 @@ async function scoreBacklog(chatroomId, options) {
12143
12455
  if (isNaN(priorityNum)) {
12144
12456
  console.error(`❌ Invalid priority: ${options.priority}. Must be a number.`);
12145
12457
  process.exit(1);
12458
+ return;
12146
12459
  }
12147
12460
  }
12148
12461
  try {
12149
- await client2.mutation(api.tasks.patchTask, {
12462
+ await d.backend.mutation(api.tasks.patchTask, {
12150
12463
  sessionId,
12151
12464
  taskId: options.taskId,
12152
12465
  complexity: options.complexity,
@@ -12169,25 +12482,20 @@ async function scoreBacklog(chatroomId, options) {
12169
12482
  } catch (error) {
12170
12483
  console.error(`❌ Failed to score task: ${error.message}`);
12171
12484
  process.exit(1);
12485
+ return;
12172
12486
  }
12173
12487
  }
12174
- async function markForReviewBacklog(chatroomId, options) {
12175
- const client2 = await getConvexClient();
12176
- const sessionId = getSessionId();
12177
- if (!sessionId) {
12178
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12179
- process.exit(1);
12180
- }
12181
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12182
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12183
- process.exit(1);
12184
- }
12488
+ async function markForReviewBacklog(chatroomId, options, deps) {
12489
+ const d = deps ?? await createDefaultDeps10();
12490
+ const sessionId = requireAuth2(d);
12491
+ validateChatroomId(chatroomId);
12185
12492
  if (!options.taskId || options.taskId.trim().length === 0) {
12186
12493
  console.error(`❌ Task ID is required`);
12187
12494
  process.exit(1);
12495
+ return;
12188
12496
  }
12189
12497
  try {
12190
- await client2.mutation(api.tasks.markBacklogForReview, {
12498
+ await d.backend.mutation(api.tasks.markBacklogForReview, {
12191
12499
  sessionId,
12192
12500
  taskId: options.taskId
12193
12501
  });
@@ -12201,46 +12509,25 @@ async function markForReviewBacklog(chatroomId, options) {
12201
12509
  } catch (error) {
12202
12510
  console.error(`❌ Failed to mark task for review: ${error.message}`);
12203
12511
  process.exit(1);
12204
- }
12205
- }
12206
- async function resetBacklog(chatroomId, options) {
12207
- const client2 = await getConvexClient();
12208
- const sessionId = getSessionId();
12209
- if (!sessionId) {
12210
- console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12211
- process.exit(1);
12212
- }
12213
- try {
12214
- const result = await client2.mutation(api.tasks.resetStuckTask, {
12215
- sessionId,
12216
- taskId: options.taskId
12217
- });
12218
- console.log("");
12219
- console.log("✅ Task reset to pending");
12220
- console.log(` ID: ${options.taskId}`);
12221
- if (result.previousAssignee) {
12222
- console.log(` Previous assignee: ${result.previousAssignee}`);
12223
- }
12224
- console.log("");
12225
- } catch (error) {
12226
- console.error(`❌ Failed to reset task: ${error.message}`);
12227
- process.exit(1);
12512
+ return;
12228
12513
  }
12229
12514
  }
12230
12515
  function getStatusEmoji(status) {
12231
12516
  switch (status) {
12232
12517
  case "pending":
12233
12518
  return "\uD83D\uDFE2";
12519
+ case "acknowledged":
12520
+ return "\uD83D\uDCEC";
12234
12521
  case "in_progress":
12235
12522
  return "\uD83D\uDD35";
12236
12523
  case "queued":
12237
12524
  return "\uD83D\uDFE1";
12238
12525
  case "backlog":
12239
12526
  return "⚪";
12527
+ case "backlog_acknowledged":
12528
+ return "\uD83D\uDCCB";
12240
12529
  case "completed":
12241
12530
  return "✅";
12242
- case "cancelled":
12243
- return "❌";
12244
12531
  case "pending_user_review":
12245
12532
  return "\uD83D\uDC40";
12246
12533
  case "closed":
@@ -12284,15 +12571,29 @@ function resolveContent(content, filePath, optionName) {
12284
12571
  }
12285
12572
  var init_file_content = () => {};
12286
12573
 
12287
- // src/commands/messages.ts
12574
+ // src/commands/messages/index.ts
12288
12575
  var exports_messages = {};
12289
12576
  __export(exports_messages, {
12290
12577
  listSinceMessage: () => listSinceMessage,
12291
12578
  listBySenderRole: () => listBySenderRole
12292
12579
  });
12293
- async function listBySenderRole(chatroomId, options) {
12580
+ async function createDefaultDeps11() {
12294
12581
  const client2 = await getConvexClient();
12295
- const sessionId = getSessionId();
12582
+ return {
12583
+ backend: {
12584
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
12585
+ query: (endpoint, args) => client2.query(endpoint, args)
12586
+ },
12587
+ session: {
12588
+ getSessionId,
12589
+ getConvexUrl,
12590
+ getOtherSessionUrls
12591
+ }
12592
+ };
12593
+ }
12594
+ async function listBySenderRole(chatroomId, options, deps) {
12595
+ const d = deps ?? await createDefaultDeps11();
12596
+ const sessionId = d.session.getSessionId();
12296
12597
  if (!sessionId) {
12297
12598
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12298
12599
  process.exit(1);
@@ -12302,7 +12603,7 @@ async function listBySenderRole(chatroomId, options) {
12302
12603
  process.exit(1);
12303
12604
  }
12304
12605
  try {
12305
- const messages = await client2.query(api.messages.listBySenderRole, {
12606
+ const messages = await d.backend.query(api.messages.listBySenderRole, {
12306
12607
  sessionId,
12307
12608
  chatroomId,
12308
12609
  senderRole: options.senderRole,
@@ -12352,11 +12653,12 @@ ${content.split(`
12352
12653
  console.error(`
12353
12654
  ❌ Error fetching messages: ${errorMessage}`);
12354
12655
  process.exit(1);
12656
+ return;
12355
12657
  }
12356
12658
  }
12357
- async function listSinceMessage(chatroomId, options) {
12358
- const client2 = await getConvexClient();
12359
- const sessionId = getSessionId();
12659
+ async function listSinceMessage(chatroomId, options, deps) {
12660
+ const d = deps ?? await createDefaultDeps11();
12661
+ const sessionId = d.session.getSessionId();
12360
12662
  if (!sessionId) {
12361
12663
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12362
12664
  process.exit(1);
@@ -12366,7 +12668,7 @@ async function listSinceMessage(chatroomId, options) {
12366
12668
  process.exit(1);
12367
12669
  }
12368
12670
  try {
12369
- const messages = await client2.query(api.messages.listSinceMessage, {
12671
+ const messages = await d.backend.query(api.messages.listSinceMessage, {
12370
12672
  sessionId,
12371
12673
  chatroomId,
12372
12674
  sinceMessageId: options.sinceMessageId,
@@ -12414,6 +12716,7 @@ ${content.split(`
12414
12716
  console.error(`
12415
12717
  ❌ Error fetching messages: ${errorMessage}`);
12416
12718
  process.exit(1);
12719
+ return;
12417
12720
  }
12418
12721
  }
12419
12722
  var init_messages = __esm(() => {
@@ -12422,7 +12725,7 @@ var init_messages = __esm(() => {
12422
12725
  init_client2();
12423
12726
  });
12424
12727
 
12425
- // src/commands/context.ts
12728
+ // src/commands/context/index.ts
12426
12729
  var exports_context = {};
12427
12730
  __export(exports_context, {
12428
12731
  readContext: () => readContext,
@@ -12430,9 +12733,23 @@ __export(exports_context, {
12430
12733
  listContexts: () => listContexts,
12431
12734
  inspectContext: () => inspectContext
12432
12735
  });
12433
- async function readContext(chatroomId, options) {
12736
+ async function createDefaultDeps12() {
12434
12737
  const client2 = await getConvexClient();
12435
- const sessionId = getSessionId();
12738
+ return {
12739
+ backend: {
12740
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
12741
+ query: (endpoint, args) => client2.query(endpoint, args)
12742
+ },
12743
+ session: {
12744
+ getSessionId,
12745
+ getConvexUrl,
12746
+ getOtherSessionUrls
12747
+ }
12748
+ };
12749
+ }
12750
+ async function readContext(chatroomId, options, deps) {
12751
+ const d = deps ?? await createDefaultDeps12();
12752
+ const sessionId = d.session.getSessionId();
12436
12753
  if (!sessionId) {
12437
12754
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12438
12755
  process.exit(1);
@@ -12442,12 +12759,12 @@ async function readContext(chatroomId, options) {
12442
12759
  process.exit(1);
12443
12760
  }
12444
12761
  try {
12445
- const context = await client2.query(api.messages.getContextForRole, {
12762
+ const context = await d.backend.query(api.messages.getContextForRole, {
12446
12763
  sessionId,
12447
12764
  chatroomId,
12448
12765
  role: options.role
12449
12766
  });
12450
- if (context.messages.length === 0) {
12767
+ if (context.messages.length === 0 && !context.currentContext) {
12451
12768
  console.log(`
12452
12769
  \uD83D\uDCED No context available`);
12453
12770
  return;
@@ -12455,6 +12772,18 @@ async function readContext(chatroomId, options) {
12455
12772
  console.log(`
12456
12773
  \uD83D\uDCDA CONTEXT FOR ${options.role.toUpperCase()}`);
12457
12774
  console.log("═".repeat(60));
12775
+ if (context.currentContext) {
12776
+ console.log(`
12777
+ \uD83D\uDCCC Current Context:`);
12778
+ console.log(` Created by: ${context.currentContext.createdBy}`);
12779
+ console.log(` Created at: ${new Date(context.currentContext.createdAt).toLocaleString()}`);
12780
+ console.log(` Content:`);
12781
+ const safeContextContent = sanitizeForTerminal(context.currentContext.content);
12782
+ console.log(safeContextContent.split(`
12783
+ `).map((l) => ` ${l}`).join(`
12784
+ `));
12785
+ console.log("─".repeat(60));
12786
+ }
12458
12787
  if (context.originMessage) {
12459
12788
  console.log(`
12460
12789
  \uD83C\uDFAF Origin Message:`);
@@ -12481,9 +12810,12 @@ async function readContext(chatroomId, options) {
12481
12810
  \uD83D\uDD39 Message ID: ${message._id}`);
12482
12811
  console.log(` Time: ${timestamp}`);
12483
12812
  console.log(` From: ${message.senderRole}`);
12813
+ if (message.targetRole) {
12814
+ console.log(` To: ${message.targetRole}`);
12815
+ }
12484
12816
  console.log(` Type: ${message.type}${classificationBadge}`);
12485
12817
  if (message.featureTitle) {
12486
- console.log(` Feature: ${message.featureTitle}`);
12818
+ console.log(` Feature: ${sanitizeForTerminal(message.featureTitle)}`);
12487
12819
  }
12488
12820
  if (message.taskId) {
12489
12821
  console.log(` Task:`);
@@ -12492,7 +12824,8 @@ async function readContext(chatroomId, options) {
12492
12824
  console.log(` Status: ${message.taskStatus}`);
12493
12825
  }
12494
12826
  if (message.taskContent) {
12495
- console.log(` Content: ${message.taskContent.split(`
12827
+ const safeTaskContent = sanitizeForTerminal(message.taskContent);
12828
+ console.log(` Content: ${safeTaskContent.split(`
12496
12829
  `).map((l, i2) => i2 === 0 ? l : ` ${l}`).join(`
12497
12830
  `)}`);
12498
12831
  }
@@ -12502,7 +12835,7 @@ async function readContext(chatroomId, options) {
12502
12835
  for (const task of message.attachedTasks) {
12503
12836
  console.log(` \uD83D\uDD39 Task ID: ${task._id}`);
12504
12837
  console.log(` Type: Task`);
12505
- const contentLines = task.content.split(`
12838
+ const contentLines = sanitizeForTerminal(task.content).split(`
12506
12839
  `);
12507
12840
  console.log(` Content: ${contentLines[0]}`);
12508
12841
  if (contentLines.length > 1) {
@@ -12513,20 +12846,22 @@ async function readContext(chatroomId, options) {
12513
12846
  }
12514
12847
  }
12515
12848
  console.log(` Content:`);
12516
- console.log(message.content.split(`
12849
+ const safeMessageContent = sanitizeForTerminal(message.content);
12850
+ console.log(safeMessageContent.split(`
12517
12851
  `).map((l) => ` ${l}`).join(`
12518
12852
  `));
12519
12853
  }
12520
12854
  console.log(`
12521
12855
  ` + "═".repeat(60));
12522
12856
  } catch (err) {
12523
- console.error(`❌ Failed to read context: ${err.message}`);
12857
+ console.error(`❌ Failed to read context: ${sanitizeUnknownForTerminal(err.message)}`);
12524
12858
  process.exit(1);
12859
+ return;
12525
12860
  }
12526
12861
  }
12527
- async function newContext(chatroomId, options) {
12528
- const client2 = await getConvexClient();
12529
- const sessionId = getSessionId();
12862
+ async function newContext(chatroomId, options, deps) {
12863
+ const d = deps ?? await createDefaultDeps12();
12864
+ const sessionId = d.session.getSessionId();
12530
12865
  if (!sessionId) {
12531
12866
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12532
12867
  process.exit(1);
@@ -12540,11 +12875,12 @@ async function newContext(chatroomId, options) {
12540
12875
  process.exit(1);
12541
12876
  }
12542
12877
  try {
12543
- const contextId = await client2.mutation(api.contexts.createContext, {
12878
+ const contextId = await d.backend.mutation(api.contexts.createContext, {
12544
12879
  sessionId,
12545
12880
  chatroomId,
12546
12881
  content: options.content,
12547
- role: options.role
12882
+ role: options.role,
12883
+ triggerMessageId: options.triggerMessageId
12548
12884
  });
12549
12885
  console.log(`✅ Context created successfully`);
12550
12886
  console.log(` Context ID: ${contextId}`);
@@ -12554,11 +12890,12 @@ async function newContext(chatroomId, options) {
12554
12890
  } catch (err) {
12555
12891
  console.error(`❌ Failed to create context: ${err.message}`);
12556
12892
  process.exit(1);
12893
+ return;
12557
12894
  }
12558
12895
  }
12559
- async function listContexts(chatroomId, options) {
12560
- const client2 = await getConvexClient();
12561
- const sessionId = getSessionId();
12896
+ async function listContexts(chatroomId, options, deps) {
12897
+ const d = deps ?? await createDefaultDeps12();
12898
+ const sessionId = d.session.getSessionId();
12562
12899
  if (!sessionId) {
12563
12900
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12564
12901
  process.exit(1);
@@ -12568,7 +12905,7 @@ async function listContexts(chatroomId, options) {
12568
12905
  process.exit(1);
12569
12906
  }
12570
12907
  try {
12571
- const contexts = await client2.query(api.contexts.listContexts, {
12908
+ const contexts = await d.backend.query(api.contexts.listContexts, {
12572
12909
  sessionId,
12573
12910
  chatroomId,
12574
12911
  limit: options.limit ?? 10
@@ -12594,7 +12931,8 @@ async function listContexts(chatroomId, options) {
12594
12931
  console.log(` Messages at creation: ${context.messageCountAtCreation}`);
12595
12932
  }
12596
12933
  console.log(` Content:`);
12597
- const truncatedContent = context.content.length > 200 ? context.content.slice(0, 200) + "..." : context.content;
12934
+ const safeContent = sanitizeForTerminal(context.content);
12935
+ const truncatedContent = safeContent.length > 200 ? safeContent.slice(0, 200) + "..." : safeContent;
12598
12936
  console.log(truncatedContent.split(`
12599
12937
  `).map((l) => ` ${l}`).join(`
12600
12938
  `));
@@ -12602,19 +12940,20 @@ async function listContexts(chatroomId, options) {
12602
12940
  console.log(`
12603
12941
  ` + "═".repeat(60));
12604
12942
  } catch (err) {
12605
- console.error(`❌ Failed to list contexts: ${err.message}`);
12943
+ console.error(`❌ Failed to list contexts: ${sanitizeUnknownForTerminal(err.message)}`);
12606
12944
  process.exit(1);
12945
+ return;
12607
12946
  }
12608
12947
  }
12609
- async function inspectContext(chatroomId, options) {
12610
- const client2 = await getConvexClient();
12611
- const sessionId = getSessionId();
12948
+ async function inspectContext(chatroomId, options, deps) {
12949
+ const d = deps ?? await createDefaultDeps12();
12950
+ const sessionId = d.session.getSessionId();
12612
12951
  if (!sessionId) {
12613
12952
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12614
12953
  process.exit(1);
12615
12954
  }
12616
12955
  try {
12617
- const context = await client2.query(api.contexts.getContext, {
12956
+ const context = await d.backend.query(api.contexts.getContext, {
12618
12957
  sessionId,
12619
12958
  contextId: options.contextId
12620
12959
  });
@@ -12642,7 +12981,7 @@ async function inspectContext(chatroomId, options) {
12642
12981
  console.log(`
12643
12982
  \uD83D\uDCDD Content:`);
12644
12983
  console.log("─".repeat(60));
12645
- console.log(context.content);
12984
+ console.log(sanitizeForTerminal(context.content));
12646
12985
  console.log("─".repeat(60));
12647
12986
  console.log(`
12648
12987
  \uD83D\uDCA1 To create a new context:`);
@@ -12650,8 +12989,9 @@ async function inspectContext(chatroomId, options) {
12650
12989
  console.log(`
12651
12990
  ` + "═".repeat(60));
12652
12991
  } catch (err) {
12653
- console.error(`❌ Failed to inspect context: ${err.message}`);
12992
+ console.error(`❌ Failed to inspect context: ${sanitizeUnknownForTerminal(err.message)}`);
12654
12993
  process.exit(1);
12994
+ return;
12655
12995
  }
12656
12996
  }
12657
12997
  var init_context = __esm(() => {
@@ -12660,27 +13000,43 @@ var init_context = __esm(() => {
12660
13000
  init_client2();
12661
13001
  });
12662
13002
 
12663
- // src/commands/guidelines.ts
13003
+ // src/commands/guidelines/index.ts
12664
13004
  var exports_guidelines = {};
12665
13005
  __export(exports_guidelines, {
12666
13006
  viewGuidelines: () => viewGuidelines,
12667
13007
  listGuidelineTypes: () => listGuidelineTypes
12668
13008
  });
12669
- async function viewGuidelines(options) {
13009
+ async function createDefaultDeps13() {
13010
+ const client2 = await getConvexClient();
13011
+ return {
13012
+ backend: {
13013
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
13014
+ query: (endpoint, args) => client2.query(endpoint, args)
13015
+ },
13016
+ session: {
13017
+ getSessionId,
13018
+ getConvexUrl,
13019
+ getOtherSessionUrls
13020
+ }
13021
+ };
13022
+ }
13023
+ async function viewGuidelines(options, deps) {
13024
+ const d = deps ?? await createDefaultDeps13();
12670
13025
  const { type } = options;
12671
13026
  if (!VALID_TYPES.includes(type)) {
12672
13027
  console.error(`❌ Invalid guideline type: "${type}"`);
12673
13028
  console.error(` Valid types: ${VALID_TYPES.join(", ")}`);
12674
13029
  process.exit(1);
13030
+ return;
12675
13031
  }
12676
- const sessionId = getSessionId();
13032
+ const sessionId = d.session.getSessionId();
12677
13033
  if (!sessionId) {
12678
13034
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12679
13035
  process.exit(1);
13036
+ return;
12680
13037
  }
12681
- const client2 = await getConvexClient();
12682
13038
  try {
12683
- const result = await client2.query(api.guidelines.getGuidelines, {
13039
+ const result = await d.backend.query(api.guidelines.getGuidelines, {
12684
13040
  type
12685
13041
  });
12686
13042
  console.log(`
@@ -12696,17 +13052,19 @@ ${"═".repeat(60)}
12696
13052
  const err = error;
12697
13053
  console.error(`❌ Error fetching guidelines: ${err.message}`);
12698
13054
  process.exit(1);
13055
+ return;
12699
13056
  }
12700
13057
  }
12701
- async function listGuidelineTypes() {
12702
- const sessionId = getSessionId();
13058
+ async function listGuidelineTypes(deps) {
13059
+ const d = deps ?? await createDefaultDeps13();
13060
+ const sessionId = d.session.getSessionId();
12703
13061
  if (!sessionId) {
12704
13062
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
12705
13063
  process.exit(1);
13064
+ return;
12706
13065
  }
12707
- const client2 = await getConvexClient();
12708
13066
  try {
12709
- const types2 = await client2.query(api.guidelines.listGuidelineTypes, {});
13067
+ const types2 = await d.backend.query(api.guidelines.listGuidelineTypes, {});
12710
13068
  console.log(`
12711
13069
  \uD83D\uDCCB Available Guideline Types
12712
13070
  `);
@@ -12722,6 +13080,7 @@ Usage: chatroom guidelines view --type=<type>
12722
13080
  const err = error;
12723
13081
  console.error(`❌ Error fetching guideline types: ${err.message}`);
12724
13082
  process.exit(1);
13083
+ return;
12725
13084
  }
12726
13085
  }
12727
13086
  var VALID_TYPES;
@@ -12732,26 +13091,44 @@ var init_guidelines = __esm(() => {
12732
13091
  VALID_TYPES = ["coding", "security", "design", "performance", "all"];
12733
13092
  });
12734
13093
 
12735
- // src/commands/artifact.ts
13094
+ // src/commands/artifact/index.ts
12736
13095
  var exports_artifact = {};
12737
13096
  __export(exports_artifact, {
12738
13097
  viewManyArtifacts: () => viewManyArtifacts,
12739
13098
  viewArtifact: () => viewArtifact,
12740
13099
  createArtifact: () => createArtifact
12741
13100
  });
12742
- async function createArtifact(chatroomId, options) {
12743
- const sessionId = getSessionId();
12744
- if (!sessionId) {
12745
- formatAuthError();
12746
- process.exit(1);
12747
- }
13101
+ async function createDefaultDeps14() {
13102
+ const client2 = await getConvexClient();
13103
+ return {
13104
+ backend: {
13105
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
13106
+ query: (endpoint, args) => client2.query(endpoint, args)
13107
+ },
13108
+ session: {
13109
+ getSessionId,
13110
+ getConvexUrl,
13111
+ getOtherSessionUrls
13112
+ }
13113
+ };
13114
+ }
13115
+ async function createArtifact(chatroomId, options, deps) {
13116
+ const d = deps ?? await createDefaultDeps14();
13117
+ const sessionId = d.session.getSessionId();
13118
+ if (!sessionId) {
13119
+ formatAuthError(d.session.getConvexUrl(), d.session.getOtherSessionUrls());
13120
+ process.exit(1);
13121
+ return;
13122
+ }
12748
13123
  if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12749
13124
  formatChatroomIdError(chatroomId);
12750
13125
  process.exit(1);
13126
+ return;
12751
13127
  }
12752
13128
  if (!options.fromFile.endsWith(".md")) {
12753
13129
  formatValidationError("file extension", options.fromFile, "*.md");
12754
13130
  process.exit(1);
13131
+ return;
12755
13132
  }
12756
13133
  let content;
12757
13134
  try {
@@ -12759,14 +13136,15 @@ async function createArtifact(chatroomId, options) {
12759
13136
  } catch (err) {
12760
13137
  formatFileError("read for --from-file", options.fromFile, err.message);
12761
13138
  process.exit(1);
13139
+ return;
12762
13140
  }
12763
13141
  if (!content || content.trim().length === 0) {
12764
13142
  formatError("File is empty");
12765
13143
  process.exit(1);
13144
+ return;
12766
13145
  }
12767
- const client2 = await getConvexClient();
12768
13146
  try {
12769
- const artifactId = await client2.mutation(api.artifacts.create, {
13147
+ const artifactId = await d.backend.mutation(api.artifacts.create, {
12770
13148
  sessionId,
12771
13149
  chatroomId,
12772
13150
  filename: options.filename,
@@ -12785,21 +13163,24 @@ async function createArtifact(chatroomId, options) {
12785
13163
  } catch (error) {
12786
13164
  formatError("Failed to create artifact", [String(error)]);
12787
13165
  process.exit(1);
13166
+ return;
12788
13167
  }
12789
13168
  }
12790
- async function viewArtifact(chatroomId, options) {
12791
- const sessionId = getSessionId();
13169
+ async function viewArtifact(chatroomId, options, deps) {
13170
+ const d = deps ?? await createDefaultDeps14();
13171
+ const sessionId = d.session.getSessionId();
12792
13172
  if (!sessionId) {
12793
- formatAuthError();
13173
+ formatAuthError(d.session.getConvexUrl(), d.session.getOtherSessionUrls());
12794
13174
  process.exit(1);
13175
+ return;
12795
13176
  }
12796
13177
  if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12797
13178
  formatChatroomIdError(chatroomId);
12798
13179
  process.exit(1);
13180
+ return;
12799
13181
  }
12800
- const client2 = await getConvexClient();
12801
13182
  try {
12802
- const artifact = await client2.query(api.artifacts.get, {
13183
+ const artifact = await d.backend.query(api.artifacts.get, {
12803
13184
  sessionId,
12804
13185
  artifactId: options.artifactId
12805
13186
  });
@@ -12810,6 +13191,7 @@ async function viewArtifact(chatroomId, options) {
12810
13191
  `chatroom artifact create ${chatroomId} --from-file=... --filename=...`
12811
13192
  ]);
12812
13193
  process.exit(1);
13194
+ return;
12813
13195
  }
12814
13196
  console.log(`\uD83D\uDCC4 Artifact: ${artifact.filename}`);
12815
13197
  console.log(`\uD83C\uDD94 ID: ${artifact._id}`);
@@ -12825,33 +13207,38 @@ async function viewArtifact(chatroomId, options) {
12825
13207
  } catch (error) {
12826
13208
  formatError("Failed to view artifact", [String(error)]);
12827
13209
  process.exit(1);
13210
+ return;
12828
13211
  }
12829
13212
  }
12830
- async function viewManyArtifacts(chatroomId, options) {
12831
- const sessionId = getSessionId();
13213
+ async function viewManyArtifacts(chatroomId, options, deps) {
13214
+ const d = deps ?? await createDefaultDeps14();
13215
+ const sessionId = d.session.getSessionId();
12832
13216
  if (!sessionId) {
12833
- formatAuthError();
13217
+ formatAuthError(d.session.getConvexUrl(), d.session.getOtherSessionUrls());
12834
13218
  process.exit(1);
13219
+ return;
12835
13220
  }
12836
13221
  if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12837
13222
  formatChatroomIdError(chatroomId);
12838
13223
  process.exit(1);
13224
+ return;
12839
13225
  }
12840
13226
  if (options.artifactIds.length === 0) {
12841
13227
  formatError("No artifact IDs provided", [
12842
13228
  "Usage: chatroom artifact view-many <chatroomId> --artifact=id1 --artifact=id2"
12843
13229
  ]);
12844
13230
  process.exit(1);
13231
+ return;
12845
13232
  }
12846
- const client2 = await getConvexClient();
12847
13233
  try {
12848
- const artifacts = await client2.query(api.artifacts.getMany, {
13234
+ const artifacts = await d.backend.query(api.artifacts.getMany, {
12849
13235
  sessionId,
12850
13236
  artifactIds: options.artifactIds
12851
13237
  });
12852
13238
  if (artifacts.length === 0) {
12853
13239
  formatError("No artifacts found");
12854
13240
  process.exit(1);
13241
+ return;
12855
13242
  }
12856
13243
  artifacts.forEach((artifact, index) => {
12857
13244
  if (index > 0) {
@@ -12874,20 +13261,248 @@ async function viewManyArtifacts(chatroomId, options) {
12874
13261
  } catch (error) {
12875
13262
  formatError("Failed to view artifacts", [String(error)]);
12876
13263
  process.exit(1);
13264
+ return;
12877
13265
  }
12878
13266
  }
12879
13267
  var init_artifact = __esm(() => {
12880
13268
  init_api3();
12881
13269
  init_storage();
12882
13270
  init_client2();
13271
+ init_error_formatting();
12883
13272
  init_file_content();
12884
13273
  });
12885
13274
 
13275
+ // src/commands/get-system-prompt/index.ts
13276
+ var exports_get_system_prompt = {};
13277
+ __export(exports_get_system_prompt, {
13278
+ getSystemPrompt: () => getSystemPrompt
13279
+ });
13280
+ async function createDefaultDeps15() {
13281
+ const client2 = await getConvexClient();
13282
+ return {
13283
+ backend: {
13284
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
13285
+ query: (endpoint, args) => client2.query(endpoint, args)
13286
+ },
13287
+ session: {
13288
+ getSessionId,
13289
+ getConvexUrl,
13290
+ getOtherSessionUrls
13291
+ }
13292
+ };
13293
+ }
13294
+ async function getSystemPrompt(chatroomId, options, deps) {
13295
+ const d = deps ?? await createDefaultDeps15();
13296
+ const { role } = options;
13297
+ const sessionId = d.session.getSessionId();
13298
+ if (!sessionId) {
13299
+ console.error(`❌ Not authenticated. Please run: chatroom auth login`);
13300
+ process.exit(1);
13301
+ return;
13302
+ }
13303
+ const convexUrl = d.session.getConvexUrl();
13304
+ try {
13305
+ const chatroom = await d.backend.query(api.chatrooms.get, {
13306
+ sessionId,
13307
+ chatroomId
13308
+ });
13309
+ if (!chatroom) {
13310
+ console.error(`❌ Chatroom not found: ${chatroomId}`);
13311
+ process.exit(1);
13312
+ return;
13313
+ }
13314
+ const prompt = await d.backend.query(api.prompts.webapp.getAgentPrompt, {
13315
+ chatroomId,
13316
+ role,
13317
+ teamName: chatroom.teamName,
13318
+ teamRoles: chatroom.teamRoles,
13319
+ teamEntryPoint: chatroom.teamEntryPoint,
13320
+ convexUrl: convexUrl ?? undefined
13321
+ });
13322
+ console.log(prompt);
13323
+ } catch (error) {
13324
+ const err = error;
13325
+ console.error(`❌ Error fetching system prompt: ${err.message}`);
13326
+ process.exit(1);
13327
+ return;
13328
+ }
13329
+ }
13330
+ var init_get_system_prompt = __esm(() => {
13331
+ init_api3();
13332
+ init_storage();
13333
+ init_client2();
13334
+ });
13335
+
13336
+ // ../../services/backend/config/reliability.ts
13337
+ var DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
13338
+
13339
+ // src/commands/machine/daemon-start/utils.ts
13340
+ function formatTimestamp() {
13341
+ return new Date().toISOString().replace("T", " ").substring(0, 19);
13342
+ }
13343
+ function parseMachineCommand(raw) {
13344
+ switch (raw.type) {
13345
+ case "ping":
13346
+ return { _id: raw._id, type: "ping", payload: {}, createdAt: raw.createdAt };
13347
+ case "status":
13348
+ return { _id: raw._id, type: "status", payload: {}, createdAt: raw.createdAt };
13349
+ case "start-agent": {
13350
+ const { chatroomId, role, agentHarness } = raw.payload;
13351
+ if (!chatroomId || !role || !agentHarness) {
13352
+ console.error(` ⚠️ Invalid start-agent command: missing chatroomId, role, or agentHarness`);
13353
+ return null;
13354
+ }
13355
+ return {
13356
+ _id: raw._id,
13357
+ type: "start-agent",
13358
+ payload: {
13359
+ chatroomId,
13360
+ role,
13361
+ agentHarness,
13362
+ model: raw.payload.model,
13363
+ workingDir: raw.payload.workingDir
13364
+ },
13365
+ createdAt: raw.createdAt
13366
+ };
13367
+ }
13368
+ case "stop-agent": {
13369
+ const { chatroomId, role } = raw.payload;
13370
+ if (!chatroomId || !role) {
13371
+ console.error(` ⚠️ Invalid stop-agent command: missing chatroomId or role`);
13372
+ return null;
13373
+ }
13374
+ return {
13375
+ _id: raw._id,
13376
+ type: "stop-agent",
13377
+ payload: { chatroomId, role },
13378
+ createdAt: raw.createdAt
13379
+ };
13380
+ }
13381
+ default:
13382
+ return null;
13383
+ }
13384
+ }
13385
+
13386
+ // src/commands/machine/events/on-agent-shutdown/index.ts
13387
+ async function onAgentShutdown(ctx, options) {
13388
+ const { chatroomId, role, pid, skipKill } = options;
13389
+ let killed = false;
13390
+ if (!skipKill) {
13391
+ try {
13392
+ ctx.deps.processes.kill(-pid, "SIGTERM");
13393
+ } catch {
13394
+ killed = true;
13395
+ }
13396
+ if (!killed) {
13397
+ const SIGTERM_TIMEOUT_MS = 1e4;
13398
+ const POLL_INTERVAL_MS2 = 500;
13399
+ const deadline = Date.now() + SIGTERM_TIMEOUT_MS;
13400
+ while (Date.now() < deadline) {
13401
+ await ctx.deps.clock.delay(POLL_INTERVAL_MS2);
13402
+ try {
13403
+ ctx.deps.processes.kill(pid, 0);
13404
+ } catch {
13405
+ killed = true;
13406
+ break;
13407
+ }
13408
+ }
13409
+ }
13410
+ if (!killed) {
13411
+ try {
13412
+ ctx.deps.processes.kill(-pid, "SIGKILL");
13413
+ } catch {
13414
+ killed = true;
13415
+ }
13416
+ }
13417
+ if (!killed) {
13418
+ await ctx.deps.clock.delay(5000);
13419
+ try {
13420
+ ctx.deps.processes.kill(pid, 0);
13421
+ console.log(` ⚠️ Process ${pid} (${role}) still alive after SIGKILL — possible zombie`);
13422
+ } catch {
13423
+ killed = true;
13424
+ }
13425
+ }
13426
+ }
13427
+ ctx.deps.stops.mark(chatroomId, role);
13428
+ ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
13429
+ let spawnedAgentCleared = false;
13430
+ try {
13431
+ await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
13432
+ sessionId: ctx.sessionId,
13433
+ machineId: ctx.machineId,
13434
+ chatroomId,
13435
+ role,
13436
+ pid: undefined
13437
+ });
13438
+ spawnedAgentCleared = true;
13439
+ } catch (e) {
13440
+ console.log(` ⚠️ Failed to clear spawnedAgent for ${role}: ${e.message}`);
13441
+ }
13442
+ let participantRemoved = false;
13443
+ try {
13444
+ await ctx.deps.backend.mutation(api.participants.leave, {
13445
+ sessionId: ctx.sessionId,
13446
+ chatroomId,
13447
+ role
13448
+ });
13449
+ participantRemoved = true;
13450
+ } catch (e) {
13451
+ console.log(` ⚠️ Failed to remove participant for ${role}: ${e.message}`);
13452
+ }
13453
+ return { killed, cleaned: spawnedAgentCleared && participantRemoved };
13454
+ }
13455
+ var init_on_agent_shutdown = __esm(() => {
13456
+ init_api3();
13457
+ });
13458
+
13459
+ // src/commands/machine/events/on-daemon-shutdown/index.ts
13460
+ async function onDaemonShutdown(ctx) {
13461
+ const agents = ctx.deps.machine.listAgentEntries(ctx.machineId);
13462
+ if (agents.length > 0) {
13463
+ console.log(`[${formatTimestamp()}] Stopping ${agents.length} agent(s)...`);
13464
+ await Promise.allSettled(agents.map(async ({ chatroomId, role, entry }) => {
13465
+ const result = await onAgentShutdown(ctx, {
13466
+ chatroomId,
13467
+ role,
13468
+ pid: entry.pid
13469
+ });
13470
+ if (result.killed) {
13471
+ console.log(` Sent SIGTERM to ${role} (PID ${entry.pid})`);
13472
+ } else {
13473
+ console.log(` ${role} (PID ${entry.pid}) already exited`);
13474
+ }
13475
+ return result;
13476
+ }));
13477
+ await ctx.deps.clock.delay(AGENT_SHUTDOWN_TIMEOUT_MS);
13478
+ for (const { role, entry } of agents) {
13479
+ try {
13480
+ ctx.deps.processes.kill(entry.pid, 0);
13481
+ ctx.deps.processes.kill(entry.pid, "SIGKILL");
13482
+ console.log(` Force-killed ${role} (PID ${entry.pid})`);
13483
+ } catch {}
13484
+ }
13485
+ console.log(`[${formatTimestamp()}] All agents stopped`);
13486
+ }
13487
+ try {
13488
+ await ctx.deps.backend.mutation(api.machines.updateDaemonStatus, {
13489
+ sessionId: ctx.sessionId,
13490
+ machineId: ctx.machineId,
13491
+ connected: false
13492
+ });
13493
+ } catch {}
13494
+ }
13495
+ var AGENT_SHUTDOWN_TIMEOUT_MS = 5000;
13496
+ var init_on_daemon_shutdown = __esm(() => {
13497
+ init_api3();
13498
+ init_on_agent_shutdown();
13499
+ });
13500
+
12886
13501
  // src/commands/machine/pid.ts
12887
13502
  import { createHash } from "node:crypto";
12888
- import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, mkdirSync as mkdirSync4 } from "node:fs";
13503
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
12889
13504
  import { homedir as homedir4 } from "node:os";
12890
- import { join as join6 } from "node:path";
13505
+ import { join as join5 } from "node:path";
12891
13506
  function getUrlHash() {
12892
13507
  const url = getConvexUrl();
12893
13508
  return createHash("sha256").update(url).digest("hex").substring(0, 8);
@@ -12901,7 +13516,7 @@ function ensureChatroomDir() {
12901
13516
  }
12902
13517
  }
12903
13518
  function getPidFilePath() {
12904
- return join6(CHATROOM_DIR4, getPidFileName());
13519
+ return join5(CHATROOM_DIR4, getPidFileName());
12905
13520
  }
12906
13521
  function isProcessRunning(pid) {
12907
13522
  try {
@@ -12930,13 +13545,13 @@ function readPid() {
12930
13545
  function writePid() {
12931
13546
  ensureChatroomDir();
12932
13547
  const pidPath = getPidFilePath();
12933
- writeFileSync5(pidPath, process.pid.toString(), "utf-8");
13548
+ writeFileSync4(pidPath, process.pid.toString(), "utf-8");
12934
13549
  }
12935
13550
  function removePid() {
12936
13551
  const pidPath = getPidFilePath();
12937
13552
  try {
12938
13553
  if (existsSync4(pidPath)) {
12939
- unlinkSync3(pidPath);
13554
+ unlinkSync2(pidPath);
12940
13555
  }
12941
13556
  } catch {}
12942
13557
  }
@@ -12966,171 +13581,16 @@ function releaseLock() {
12966
13581
  var CHATROOM_DIR4;
12967
13582
  var init_pid = __esm(() => {
12968
13583
  init_client2();
12969
- CHATROOM_DIR4 = join6(homedir4(), ".chatroom");
13584
+ CHATROOM_DIR4 = join5(homedir4(), ".chatroom");
12970
13585
  });
12971
13586
 
12972
- // src/commands/machine/daemon-start.ts
12973
- import { execSync as execSync3 } from "node:child_process";
12974
- import { stat } from "node:fs/promises";
12975
- function parseMachineCommand(raw) {
12976
- switch (raw.type) {
12977
- case "ping":
12978
- return { _id: raw._id, type: "ping", payload: {}, createdAt: raw.createdAt };
12979
- case "status":
12980
- return { _id: raw._id, type: "status", payload: {}, createdAt: raw.createdAt };
12981
- case "start-agent": {
12982
- const { chatroomId, role, agentHarness } = raw.payload;
12983
- if (!chatroomId || !role || !agentHarness) {
12984
- console.error(` ⚠️ Invalid start-agent command: missing chatroomId, role, or agentHarness`);
12985
- return null;
12986
- }
12987
- return {
12988
- _id: raw._id,
12989
- type: "start-agent",
12990
- payload: {
12991
- chatroomId,
12992
- role,
12993
- agentHarness,
12994
- model: raw.payload.model,
12995
- workingDir: raw.payload.workingDir
12996
- },
12997
- createdAt: raw.createdAt
12998
- };
12999
- }
13000
- case "stop-agent": {
13001
- const { chatroomId, role } = raw.payload;
13002
- if (!chatroomId || !role) {
13003
- console.error(` ⚠️ Invalid stop-agent command: missing chatroomId or role`);
13004
- return null;
13005
- }
13006
- return {
13007
- _id: raw._id,
13008
- type: "stop-agent",
13009
- payload: { chatroomId, role },
13010
- createdAt: raw.createdAt
13011
- };
13012
- }
13013
- default:
13014
- return null;
13015
- }
13016
- }
13017
- function formatTimestamp() {
13018
- return new Date().toISOString().replace("T", " ").substring(0, 19);
13019
- }
13020
- function verifyPidOwnership(pid, expectedHarness) {
13021
- try {
13022
- process.kill(pid, 0);
13023
- } catch {
13024
- return false;
13025
- }
13026
- if (!expectedHarness) {
13027
- return true;
13028
- }
13029
- try {
13030
- const platform = process.platform;
13031
- let processName = "";
13032
- if (platform === "darwin" || platform === "linux") {
13033
- processName = execSync3(`ps -p ${pid} -o comm= 2>/dev/null`, {
13034
- encoding: "utf-8",
13035
- timeout: 3000
13036
- }).trim();
13037
- }
13038
- if (!processName) {
13039
- return true;
13040
- }
13041
- const harnessLower = expectedHarness.toLowerCase();
13042
- const procLower = processName.toLowerCase();
13043
- return procLower.includes(harnessLower) || procLower.includes("node") || procLower.includes("bun");
13044
- } catch {
13045
- return true;
13046
- }
13047
- }
13048
- async function clearAgentPidEverywhere(ctx, chatroomId, role) {
13049
- try {
13050
- await ctx.client.mutation(api.machines.updateSpawnedAgent, {
13051
- sessionId: ctx.sessionId,
13052
- machineId: ctx.machineId,
13053
- chatroomId,
13054
- role,
13055
- pid: undefined
13056
- });
13057
- } catch (e) {
13058
- console.log(` ⚠️ Failed to clear PID in backend: ${e.message}`);
13059
- }
13060
- clearAgentPid(ctx.machineId, chatroomId, role);
13061
- }
13062
- async function handleAgentCrashRecovery(ctx, originalCommand, _crashedPid) {
13063
- const { chatroomId, role } = originalCommand.payload;
13064
- const ts = formatTimestamp();
13065
- await clearAgentPidEverywhere(ctx, chatroomId, role).catch((err) => {
13066
- console.log(` ⚠️ Failed to clear PID after exit: ${err.message}`);
13067
- });
13068
- try {
13069
- await ctx.client.mutation(api.participants.leave, {
13070
- sessionId: ctx.sessionId,
13071
- chatroomId,
13072
- role
13073
- });
13074
- console.log(`[${ts}] Marked ${role} as offline (participant removed)`);
13075
- } catch (leaveErr) {
13076
- console.log(`[${ts}] ⚠️ Could not remove participant: ${leaveErr.message}`);
13077
- }
13078
- console.log(`[${ts}] \uD83D\uDD04 Attempting to restart ${role} (max ${MAX_CRASH_RESTART_ATTEMPTS} attempts)...`);
13079
- for (let attempt = 1;attempt <= MAX_CRASH_RESTART_ATTEMPTS; attempt++) {
13080
- const attemptTs = formatTimestamp();
13081
- console.log(`[${attemptTs}] Restart attempt ${attempt}/${MAX_CRASH_RESTART_ATTEMPTS}...`);
13082
- await new Promise((resolve2) => setTimeout(resolve2, CRASH_RESTART_DELAY_MS));
13083
- try {
13084
- const result = await handleStartAgent(ctx, originalCommand);
13085
- if (!result.failed) {
13086
- const successTs = formatTimestamp();
13087
- console.log(`[${successTs}] ✅ ${role} restarted successfully on attempt ${attempt}`);
13088
- return;
13089
- }
13090
- console.log(`[${attemptTs}] ⚠️ Restart attempt ${attempt} failed: ${result.result}`);
13091
- } catch (restartErr) {
13092
- console.log(`[${attemptTs}] ⚠️ Restart attempt ${attempt} error: ${restartErr.message}`);
13093
- }
13094
- }
13095
- const failTs = formatTimestamp();
13096
- console.log(`[${failTs}] ❌ Failed to restart ${role} after ${MAX_CRASH_RESTART_ATTEMPTS} attempts. ` + `The agent will need to be restarted manually or via the webapp.`);
13097
- }
13098
- async function recoverAgentState(ctx) {
13099
- const entries = listAgentEntries(ctx.machineId);
13100
- if (entries.length === 0) {
13101
- console.log(` No agent entries found — nothing to recover`);
13102
- return;
13103
- }
13104
- let recovered = 0;
13105
- let cleared = 0;
13106
- for (const { chatroomId, role, entry } of entries) {
13107
- const { pid, harness } = entry;
13108
- const alive = verifyPidOwnership(pid, harness);
13109
- if (alive) {
13110
- console.log(` ✅ Recovered: ${role} (PID ${pid}, harness: ${harness})`);
13111
- recovered++;
13112
- } else {
13113
- console.log(` \uD83E\uDDF9 Stale PID ${pid} for ${role} — clearing`);
13114
- await clearAgentPidEverywhere(ctx, chatroomId, role);
13115
- cleared++;
13116
- }
13117
- }
13118
- console.log(` Recovery complete: ${recovered} alive, ${cleared} stale cleared`);
13119
- }
13587
+ // src/commands/machine/daemon-start/handlers/ping.ts
13120
13588
  function handlePing() {
13121
13589
  console.log(` ↪ Responding: pong`);
13122
13590
  return { result: "pong", failed: false };
13123
13591
  }
13124
- function handleStatus(ctx) {
13125
- const result = JSON.stringify({
13126
- hostname: ctx.config?.hostname,
13127
- os: ctx.config?.os,
13128
- availableHarnesses: ctx.config?.availableHarnesses,
13129
- chatroomAgents: Object.keys(ctx.config?.chatroomAgents ?? {})
13130
- });
13131
- console.log(` ↪ Responding with status`);
13132
- return { result, failed: false };
13133
- }
13592
+
13593
+ // src/commands/machine/daemon-start/handlers/start-agent.ts
13134
13594
  async function handleStartAgent(ctx, command) {
13135
13595
  const { chatroomId, role, agentHarness, model, workingDir } = command.payload;
13136
13596
  console.log(` ↪ start-agent command received`);
@@ -13140,100 +13600,163 @@ async function handleStartAgent(ctx, command) {
13140
13600
  if (model) {
13141
13601
  console.log(` Model: ${model}`);
13142
13602
  }
13143
- let agentContext = getAgentContext(chatroomId, role);
13144
- if (!agentContext && workingDir) {
13145
- console.log(` No local agent context, using workingDir from command payload`);
13146
- updateAgentContext(chatroomId, role, agentHarness, workingDir);
13147
- agentContext = getAgentContext(chatroomId, role);
13148
- }
13149
- if (!agentContext) {
13150
- const msg = `No agent context found for ${chatroomId}/${role}`;
13151
- console.log(` ⚠️ ${msg}`);
13152
- return { result: msg, failed: true };
13603
+ if (!workingDir) {
13604
+ const msg2 = `No workingDir provided in command payload for ${chatroomId}/${role}`;
13605
+ console.log(` ⚠️ ${msg2}`);
13606
+ return { result: msg2, failed: true };
13153
13607
  }
13154
- console.log(` Working dir: ${agentContext.workingDir}`);
13608
+ console.log(` Working dir: ${workingDir}`);
13155
13609
  try {
13156
- const dirStat = await stat(agentContext.workingDir);
13610
+ const dirStat = await ctx.deps.fs.stat(workingDir);
13157
13611
  if (!dirStat.isDirectory()) {
13158
- const msg = `Working directory is not a directory: ${agentContext.workingDir}`;
13159
- console.log(` ⚠️ ${msg}`);
13160
- return { result: msg, failed: true };
13612
+ const msg2 = `Working directory is not a directory: ${workingDir}`;
13613
+ console.log(` ⚠️ ${msg2}`);
13614
+ return { result: msg2, failed: true };
13161
13615
  }
13162
13616
  } catch {
13163
- const msg = `Working directory does not exist: ${agentContext.workingDir}`;
13164
- console.log(` ⚠️ ${msg}`);
13165
- return { result: msg, failed: true };
13617
+ const msg2 = `Working directory does not exist: ${workingDir}`;
13618
+ console.log(` ⚠️ ${msg2}`);
13619
+ return { result: msg2, failed: true };
13620
+ }
13621
+ try {
13622
+ const existingConfigs = await ctx.deps.backend.query(api.machines.getAgentConfigs, {
13623
+ sessionId: ctx.sessionId,
13624
+ chatroomId
13625
+ });
13626
+ const existingConfig = existingConfigs.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
13627
+ if (existingConfig?.spawnedAgentPid) {
13628
+ const existingPid = existingConfig.spawnedAgentPid;
13629
+ const isAlive = ctx.remoteAgentService.isAlive(existingPid);
13630
+ if (isAlive) {
13631
+ console.log(` ⚠️ Existing agent detected (PID: ${existingPid}) — stopping before respawn`);
13632
+ await onAgentShutdown(ctx, { chatroomId, role, pid: existingPid });
13633
+ console.log(` ✅ Existing agent stopped`);
13634
+ }
13635
+ }
13636
+ } catch (e) {
13637
+ console.log(` ⚠️ Could not check for existing agent (proceeding): ${e.message}`);
13166
13638
  }
13167
13639
  const convexUrl = getConvexUrl();
13168
- const initPromptResult = await ctx.client.query(api.messages.getInitPrompt, {
13640
+ const initPromptResult = await ctx.deps.backend.query(api.messages.getInitPrompt, {
13169
13641
  sessionId: ctx.sessionId,
13170
13642
  chatroomId,
13171
13643
  role,
13172
13644
  convexUrl
13173
13645
  });
13174
13646
  if (!initPromptResult?.prompt) {
13175
- const msg = "Failed to fetch init prompt from backend";
13176
- console.log(` ⚠️ ${msg}`);
13177
- return { result: msg, failed: true };
13647
+ const msg2 = "Failed to fetch init prompt from backend";
13648
+ console.log(` ⚠️ ${msg2}`);
13649
+ return { result: msg2, failed: true };
13178
13650
  }
13179
13651
  console.log(` Fetched split init prompt from backend`);
13180
- const harnessVersion = ctx.config?.harnessVersions?.[agentHarness];
13181
- const registry = getDriverRegistry();
13182
- let driver;
13652
+ const combinedPrompt = `${initPromptResult.rolePrompt}
13653
+
13654
+ ${initPromptResult.initialMessage}`;
13655
+ let spawnResult;
13183
13656
  try {
13184
- driver = registry.get(agentHarness);
13185
- } catch {
13186
- const msg = `No driver registered for harness: ${agentHarness}`;
13187
- console.log(` ⚠️ ${msg}`);
13188
- return { result: msg, failed: true };
13657
+ spawnResult = await ctx.remoteAgentService.spawn({
13658
+ workingDir,
13659
+ prompt: combinedPrompt,
13660
+ model,
13661
+ context: { machineId: ctx.machineId, chatroomId, role }
13662
+ });
13663
+ } catch (e) {
13664
+ const msg2 = `Failed to spawn agent: ${e.message}`;
13665
+ console.log(` ⚠️ ${msg2}`);
13666
+ return { result: msg2, failed: true };
13667
+ }
13668
+ const { pid } = spawnResult;
13669
+ const msg = `Agent spawned (PID: ${pid})`;
13670
+ console.log(` ✅ ${msg}`);
13671
+ try {
13672
+ await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
13673
+ sessionId: ctx.sessionId,
13674
+ machineId: ctx.machineId,
13675
+ chatroomId,
13676
+ role,
13677
+ pid,
13678
+ model
13679
+ });
13680
+ console.log(` Updated backend with PID: ${pid}`);
13681
+ ctx.deps.machine.persistAgentPid(ctx.machineId, chatroomId, role, pid, agentHarness);
13682
+ } catch (e) {
13683
+ console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
13189
13684
  }
13190
- const startResult = await driver.start({
13191
- workingDir: agentContext.workingDir,
13192
- rolePrompt: initPromptResult.rolePrompt,
13193
- initialMessage: initPromptResult.initialMessage,
13194
- harnessVersion: harnessVersion ?? undefined,
13685
+ ctx.events.emit("agent:started", {
13686
+ chatroomId,
13687
+ role,
13688
+ pid,
13689
+ harness: agentHarness,
13195
13690
  model
13196
13691
  });
13197
- if (startResult.success && startResult.handle) {
13198
- const msg = `Agent spawned (PID: ${startResult.handle.pid})`;
13199
- console.log(` ✅ ${msg}`);
13200
- if (startResult.handle.pid) {
13201
- try {
13202
- await ctx.client.mutation(api.machines.updateSpawnedAgent, {
13203
- sessionId: ctx.sessionId,
13204
- machineId: ctx.machineId,
13205
- chatroomId,
13206
- role,
13207
- pid: startResult.handle.pid,
13208
- model
13209
- });
13210
- console.log(` Updated backend with PID: ${startResult.handle.pid}`);
13211
- persistAgentPid(ctx.machineId, chatroomId, role, startResult.handle.pid, agentHarness);
13212
- } catch (e) {
13213
- console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
13214
- }
13215
- if (startResult.onExit) {
13216
- const spawnedPid = startResult.handle.pid;
13217
- startResult.onExit((code2, signal) => {
13218
- const ts = formatTimestamp();
13219
- console.log(`[${ts}] ⚠️ Agent process exited unexpectedly ` + `(PID: ${spawnedPid}, role: ${role}, code: ${code2}, signal: ${signal})`);
13220
- handleAgentCrashRecovery(ctx, command, spawnedPid).catch((err) => {
13221
- console.log(` ⚠️ Crash recovery failed for ${role}: ${err.message}`);
13222
- });
13223
- });
13224
- }
13692
+ spawnResult.onExit(({ code: code2, signal }) => {
13693
+ const wasIntentional = ctx.deps.stops.consume(chatroomId, role);
13694
+ ctx.events.emit("agent:exited", {
13695
+ chatroomId,
13696
+ role,
13697
+ pid,
13698
+ code: code2,
13699
+ signal,
13700
+ intentional: wasIntentional
13701
+ });
13702
+ });
13703
+ let lastReportedTokenAt = 0;
13704
+ spawnResult.onOutput(() => {
13705
+ const now = Date.now();
13706
+ if (now - lastReportedTokenAt >= 30000) {
13707
+ lastReportedTokenAt = now;
13708
+ ctx.deps.backend.mutation(api.participants.updateTokenActivity, {
13709
+ sessionId: ctx.sessionId,
13710
+ chatroomId,
13711
+ role
13712
+ }).catch(() => {});
13225
13713
  }
13226
- return { result: msg, failed: false };
13714
+ });
13715
+ return { result: msg, failed: false };
13716
+ }
13717
+ var init_start_agent = __esm(() => {
13718
+ init_api3();
13719
+ init_client2();
13720
+ init_on_agent_shutdown();
13721
+ });
13722
+
13723
+ // src/commands/machine/daemon-start/handlers/status.ts
13724
+ function handleStatus(ctx) {
13725
+ const result = JSON.stringify({
13726
+ hostname: ctx.config?.hostname,
13727
+ os: ctx.config?.os,
13728
+ availableHarnesses: ctx.config?.availableHarnesses
13729
+ });
13730
+ console.log(` ↪ Responding with status`);
13731
+ return { result, failed: false };
13732
+ }
13733
+
13734
+ // src/commands/machine/daemon-start/handlers/shared.ts
13735
+ async function clearAgentPidEverywhere(ctx, chatroomId, role) {
13736
+ try {
13737
+ await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
13738
+ sessionId: ctx.sessionId,
13739
+ machineId: ctx.machineId,
13740
+ chatroomId,
13741
+ role,
13742
+ pid: undefined
13743
+ });
13744
+ } catch (e) {
13745
+ console.log(` ⚠️ Failed to clear PID in backend: ${e.message}`);
13227
13746
  }
13228
- console.log(` ⚠️ ${startResult.message}`);
13229
- return { result: startResult.message, failed: true };
13747
+ ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
13230
13748
  }
13749
+ var init_shared = __esm(() => {
13750
+ init_api3();
13751
+ });
13752
+
13753
+ // src/commands/machine/daemon-start/handlers/stop-agent.ts
13231
13754
  async function handleStopAgent(ctx, command) {
13232
13755
  const { chatroomId, role } = command.payload;
13233
13756
  console.log(` ↪ stop-agent command received`);
13234
13757
  console.log(` Chatroom: ${chatroomId}`);
13235
13758
  console.log(` Role: ${role}`);
13236
- const configsResult = await ctx.client.query(api.machines.getAgentConfigs, {
13759
+ const configsResult = await ctx.deps.backend.query(api.machines.getAgentConfigs, {
13237
13760
  sessionId: ctx.sessionId,
13238
13761
  chatroomId
13239
13762
  });
@@ -13244,28 +13767,14 @@ async function handleStopAgent(ctx, command) {
13244
13767
  return { result: msg, failed: true };
13245
13768
  }
13246
13769
  const pidToKill = targetConfig.spawnedAgentPid;
13247
- const agentHarness = targetConfig.agentType || undefined;
13248
13770
  console.log(` Stopping agent with PID: ${pidToKill}`);
13249
- const stopHandle = {
13250
- harness: agentHarness || "opencode",
13251
- type: "process",
13252
- pid: pidToKill,
13253
- workingDir: ""
13254
- };
13255
- const registry = getDriverRegistry();
13256
- let stopDriver;
13257
- try {
13258
- stopDriver = agentHarness ? registry.get(agentHarness) : null;
13259
- } catch {
13260
- stopDriver = null;
13261
- }
13262
- const isAlive = stopDriver ? await stopDriver.isAlive(stopHandle) : verifyPidOwnership(pidToKill, agentHarness);
13771
+ const isAlive = ctx.remoteAgentService.isAlive(pidToKill);
13263
13772
  if (!isAlive) {
13264
13773
  console.log(` ⚠️ PID ${pidToKill} does not appear to belong to the expected agent`);
13265
13774
  await clearAgentPidEverywhere(ctx, chatroomId, role);
13266
13775
  console.log(` Cleared stale PID`);
13267
13776
  try {
13268
- await ctx.client.mutation(api.participants.leave, {
13777
+ await ctx.deps.backend.mutation(api.participants.leave, {
13269
13778
  sessionId: ctx.sessionId,
13270
13779
  chatroomId,
13271
13780
  role
@@ -13278,54 +13787,58 @@ async function handleStopAgent(ctx, command) {
13278
13787
  };
13279
13788
  }
13280
13789
  try {
13281
- if (stopDriver) {
13282
- await stopDriver.stop(stopHandle);
13283
- } else {
13284
- process.kill(pidToKill, "SIGTERM");
13285
- }
13286
- const msg = `Agent stopped (PID: ${pidToKill})`;
13287
- console.log(` ✅ ${msg}`);
13288
- await clearAgentPidEverywhere(ctx, chatroomId, role);
13289
- console.log(` Cleared PID`);
13290
- try {
13291
- await ctx.client.mutation(api.participants.leave, {
13292
- sessionId: ctx.sessionId,
13293
- chatroomId,
13294
- role
13295
- });
13296
- console.log(` Removed participant record`);
13297
- } catch (leaveErr) {
13298
- console.log(` ⚠️ Could not remove participant: ${leaveErr.message}`);
13299
- }
13300
- return { result: msg, failed: false };
13790
+ const shutdownResult = await onAgentShutdown(ctx, {
13791
+ chatroomId,
13792
+ role,
13793
+ pid: pidToKill
13794
+ });
13795
+ const msg = shutdownResult.killed ? `Agent stopped (PID: ${pidToKill})` : `Agent stop attempted (PID: ${pidToKill}) — process may still be running`;
13796
+ console.log(` ${shutdownResult.killed ? "" : "⚠️ "} ${msg}`);
13797
+ return { result: msg, failed: !shutdownResult.killed };
13301
13798
  } catch (e) {
13302
- const err = e;
13303
- if (err.code === "ESRCH") {
13304
- await clearAgentPidEverywhere(ctx, chatroomId, role);
13305
- try {
13306
- await ctx.client.mutation(api.participants.leave, {
13307
- sessionId: ctx.sessionId,
13308
- chatroomId,
13309
- role
13310
- });
13311
- } catch {}
13312
- const msg2 = "Process not found (may have already exited)";
13313
- console.log(` ⚠️ ${msg2}`);
13314
- return { result: msg2, failed: true };
13315
- }
13316
- const msg = `Failed to stop agent: ${err.message}`;
13799
+ const msg = `Failed to stop agent: ${e.message}`;
13317
13800
  console.log(` ⚠️ ${msg}`);
13318
13801
  return { result: msg, failed: true };
13319
13802
  }
13320
13803
  }
13804
+ var init_stop_agent = __esm(() => {
13805
+ init_api3();
13806
+ init_on_agent_shutdown();
13807
+ init_shared();
13808
+ });
13809
+
13810
+ // src/commands/machine/daemon-start/command-loop.ts
13811
+ async function refreshModels(ctx) {
13812
+ const models = await ctx.remoteAgentService.listModels();
13813
+ if (!ctx.config)
13814
+ return;
13815
+ try {
13816
+ await ctx.deps.backend.mutation(api.machines.register, {
13817
+ sessionId: ctx.sessionId,
13818
+ machineId: ctx.machineId,
13819
+ hostname: ctx.config.hostname,
13820
+ os: ctx.config.os,
13821
+ availableHarnesses: ctx.config.availableHarnesses,
13822
+ harnessVersions: ctx.config.harnessVersions,
13823
+ availableModels: models
13824
+ });
13825
+ console.log(`[${formatTimestamp()}] \uD83D\uDD04 Model refresh: ${models.length > 0 ? `${models.length} models` : "none discovered"}`);
13826
+ } catch (error) {
13827
+ console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${error.message}`);
13828
+ }
13829
+ }
13321
13830
  async function processCommand(ctx, command) {
13322
13831
  console.log(`[${formatTimestamp()}] \uD83D\uDCE8 Command received: ${command.type}`);
13323
13832
  try {
13324
- await ctx.client.mutation(api.machines.ackCommand, {
13833
+ await ctx.deps.backend.mutation(api.machines.ackCommand, {
13325
13834
  sessionId: ctx.sessionId,
13326
13835
  commandId: command._id,
13327
13836
  status: "processing"
13328
13837
  });
13838
+ ctx.events.emit("command:processing", {
13839
+ commandId: command._id.toString(),
13840
+ type: command.type
13841
+ });
13329
13842
  let commandResult;
13330
13843
  switch (command.type) {
13331
13844
  case "ping":
@@ -13349,147 +13862,39 @@ async function processCommand(ctx, command) {
13349
13862
  }
13350
13863
  }
13351
13864
  const finalStatus = commandResult.failed ? "failed" : "completed";
13352
- await ctx.client.mutation(api.machines.ackCommand, {
13865
+ await ctx.deps.backend.mutation(api.machines.ackCommand, {
13353
13866
  sessionId: ctx.sessionId,
13354
13867
  commandId: command._id,
13355
13868
  status: finalStatus,
13356
13869
  result: commandResult.result
13357
13870
  });
13358
- if (commandResult.failed) {
13359
- console.log(` ❌ Command failed: ${commandResult.result}`);
13360
- } else {
13361
- console.log(` ✅ Command completed`);
13362
- }
13363
- } catch (error) {
13364
- console.error(` ❌ Command failed: ${error.message}`);
13365
- try {
13366
- await ctx.client.mutation(api.machines.ackCommand, {
13367
- sessionId: ctx.sessionId,
13368
- commandId: command._id,
13369
- status: "failed",
13370
- result: error.message
13371
- });
13372
- } catch {}
13373
- }
13374
- }
13375
- async function discoverModels() {
13376
- const models = [];
13377
- try {
13378
- const registry = getDriverRegistry();
13379
- for (const driver of registry.all()) {
13380
- if (driver.capabilities.dynamicModelDiscovery) {
13381
- const driverModels = await driver.listModels();
13382
- models.push(...driverModels);
13383
- }
13384
- }
13385
- } catch {}
13386
- return models;
13387
- }
13388
- async function refreshModels(ctx) {
13389
- const models = await discoverModels();
13390
- if (!ctx.config)
13391
- return;
13392
- try {
13393
- await ctx.client.mutation(api.machines.register, {
13394
- sessionId: ctx.sessionId,
13395
- machineId: ctx.machineId,
13396
- hostname: ctx.config.hostname,
13397
- os: ctx.config.os,
13398
- availableHarnesses: ctx.config.availableHarnesses,
13399
- harnessVersions: ctx.config.harnessVersions,
13400
- availableModels: models
13401
- });
13402
- console.log(`[${formatTimestamp()}] \uD83D\uDD04 Model refresh: ${models.length > 0 ? `${models.length} models` : "none discovered"}`);
13403
- } catch (error) {
13404
- console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${error.message}`);
13405
- }
13406
- }
13407
- async function initDaemon() {
13408
- if (!acquireLock()) {
13409
- process.exit(1);
13410
- }
13411
- const convexUrl = getConvexUrl();
13412
- const sessionId = getSessionId();
13413
- if (!sessionId) {
13414
- const otherUrls = getOtherSessionUrls();
13415
- console.error(`❌ Not authenticated for: ${convexUrl}`);
13416
- if (otherUrls.length > 0) {
13417
- console.error(`
13418
- \uD83D\uDCA1 You have sessions for other environments:`);
13419
- for (const url of otherUrls) {
13420
- console.error(` • ${url}`);
13421
- }
13422
- }
13423
- console.error(`
13424
- Run: chatroom auth login`);
13425
- releaseLock();
13426
- process.exit(1);
13427
- }
13428
- const machineId = getMachineId();
13429
- if (!machineId) {
13430
- console.error(`❌ Machine not registered`);
13431
- console.error(`
13432
- Run any chatroom command first to register this machine,`);
13433
- console.error(`for example: chatroom auth status`);
13434
- releaseLock();
13435
- process.exit(1);
13436
- }
13437
- const client2 = await getConvexClient();
13438
- const typedSessionId = sessionId;
13439
- const config3 = loadMachineConfig();
13440
- const availableModels = await discoverModels();
13441
- if (config3) {
13442
- try {
13443
- await client2.mutation(api.machines.register, {
13444
- sessionId: typedSessionId,
13445
- machineId,
13446
- hostname: config3.hostname,
13447
- os: config3.os,
13448
- availableHarnesses: config3.availableHarnesses,
13449
- harnessVersions: config3.harnessVersions,
13450
- availableModels
13451
- });
13452
- } catch (error) {
13453
- console.warn(`⚠️ Machine registration update failed: ${error.message}`);
13454
- }
13455
- }
13456
- try {
13457
- await client2.mutation(api.machines.updateDaemonStatus, {
13458
- sessionId: typedSessionId,
13459
- machineId,
13460
- connected: true
13871
+ ctx.events.emit("command:completed", {
13872
+ commandId: command._id.toString(),
13873
+ type: command.type,
13874
+ failed: commandResult.failed,
13875
+ result: commandResult.result
13461
13876
  });
13462
- } catch (error) {
13463
- if (isNetworkError(error)) {
13464
- formatConnectivityError(error, convexUrl);
13877
+ if (commandResult.failed) {
13878
+ console.log(` ❌ Command failed: ${commandResult.result}`);
13465
13879
  } else {
13466
- console.error(`❌ Failed to update daemon status: ${error.message}`);
13880
+ console.log(` ✅ Command completed`);
13467
13881
  }
13468
- releaseLock();
13469
- process.exit(1);
13470
- }
13471
- const ctx = { client: client2, sessionId: typedSessionId, machineId, config: config3 };
13472
- console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
13473
- console.log(` CLI version: ${getVersion()}`);
13474
- console.log(` Machine ID: ${machineId}`);
13475
- console.log(` Hostname: ${config3?.hostname ?? "Unknown"}`);
13476
- console.log(` Available harnesses: ${config3?.availableHarnesses.join(", ") || "none"}`);
13477
- console.log(` Available models: ${availableModels.length > 0 ? availableModels.length : "none discovered"}`);
13478
- console.log(` PID: ${process.pid}`);
13479
- console.log(`
13480
- [${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
13481
- try {
13482
- await recoverAgentState(ctx);
13483
- } catch (e) {
13484
- console.log(` ⚠️ Recovery failed: ${e.message}`);
13485
- console.log(` Continuing with fresh state`);
13882
+ } catch (error) {
13883
+ console.error(` ❌ Command failed: ${error.message}`);
13884
+ try {
13885
+ await ctx.deps.backend.mutation(api.machines.ackCommand, {
13886
+ sessionId: ctx.sessionId,
13887
+ commandId: command._id,
13888
+ status: "failed",
13889
+ result: error.message
13890
+ });
13891
+ } catch {}
13486
13892
  }
13487
- return ctx;
13488
13893
  }
13489
13894
  async function startCommandLoop(ctx) {
13490
13895
  let heartbeatCount = 0;
13491
13896
  const heartbeatTimer = setInterval(() => {
13492
- ctx.client.mutation(api.machines.daemonHeartbeat, {
13897
+ ctx.deps.backend.mutation(api.machines.daemonHeartbeat, {
13493
13898
  sessionId: ctx.sessionId,
13494
13899
  machineId: ctx.machineId
13495
13900
  }).then(() => {
@@ -13504,13 +13909,7 @@ async function startCommandLoop(ctx) {
13504
13909
  console.log(`
13505
13910
  [${formatTimestamp()}] Shutting down...`);
13506
13911
  clearInterval(heartbeatTimer);
13507
- try {
13508
- await ctx.client.mutation(api.machines.updateDaemonStatus, {
13509
- sessionId: ctx.sessionId,
13510
- machineId: ctx.machineId,
13511
- connected: false
13512
- });
13513
- } catch {}
13912
+ await onDaemonShutdown(ctx);
13514
13913
  releaseLock();
13515
13914
  process.exit(0);
13516
13915
  };
@@ -13566,7 +13965,7 @@ Listening for commands...`);
13566
13965
  parsed.push(command);
13567
13966
  } else {
13568
13967
  try {
13569
- await ctx.client.mutation(api.machines.ackCommand, {
13968
+ await ctx.deps.backend.mutation(api.machines.ackCommand, {
13570
13969
  sessionId: ctx.sessionId,
13571
13970
  commandId: raw._id,
13572
13971
  status: "failed",
@@ -13587,20 +13986,283 @@ Listening for commands...`);
13587
13986
  modelRefreshTimer.unref();
13588
13987
  return await new Promise(() => {});
13589
13988
  }
13590
- async function daemonStart() {
13591
- const ctx = await initDaemon();
13592
- await startCommandLoop(ctx);
13593
- }
13594
- var MAX_CRASH_RESTART_ATTEMPTS = 3, CRASH_RESTART_DELAY_MS = 3000, MODEL_REFRESH_INTERVAL_MS;
13595
- var init_daemon_start = __esm(() => {
13989
+ var MODEL_REFRESH_INTERVAL_MS;
13990
+ var init_command_loop = __esm(() => {
13991
+ init_api3();
13992
+ init_client2();
13993
+ init_on_daemon_shutdown();
13596
13994
  init_pid();
13995
+ init_start_agent();
13996
+ init_stop_agent();
13997
+ MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
13998
+ });
13999
+
14000
+ // src/commands/machine/daemon-start/handlers/state-recovery.ts
14001
+ async function recoverAgentState(ctx) {
14002
+ const entries = ctx.deps.machine.listAgentEntries(ctx.machineId);
14003
+ if (entries.length === 0) {
14004
+ console.log(` No agent entries found — nothing to recover`);
14005
+ return;
14006
+ }
14007
+ let recovered = 0;
14008
+ let cleared = 0;
14009
+ for (const { chatroomId, role, entry } of entries) {
14010
+ const { pid, harness } = entry;
14011
+ const alive = ctx.remoteAgentService.isAlive(pid);
14012
+ if (alive) {
14013
+ console.log(` ✅ Recovered: ${role} (PID ${pid}, harness: ${harness})`);
14014
+ recovered++;
14015
+ } else {
14016
+ console.log(` \uD83E\uDDF9 Stale PID ${pid} for ${role} — clearing`);
14017
+ await clearAgentPidEverywhere(ctx, chatroomId, role);
14018
+ cleared++;
14019
+ }
14020
+ }
14021
+ console.log(` Recovery complete: ${recovered} alive, ${cleared} stale cleared`);
14022
+ }
14023
+ var init_state_recovery = __esm(() => {
14024
+ init_shared();
14025
+ });
14026
+
14027
+ // src/commands/machine/daemon-start/event-bus.ts
14028
+ class DaemonEventBus {
14029
+ listeners = new Map;
14030
+ on(event, listener) {
14031
+ if (!this.listeners.has(event)) {
14032
+ this.listeners.set(event, new Set);
14033
+ }
14034
+ this.listeners.get(event).add(listener);
14035
+ return () => {
14036
+ this.listeners.get(event)?.delete(listener);
14037
+ };
14038
+ }
14039
+ emit(event, payload) {
14040
+ const set = this.listeners.get(event);
14041
+ if (!set)
14042
+ return;
14043
+ for (const listener of set) {
14044
+ try {
14045
+ listener(payload);
14046
+ } catch (err) {
14047
+ console.warn(`[EventBus] Listener error on "${event}": ${err.message}`);
14048
+ }
14049
+ }
14050
+ }
14051
+ removeAllListeners() {
14052
+ this.listeners.clear();
14053
+ }
14054
+ }
14055
+
14056
+ // src/commands/machine/daemon-start/event-listeners.ts
14057
+ function registerEventListeners(ctx) {
14058
+ const unsubs = [];
14059
+ unsubs.push(ctx.events.on("agent:exited", (payload) => {
14060
+ const { chatroomId, role, pid, code: code2, signal, intentional } = payload;
14061
+ const ts = formatTimestamp();
14062
+ if (intentional) {
14063
+ console.log(`[${ts}] ℹ️ Agent process exited after intentional stop ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14064
+ } else {
14065
+ console.log(`[${ts}] ⚠️ Agent process exited ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14066
+ }
14067
+ ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
14068
+ sessionId: ctx.sessionId,
14069
+ machineId: ctx.machineId,
14070
+ chatroomId,
14071
+ role,
14072
+ pid: undefined
14073
+ }).catch((err) => {
14074
+ console.log(` ⚠️ Failed to clear PID in backend: ${err.message}`);
14075
+ });
14076
+ ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
14077
+ ctx.remoteAgentService.untrack(pid);
14078
+ ctx.deps.backend.mutation(api.participants.leave, {
14079
+ sessionId: ctx.sessionId,
14080
+ chatroomId,
14081
+ role
14082
+ }).catch((err) => {
14083
+ console.log(` ⚠️ Could not remove participant: ${err.message}`);
14084
+ });
14085
+ }));
14086
+ unsubs.push(ctx.events.on("agent:started", (payload) => {
14087
+ const ts = formatTimestamp();
14088
+ console.log(`[${ts}] \uD83D\uDFE2 Agent started: ${payload.role} (PID: ${payload.pid}, harness: ${payload.harness})`);
14089
+ }));
14090
+ unsubs.push(ctx.events.on("agent:stopped", (payload) => {
14091
+ const ts = formatTimestamp();
14092
+ console.log(`[${ts}] \uD83D\uDD34 Agent stopped: ${payload.role} (PID: ${payload.pid})`);
14093
+ }));
14094
+ return () => {
14095
+ for (const unsub of unsubs) {
14096
+ unsub();
14097
+ }
14098
+ };
14099
+ }
14100
+ var init_event_listeners = __esm(() => {
14101
+ init_api3();
14102
+ });
14103
+
14104
+ // src/commands/machine/daemon-start/init.ts
14105
+ import { stat } from "node:fs/promises";
14106
+ async function discoverModels(service) {
14107
+ try {
14108
+ return await service.listModels();
14109
+ } catch {
14110
+ return [];
14111
+ }
14112
+ }
14113
+ function createDefaultDeps16() {
14114
+ return {
14115
+ backend: {
14116
+ mutation: async () => {
14117
+ throw new Error("Backend not initialized");
14118
+ },
14119
+ query: async () => {
14120
+ throw new Error("Backend not initialized");
14121
+ }
14122
+ },
14123
+ processes: {
14124
+ kill: (pid, signal) => process.kill(pid, signal)
14125
+ },
14126
+ fs: {
14127
+ stat
14128
+ },
14129
+ stops: {
14130
+ mark: markIntentionalStop,
14131
+ consume: consumeIntentionalStop,
14132
+ clear: clearIntentionalStop
14133
+ },
14134
+ machine: {
14135
+ clearAgentPid,
14136
+ persistAgentPid,
14137
+ listAgentEntries
14138
+ },
14139
+ clock: {
14140
+ now: () => Date.now(),
14141
+ delay: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
14142
+ }
14143
+ };
14144
+ }
14145
+ async function initDaemon() {
14146
+ if (!acquireLock()) {
14147
+ process.exit(1);
14148
+ }
14149
+ const convexUrl = getConvexUrl();
14150
+ const sessionId = getSessionId();
14151
+ if (!sessionId) {
14152
+ const otherUrls = getOtherSessionUrls();
14153
+ console.error(`❌ Not authenticated for: ${convexUrl}`);
14154
+ if (otherUrls.length > 0) {
14155
+ console.error(`
14156
+ \uD83D\uDCA1 You have sessions for other environments:`);
14157
+ for (const url of otherUrls) {
14158
+ console.error(` • ${url}`);
14159
+ }
14160
+ }
14161
+ console.error(`
14162
+ Run: chatroom auth login`);
14163
+ releaseLock();
14164
+ process.exit(1);
14165
+ }
14166
+ const machineId = getMachineId();
14167
+ if (!machineId) {
14168
+ console.error(`❌ Machine not registered`);
14169
+ console.error(`
14170
+ Run any chatroom command first to register this machine,`);
14171
+ console.error(`for example: chatroom auth status`);
14172
+ releaseLock();
14173
+ process.exit(1);
14174
+ }
14175
+ const client2 = await getConvexClient();
14176
+ const typedSessionId = sessionId;
14177
+ const config3 = loadMachineConfig();
14178
+ const remoteAgentService = new OpenCodeAgentService;
14179
+ const availableModels = await discoverModels(remoteAgentService);
14180
+ if (config3) {
14181
+ try {
14182
+ await client2.mutation(api.machines.register, {
14183
+ sessionId: typedSessionId,
14184
+ machineId,
14185
+ hostname: config3.hostname,
14186
+ os: config3.os,
14187
+ availableHarnesses: config3.availableHarnesses,
14188
+ harnessVersions: config3.harnessVersions,
14189
+ availableModels
14190
+ });
14191
+ } catch (error) {
14192
+ console.warn(`⚠️ Machine registration update failed: ${error.message}`);
14193
+ }
14194
+ }
14195
+ try {
14196
+ await client2.mutation(api.machines.updateDaemonStatus, {
14197
+ sessionId: typedSessionId,
14198
+ machineId,
14199
+ connected: true
14200
+ });
14201
+ } catch (error) {
14202
+ if (isNetworkError(error)) {
14203
+ formatConnectivityError(error, convexUrl);
14204
+ } else {
14205
+ console.error(`❌ Failed to update daemon status: ${error.message}`);
14206
+ }
14207
+ releaseLock();
14208
+ process.exit(1);
14209
+ }
14210
+ const deps = createDefaultDeps16();
14211
+ deps.backend.mutation = (endpoint, args) => client2.mutation(endpoint, args);
14212
+ deps.backend.query = (endpoint, args) => client2.query(endpoint, args);
14213
+ const events = new DaemonEventBus;
14214
+ const ctx = {
14215
+ client: client2,
14216
+ sessionId: typedSessionId,
14217
+ machineId,
14218
+ config: config3,
14219
+ deps,
14220
+ events,
14221
+ remoteAgentService
14222
+ };
14223
+ registerEventListeners(ctx);
14224
+ console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
14225
+ console.log(` CLI version: ${getVersion()}`);
14226
+ console.log(` Machine ID: ${machineId}`);
14227
+ console.log(` Hostname: ${config3?.hostname ?? "Unknown"}`);
14228
+ console.log(` Available harnesses: ${config3?.availableHarnesses.join(", ") || "none"}`);
14229
+ console.log(` Available models: ${availableModels.length > 0 ? `${availableModels.length} models` : "none discovered"}`);
14230
+ console.log(` PID: ${process.pid}`);
14231
+ console.log(`
14232
+ [${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
14233
+ try {
14234
+ await recoverAgentState(ctx);
14235
+ } catch (e) {
14236
+ console.log(` ⚠️ Recovery failed: ${e.message}`);
14237
+ console.log(` Continuing with fresh state`);
14238
+ }
14239
+ return ctx;
14240
+ }
14241
+ var init_init2 = __esm(() => {
14242
+ init_state_recovery();
13597
14243
  init_api3();
13598
- init_agent_drivers();
13599
14244
  init_storage();
13600
14245
  init_client2();
13601
14246
  init_machine();
14247
+ init_intentional_stops();
14248
+ init_opencode();
14249
+ init_error_formatting();
13602
14250
  init_version();
13603
- MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
14251
+ init_pid();
14252
+ init_event_listeners();
14253
+ });
14254
+
14255
+ // src/commands/machine/daemon-start/index.ts
14256
+ async function daemonStart() {
14257
+ const ctx = await initDaemon();
14258
+ await startCommandLoop(ctx);
14259
+ }
14260
+ var init_daemon_start = __esm(() => {
14261
+ init_command_loop();
14262
+ init_init2();
14263
+ init_start_agent();
14264
+ init_stop_agent();
14265
+ init_state_recovery();
13604
14266
  });
13605
14267
 
13606
14268
  // src/commands/machine/daemon-stop.ts
@@ -13613,7 +14275,7 @@ async function daemonStop() {
13613
14275
  console.log(`Stopping daemon (PID: ${pid})...`);
13614
14276
  try {
13615
14277
  process.kill(pid, "SIGTERM");
13616
- await new Promise((resolve2) => setTimeout(resolve2, 1000));
14278
+ await new Promise((resolve2) => setTimeout(resolve2, 8000));
13617
14279
  try {
13618
14280
  process.kill(pid, 0);
13619
14281
  console.log(`Process did not exit gracefully, forcing...`);
@@ -13661,29 +14323,56 @@ var init_machine2 = __esm(() => {
13661
14323
  init_daemon_status();
13662
14324
  });
13663
14325
 
13664
- // src/commands/opencode-install.ts
14326
+ // src/commands/opencode-install/index.ts
13665
14327
  var exports_opencode_install = {};
13666
14328
  __export(exports_opencode_install, {
13667
14329
  installTool: () => installTool
13668
14330
  });
13669
- async function isChatroomInstalled() {
14331
+ async function isChatroomInstalledDefault() {
13670
14332
  try {
13671
- const { execSync: execSync4 } = await import("child_process");
13672
- execSync4("chatroom --version", { stdio: "pipe" });
14333
+ const { execSync: execSync3 } = await import("child_process");
14334
+ execSync3("chatroom --version", { stdio: "pipe" });
13673
14335
  return true;
13674
14336
  } catch {
13675
14337
  return false;
13676
14338
  }
13677
14339
  }
13678
- async function installTool(options = {}) {
14340
+ async function createDefaultDeps17() {
14341
+ const client2 = await getConvexClient();
14342
+ const fs = await import("fs/promises");
14343
+ return {
14344
+ backend: {
14345
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
14346
+ query: (endpoint, args) => client2.query(endpoint, args)
14347
+ },
14348
+ session: {
14349
+ getSessionId,
14350
+ getConvexUrl,
14351
+ getOtherSessionUrls
14352
+ },
14353
+ fs: {
14354
+ access: async (p) => {
14355
+ await fs.access(p);
14356
+ },
14357
+ mkdir: async (p, options) => {
14358
+ await fs.mkdir(p, options);
14359
+ },
14360
+ writeFile: async (p, content, encoding) => {
14361
+ await fs.writeFile(p, content, encoding);
14362
+ }
14363
+ },
14364
+ isChatroomInstalled: isChatroomInstalledDefault
14365
+ };
14366
+ }
14367
+ async function installTool(options = {}, deps) {
14368
+ const d = deps ?? await createDefaultDeps17();
13679
14369
  const { checkExisting = true } = options;
13680
14370
  const os = await import("os");
13681
- const fs = await import("fs/promises");
13682
- const path = await import("path");
14371
+ const path2 = await import("path");
13683
14372
  const homeDir = os.homedir();
13684
- const toolDir = path.join(homeDir, ".config", "opencode", "tool");
13685
- const toolPath = path.join(toolDir, "chatroom.ts");
13686
- const handoffToolPath = path.join(toolDir, "chatroom-handoff.ts");
14373
+ const toolDir = path2.join(homeDir, ".config", "opencode", "tool");
14374
+ const toolPath = path2.join(toolDir, "chatroom.ts");
14375
+ const handoffToolPath = path2.join(toolDir, "chatroom-handoff.ts");
13687
14376
  const toolContent = `import { tool } from "@opencode-ai/plugin";
13688
14377
 
13689
14378
  /**
@@ -13713,7 +14402,7 @@ async function checkChatroomStatus(): Promise<{ installed: boolean; authenticate
13713
14402
 
13714
14403
  export default tool({
13715
14404
  description:
13716
- "Wait for tasks in a multi-agent chatroom. This command joins a chatroom with a specific role and waits for tasks to be assigned. It's a long-running operation that polls for pending tasks and handles the complete workflow including authentication, task claiming, and graceful interruption handling. Use this instead of bash 'chatroom wait-for-task' to avoid timeout issues.",
14405
+ "Get next task in a multi-agent chatroom. This command joins a chatroom with a specific role and waits for tasks to be assigned. It's a long-running operation that polls for pending tasks and handles the complete workflow including authentication, task claiming, and graceful interruption handling. Use this instead of bash 'chatroom get-next-task' to avoid timeout issues.",
13717
14406
  args: {
13718
14407
  chatroomId: tool.schema
13719
14408
  .string()
@@ -13773,7 +14462,7 @@ After logging in, try this command again.\`;
13773
14462
  }
13774
14463
 
13775
14464
  // Build command arguments
13776
- const cmdArgs = ['wait-for-task', args.chatroomId, '--role', args.role];
14465
+ const cmdArgs = ['get-next-task', args.chatroomId, '--role', args.role];
13777
14466
 
13778
14467
  if (args.duration !== undefined) {
13779
14468
  cmdArgs.push('--duration', args.duration);
@@ -13790,7 +14479,7 @@ After logging in, try this command again.\`;
13790
14479
  env.CHATROOM_CONVEX_URL = args.convexUrl;
13791
14480
  }
13792
14481
 
13793
- // Execute the wait-for-task command
14482
+ // Execute the get-next-task command
13794
14483
  // This is a long-running operation that polls for tasks
13795
14484
  const proc = Bun.spawn(['chatroom', ...cmdArgs], {
13796
14485
  stdout: 'pipe',
@@ -13967,11 +14656,11 @@ After logging in, try this command again.\`;
13967
14656
  if (checkExisting) {
13968
14657
  const existingFiles = [];
13969
14658
  try {
13970
- await fs.access(toolPath);
14659
+ await d.fs.access(toolPath);
13971
14660
  existingFiles.push(toolPath);
13972
14661
  } catch {}
13973
14662
  try {
13974
- await fs.access(handoffToolPath);
14663
+ await d.fs.access(handoffToolPath);
13975
14664
  existingFiles.push(handoffToolPath);
13976
14665
  } catch {}
13977
14666
  if (existingFiles.length > 0) {
@@ -13991,7 +14680,7 @@ Then run this command again, or use --force to overwrite.`;
13991
14680
  };
13992
14681
  }
13993
14682
  }
13994
- const installed = await isChatroomInstalled();
14683
+ const installed = await d.isChatroomInstalled();
13995
14684
  if (!installed) {
13996
14685
  const message2 = `⚠️ Chatroom CLI is not installed.
13997
14686
 
@@ -14005,9 +14694,9 @@ After installation, run this command again.`;
14005
14694
  message: message2
14006
14695
  };
14007
14696
  }
14008
- await fs.mkdir(toolDir, { recursive: true });
14009
- await fs.writeFile(toolPath, toolContent, "utf-8");
14010
- await fs.writeFile(handoffToolPath, handoffToolContent, "utf-8");
14697
+ await d.fs.mkdir(toolDir, { recursive: true });
14698
+ await d.fs.writeFile(toolPath, toolContent, "utf-8");
14699
+ await d.fs.writeFile(handoffToolPath, handoffToolContent, "utf-8");
14011
14700
  const message = `✅ Installed chatroom OpenCode tools successfully!
14012
14701
 
14013
14702
  Locations:
@@ -14015,7 +14704,7 @@ Locations:
14015
14704
  • ${handoffToolPath}
14016
14705
 
14017
14706
  The following commands are now available in OpenCode:
14018
- • chatroom (wait-for-task) - Join chatroom and wait for tasks (no more timeouts!)
14707
+ • chatroom (get-next-task) - Get next task from chatroom (no more timeouts!)
14019
14708
  • chatroom-handoff - Complete your task and hand off to the next role
14020
14709
 
14021
14710
  Both tools will automatically check for:
@@ -14043,8 +14732,56 @@ If you're not authenticated, run:
14043
14732
  };
14044
14733
  }
14045
14734
  }
14735
+ var init_opencode_install = __esm(() => {
14736
+ init_storage();
14737
+ init_client2();
14738
+ });
14739
+
14740
+ // src/infrastructure/retry-queue.ts
14741
+ async function withRetry(fn, opts) {
14742
+ const { maxRetries, baseDelayMs, maxDelayMs } = { ...DEFAULTS, ...opts };
14743
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
14744
+ try {
14745
+ return await fn();
14746
+ } catch (e) {
14747
+ if (attempt === maxRetries) {
14748
+ console.warn(`[retry-queue] All ${maxRetries + 1} attempts exhausted: ${e.message}`);
14749
+ return;
14750
+ }
14751
+ const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
14752
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
14753
+ }
14754
+ }
14755
+ return;
14756
+ }
14757
+ var DEFAULTS;
14758
+ var init_retry_queue = __esm(() => {
14759
+ DEFAULTS = {
14760
+ maxRetries: 3,
14761
+ baseDelayMs: 500,
14762
+ maxDelayMs: 5000
14763
+ };
14764
+ });
14765
+
14766
+ // src/infrastructure/lifecycle-heartbeat.ts
14767
+ var exports_lifecycle_heartbeat = {};
14768
+ __export(exports_lifecycle_heartbeat, {
14769
+ sendLifecycleHeartbeat: () => sendLifecycleHeartbeat
14770
+ });
14771
+ function sendLifecycleHeartbeat(client2, opts) {
14772
+ withRetry(() => client2.mutation(api.participants.join, {
14773
+ sessionId: opts.sessionId,
14774
+ chatroomId: opts.chatroomId,
14775
+ role: opts.role,
14776
+ ...opts.action !== undefined ? { action: opts.action } : {}
14777
+ })).catch(() => {});
14778
+ }
14779
+ var init_lifecycle_heartbeat = __esm(() => {
14780
+ init_api3();
14781
+ init_retry_queue();
14782
+ });
14046
14783
 
14047
- // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/esm.mjs
14784
+ // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
14048
14785
  var import__ = __toESM(require_commander(), 1);
14049
14786
  var {
14050
14787
  program,
@@ -14089,8 +14826,8 @@ async function maybeRequireAuth() {
14089
14826
  console.log("⚠️ Skipping authentication (--skip-auth flag)");
14090
14827
  return;
14091
14828
  }
14092
- const { requireAuth: requireAuth2 } = await Promise.resolve().then(() => (init_middleware(), exports_middleware));
14093
- await requireAuth2();
14829
+ const { requireAuth: requireAuth3 } = await Promise.resolve().then(() => (init_middleware(), exports_middleware));
14830
+ await requireAuth3();
14094
14831
  }
14095
14832
  var authCommand = program2.command("auth").description("Manage CLI authentication");
14096
14833
  authCommand.command("login").description("Authenticate the CLI via browser").option("-f, --force", "Re-authenticate even if already logged in").action(async (options) => {
@@ -14109,6 +14846,10 @@ program2.command("update").description("Update the CLI to the latest version").a
14109
14846
  const { update: update2 } = await Promise.resolve().then(() => (init_update(), exports_update));
14110
14847
  await update2();
14111
14848
  });
14849
+ program2.command("init").description("Initialize chatroom integration in your project").option("--dir <path>", "Directory to initialize (default: current directory)").action(async (options) => {
14850
+ const { init: init2 } = await Promise.resolve().then(() => (init_init(), exports_init));
14851
+ await init2({ dir: options.dir });
14852
+ });
14112
14853
  program2.command("register-agent").description("Register agent type for a chatroom role").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Role to register as (e.g., builder, reviewer)").requiredOption("--type <type>", "Agent type: remote or custom").action(async (options) => {
14113
14854
  await maybeRequireAuth();
14114
14855
  if (options.type !== "remote" && options.type !== "custom") {
@@ -14121,10 +14862,10 @@ program2.command("register-agent").description("Register agent type for a chatro
14121
14862
  type: options.type
14122
14863
  });
14123
14864
  });
14124
- program2.command("wait-for-task").description("Join a chatroom and wait for tasks").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Role to join as (e.g., builder, reviewer)").action(async (options) => {
14865
+ program2.command("get-next-task").description("Join a chatroom and get the next task").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Role to join as (e.g., builder, reviewer)").action(async (options) => {
14125
14866
  await maybeRequireAuth();
14126
- const { waitForTask: waitForTask2 } = await Promise.resolve().then(() => (init_wait_for_task(), exports_wait_for_task));
14127
- await waitForTask2(options.chatroomId, {
14867
+ const { getNextTask: getNextTask2 } = await Promise.resolve().then(() => (init_get_next_task(), exports_get_next_task));
14868
+ await getNextTask2(options.chatroomId, {
14128
14869
  role: options.role
14129
14870
  });
14130
14871
  });
@@ -14177,13 +14918,6 @@ program2.command("task-started").description("Acknowledge a task and optionally
14177
14918
  noClassify: skipClassification
14178
14919
  });
14179
14920
  });
14180
- program2.command("task-complete").description("Complete the current task without handing off to another role").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").action(async (options) => {
14181
- await maybeRequireAuth();
14182
- const { taskComplete: taskComplete2 } = await Promise.resolve().then(() => (init_task_complete(), exports_task_complete));
14183
- await taskComplete2(options.chatroomId, {
14184
- role: options.role
14185
- });
14186
- });
14187
14921
  program2.command("handoff").description("Complete your task and hand off to the next role").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--next-role <nextRole>", "Role to hand off to").option("--attach-artifact <artifactId>", "Attach artifact to handoff (can be used multiple times)", (value, previous) => {
14188
14922
  return previous ? [...previous, value] : [value];
14189
14923
  }, []).action(async (options) => {
@@ -14292,11 +15026,6 @@ backlogCommand.command("score").description("Score a backlog task by complexity,
14292
15026
  const { scoreBacklog: scoreBacklog2 } = await Promise.resolve().then(() => (init_backlog(), exports_backlog));
14293
15027
  await scoreBacklog2(options.chatroomId, options);
14294
15028
  });
14295
- backlogCommand.command("reset-task").description("Reset a stuck in_progress task back to pending").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--task-id <taskId>", "Task ID to reset").action(async (options) => {
14296
- await maybeRequireAuth();
14297
- const { resetBacklog: resetBacklog2 } = await Promise.resolve().then(() => (init_backlog(), exports_backlog));
14298
- await resetBacklog2(options.chatroomId, options);
14299
- });
14300
15029
  backlogCommand.command("mark-for-review").description("Mark a backlog task as ready for user review (backlog → pending_user_review)").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--task-id <taskId>", "Task ID to mark for review").action(async (options) => {
14301
15030
  await maybeRequireAuth();
14302
15031
  const { markForReviewBacklog: markForReviewBacklog2 } = await Promise.resolve().then(() => (init_backlog(), exports_backlog));
@@ -14336,7 +15065,7 @@ contextCommand.command("read").description("Read context for your role (conversa
14336
15065
  const { readContext: readContext2 } = await Promise.resolve().then(() => (init_context(), exports_context));
14337
15066
  await readContext2(options.chatroomId, options);
14338
15067
  });
14339
- contextCommand.command("new").description("Create a new context and pin it for all agents").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (creator of the context)").option("--content <content>", "Context summary/description (alternative: provide via stdin/heredoc)").action(async (options) => {
15068
+ contextCommand.command("new").description("Create a new context and pin it for all agents").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (creator of the context)").option("--content <content>", "Context summary/description (alternative: provide via stdin/heredoc)").option("--trigger-message-id <messageId>", "Message ID that triggered this context (anchors the context window)").action(async (options) => {
14340
15069
  await maybeRequireAuth();
14341
15070
  let content;
14342
15071
  if (options.content && options.content.trim().length > 0) {
@@ -14401,6 +15130,11 @@ artifactCommand.command("view-many").description("View multiple artifacts").requ
14401
15130
  artifactIds: options.artifact || []
14402
15131
  });
14403
15132
  });
15133
+ program2.command("get-system-prompt").description("Fetch the system prompt for your role in a chatroom").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (e.g., planner, builder, reviewer)").action(async (options) => {
15134
+ await maybeRequireAuth();
15135
+ const { getSystemPrompt: getSystemPrompt2 } = await Promise.resolve().then(() => (init_get_system_prompt(), exports_get_system_prompt));
15136
+ await getSystemPrompt2(options.chatroomId, { role: options.role });
15137
+ });
14404
15138
  var machineCommand = program2.command("machine").description("Machine daemon management for remote agent control");
14405
15139
  var daemonCommand = machineCommand.command("daemon").description("Manage the machine daemon");
14406
15140
  daemonCommand.command("start").description("Start the machine daemon to listen for remote commands").action(async () => {
@@ -14418,7 +15152,22 @@ daemonCommand.command("status").description("Check if the machine daemon is runn
14418
15152
  });
14419
15153
  var opencodeCommand = program2.command("opencode").description("OpenCode integration harness");
14420
15154
  opencodeCommand.command("install").description("Install chatroom as an OpenCode harness").option("--force", "Overwrite existing harness installation").action(async (options) => {
14421
- const { installTool: installTool2 } = await Promise.resolve().then(() => exports_opencode_install);
15155
+ const { installTool: installTool2 } = await Promise.resolve().then(() => (init_opencode_install(), exports_opencode_install));
14422
15156
  await installTool2({ checkExisting: !options.force });
14423
15157
  });
15158
+ program2.hook("preAction", async (_thisCommand, actionCommand) => {
15159
+ const opts = actionCommand.opts();
15160
+ const chatroomId = opts["chatroomId"];
15161
+ const role = opts["role"];
15162
+ if (!chatroomId || !role)
15163
+ return;
15164
+ const { getSessionId: getSessionId2 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
15165
+ const { getConvexClient: getConvexClient2 } = await Promise.resolve().then(() => (init_client2(), exports_client));
15166
+ const { sendLifecycleHeartbeat: sendLifecycleHeartbeat2 } = await Promise.resolve().then(() => (init_lifecycle_heartbeat(), exports_lifecycle_heartbeat));
15167
+ const sessionId = getSessionId2();
15168
+ if (!sessionId)
15169
+ return;
15170
+ const client2 = await getConvexClient2();
15171
+ sendLifecycleHeartbeat2(client2, { sessionId, chatroomId, role, action: actionCommand.name() });
15172
+ });
14424
15173
  program2.parse();