chatroom-cli 1.12.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1082 -588
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2167,7 +2167,7 @@ function getVersion() {
2167
2167
  }
2168
2168
  var init_version = () => {};
2169
2169
 
2170
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/common/index.js
2170
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/common/index.js
2171
2171
  function parseArgs(args) {
2172
2172
  if (args === undefined) {
2173
2173
  return {};
@@ -2203,10 +2203,10 @@ function isSimpleObject(value) {
2203
2203
  return isObject && isSimple;
2204
2204
  }
2205
2205
 
2206
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/index.js
2207
- var version = "1.31.2";
2206
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/index.js
2207
+ var version = "1.34.0";
2208
2208
 
2209
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/base64.js
2209
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/base64.js
2210
2210
  var exports_base64 = {};
2211
2211
  __export(exports_base64, {
2212
2212
  toByteArray: () => toByteArray,
@@ -2306,7 +2306,7 @@ var init_base64 = __esm(() => {
2306
2306
  revLookup[95] = 63;
2307
2307
  });
2308
2308
 
2309
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/value.js
2309
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/value.js
2310
2310
  function isSpecial(n) {
2311
2311
  return Number.isNaN(n) || !Number.isFinite(n) || Object.is(n, -0);
2312
2312
  }
@@ -2547,7 +2547,7 @@ var init_value = __esm(() => {
2547
2547
  base64ToBigInt = DataView.prototype.getBigInt64 ? modernBase64ToBigInt : slowBase64ToBigInt;
2548
2548
  });
2549
2549
 
2550
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/errors.js
2550
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/errors.js
2551
2551
  var __defProp2, __defNormalProp = (obj, key, value) => (key in obj) ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value, __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value), _a, _b, IDENTIFYING_FIELD, ConvexError;
2552
2552
  var init_errors = __esm(() => {
2553
2553
  init_value();
@@ -2564,14 +2564,14 @@ var init_errors = __esm(() => {
2564
2564
  };
2565
2565
  });
2566
2566
 
2567
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/index.js
2567
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/index.js
2568
2568
  var init_values = __esm(() => {
2569
2569
  init_value();
2570
2570
  init_base64();
2571
2571
  init_errors();
2572
2572
  });
2573
2573
 
2574
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/logging.js
2574
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/logging.js
2575
2575
  function prefix_for_source(source) {
2576
2576
  switch (source) {
2577
2577
  case "query":
@@ -2691,7 +2691,7 @@ var init_logging = __esm(() => {
2691
2691
  __defProp3 = Object.defineProperty;
2692
2692
  });
2693
2693
 
2694
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/udf_path_utils.js
2694
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/udf_path_utils.js
2695
2695
  function canonicalizeUdfPath(udfPath) {
2696
2696
  const pieces = udfPath.split(":");
2697
2697
  let moduleName;
@@ -2731,7 +2731,7 @@ var init_udf_path_utils = __esm(() => {
2731
2731
  init_values();
2732
2732
  });
2733
2733
 
2734
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/local_state.js
2734
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/local_state.js
2735
2735
  class LocalSyncState {
2736
2736
  constructor() {
2737
2737
  __publicField3(this, "nextQueryId");
@@ -2922,7 +2922,7 @@ class LocalSyncState {
2922
2922
  queryJournal(queryToken) {
2923
2923
  return this.querySet.get(queryToken)?.journal;
2924
2924
  }
2925
- restart(oldRemoteQueryResults) {
2925
+ restart() {
2926
2926
  this.unpause();
2927
2927
  this.outstandingQueriesOlderThanRestart.clear();
2928
2928
  const modifications = [];
@@ -2936,9 +2936,7 @@ class LocalSyncState {
2936
2936
  componentPath: localQuery.componentPath
2937
2937
  };
2938
2938
  modifications.push(add);
2939
- if (!oldRemoteQueryResults.has(localQuery.id)) {
2940
- this.outstandingQueriesOlderThanRestart.add(localQuery.id);
2941
- }
2939
+ this.outstandingQueriesOlderThanRestart.add(localQuery.id);
2942
2940
  }
2943
2941
  this.querySetVersion = 1;
2944
2942
  const querySet = {
@@ -3022,7 +3020,7 @@ var init_local_state = __esm(() => {
3022
3020
  __defProp4 = Object.defineProperty;
3023
3021
  });
3024
3022
 
3025
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/request_manager.js
3023
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/request_manager.js
3026
3024
  class RequestManager {
3027
3025
  constructor(logger, markConnectionStateDirty) {
3028
3026
  this.logger = logger;
@@ -3207,13 +3205,13 @@ var init_request_manager = __esm(() => {
3207
3205
  __defProp5 = Object.defineProperty;
3208
3206
  });
3209
3207
 
3210
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/functionName.js
3208
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/functionName.js
3211
3209
  var functionName;
3212
3210
  var init_functionName = __esm(() => {
3213
3211
  functionName = Symbol.for("functionName");
3214
3212
  });
3215
3213
 
3216
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/components/paths.js
3214
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/components/paths.js
3217
3215
  function extractReferencePath(reference) {
3218
3216
  return reference[toReferencePath] ?? null;
3219
3217
  }
@@ -3245,7 +3243,7 @@ var init_paths = __esm(() => {
3245
3243
  toReferencePath = Symbol.for("toReferencePath");
3246
3244
  });
3247
3245
 
3248
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/api.js
3246
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/api.js
3249
3247
  function getFunctionName(functionReference) {
3250
3248
  const address = getFunctionAddress(functionReference);
3251
3249
  if (address.name === undefined) {
@@ -3298,7 +3296,7 @@ var init_api = __esm(() => {
3298
3296
  anyApi = createApi();
3299
3297
  });
3300
3298
 
3301
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/optimistic_updates_impl.js
3299
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/optimistic_updates_impl.js
3302
3300
  class OptimisticLocalStoreImpl {
3303
3301
  constructor(queryResults) {
3304
3302
  __publicField5(this, "queryResults");
@@ -3437,7 +3435,7 @@ var init_optimistic_updates_impl = __esm(() => {
3437
3435
  __defProp6 = Object.defineProperty;
3438
3436
  });
3439
3437
 
3440
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/vendor/long.js
3438
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/vendor/long.js
3441
3439
  class Long {
3442
3440
  constructor(low, high) {
3443
3441
  __publicField6(this, "low");
@@ -3515,7 +3513,7 @@ var init_long = __esm(() => {
3515
3513
  MAX_UNSIGNED_VALUE = new Long(4294967295 | 0, 4294967295 | 0);
3516
3514
  });
3517
3515
 
3518
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/remote_query_set.js
3516
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/remote_query_set.js
3519
3517
  class RemoteQuerySet {
3520
3518
  constructor(queryPath, logger) {
3521
3519
  __publicField7(this, "version");
@@ -3591,7 +3589,7 @@ var init_remote_query_set = __esm(() => {
3591
3589
  __defProp8 = Object.defineProperty;
3592
3590
  });
3593
3591
 
3594
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/protocol.js
3592
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/protocol.js
3595
3593
  function u64ToLong(encoded) {
3596
3594
  const integerBytes = exports_base64.toByteArray(encoded);
3597
3595
  return Long.fromBytesLE(Array.from(integerBytes));
@@ -3661,7 +3659,7 @@ var init_protocol = __esm(() => {
3661
3659
  init_long();
3662
3660
  });
3663
3661
 
3664
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/web_socket_manager.js
3662
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/web_socket_manager.js
3665
3663
  function monotonicMillis() {
3666
3664
  if (firstTime === undefined) {
3667
3665
  firstTime = Date.now();
@@ -3699,6 +3697,9 @@ class WebSocketManager {
3699
3697
  __publicField8(this, "retries");
3700
3698
  __publicField8(this, "serverInactivityThreshold");
3701
3699
  __publicField8(this, "reconnectDueToServerInactivityTimeout");
3700
+ __publicField8(this, "scheduledReconnect", null);
3701
+ __publicField8(this, "networkOnlineHandler", null);
3702
+ __publicField8(this, "pendingNetworkRecoveryInfo", null);
3702
3703
  __publicField8(this, "uri");
3703
3704
  __publicField8(this, "onOpen");
3704
3705
  __publicField8(this, "onResume");
@@ -3711,7 +3712,7 @@ class WebSocketManager {
3711
3712
  this.connectionCount = 0;
3712
3713
  this.lastCloseReason = "InitialConnect";
3713
3714
  this.defaultInitialBackoff = 1000;
3714
- this.maxBackoff = 16000;
3715
+ this.maxBackoff = 64000;
3715
3716
  this.retries = 0;
3716
3717
  this.serverInactivityThreshold = 60000;
3717
3718
  this.reconnectDueToServerInactivityTimeout = null;
@@ -3721,6 +3722,7 @@ class WebSocketManager {
3721
3722
  this.onMessage = callbacks.onMessage;
3722
3723
  this.onServerDisconnectError = callbacks.onServerDisconnectError;
3723
3724
  this.logger = logger;
3725
+ this.setupNetworkListener();
3724
3726
  this.connect();
3725
3727
  }
3726
3728
  setSocketState(state) {
@@ -3728,6 +3730,27 @@ class WebSocketManager {
3728
3730
  this._logVerbose(`socket state changed: ${this.socket.state}, paused: ${"paused" in this.socket ? this.socket.paused : undefined}`);
3729
3731
  this.markConnectionStateDirty();
3730
3732
  }
3733
+ setupNetworkListener() {
3734
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
3735
+ return;
3736
+ }
3737
+ if (this.networkOnlineHandler !== null) {
3738
+ return;
3739
+ }
3740
+ this.networkOnlineHandler = () => {
3741
+ this._logVerbose("network online event detected");
3742
+ this.tryReconnectImmediately();
3743
+ };
3744
+ window.addEventListener("online", this.networkOnlineHandler);
3745
+ this._logVerbose("network online event listener registered");
3746
+ }
3747
+ cleanupNetworkListener() {
3748
+ if (this.networkOnlineHandler && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
3749
+ window.removeEventListener("online", this.networkOnlineHandler);
3750
+ this.networkOnlineHandler = null;
3751
+ this._logVerbose("network online event listener removed");
3752
+ }
3753
+ }
3731
3754
  assembleTransition(chunk) {
3732
3755
  if (chunk.partNumber < 0 || chunk.partNumber >= chunk.totalParts || chunk.totalParts === 0 || this.transitionChunkBuffer && (this.transitionChunkBuffer.totalParts !== chunk.totalParts || this.transitionChunkBuffer.transitionId !== chunk.transitionId)) {
3733
3756
  this.transitionChunkBuffer = null;
@@ -3800,6 +3823,16 @@ class WebSocketManager {
3800
3823
  }
3801
3824
  this.connectionCount += 1;
3802
3825
  this.lastCloseReason = null;
3826
+ if (this.pendingNetworkRecoveryInfo !== null) {
3827
+ const { timeSavedMs } = this.pendingNetworkRecoveryInfo;
3828
+ this.pendingNetworkRecoveryInfo = null;
3829
+ this.sendMessage({
3830
+ type: "Event",
3831
+ eventType: "NetworkRecoveryReconnect",
3832
+ event: { timeSavedMs }
3833
+ });
3834
+ this.logger.log(`Network recovery reconnect saved ~${Math.round(timeSavedMs / 1000)}s of waiting`);
3835
+ }
3803
3836
  };
3804
3837
  ws.onerror = (error) => {
3805
3838
  this.transitionChunkBuffer = null;
@@ -3901,11 +3934,26 @@ class WebSocketManager {
3901
3934
  }, this.serverInactivityThreshold);
3902
3935
  }
3903
3936
  scheduleReconnect(reason) {
3937
+ if (this.scheduledReconnect) {
3938
+ clearTimeout(this.scheduledReconnect.timeout);
3939
+ this.scheduledReconnect = null;
3940
+ }
3904
3941
  this.socket = { state: "disconnected" };
3905
3942
  const backoff = this.nextBackoff(reason);
3906
3943
  this.markConnectionStateDirty();
3907
3944
  this.logger.log(`Attempting reconnect in ${Math.round(backoff)}ms`);
3908
- setTimeout(() => this.connect(), backoff);
3945
+ const scheduledAt = monotonicMillis();
3946
+ const timeoutId = setTimeout(() => {
3947
+ if (this.scheduledReconnect?.timeout === timeoutId) {
3948
+ this.scheduledReconnect = null;
3949
+ this.connect();
3950
+ }
3951
+ }, backoff);
3952
+ this.scheduledReconnect = {
3953
+ timeout: timeoutId,
3954
+ scheduledAt,
3955
+ backoffMs: backoff
3956
+ };
3909
3957
  }
3910
3958
  closeAndReconnect(closeReason) {
3911
3959
  this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);
@@ -3973,6 +4021,11 @@ class WebSocketManager {
3973
4021
  if (this.reconnectDueToServerInactivityTimeout) {
3974
4022
  clearTimeout(this.reconnectDueToServerInactivityTimeout);
3975
4023
  }
4024
+ if (this.scheduledReconnect) {
4025
+ clearTimeout(this.scheduledReconnect.timeout);
4026
+ this.scheduledReconnect = null;
4027
+ }
4028
+ this.cleanupNetworkListener();
3976
4029
  switch (this.socket.state) {
3977
4030
  case "terminated":
3978
4031
  case "stopped":
@@ -3997,6 +4050,7 @@ class WebSocketManager {
3997
4050
  case "stopped":
3998
4051
  case "disconnected":
3999
4052
  case "ready": {
4053
+ this.cleanupNetworkListener();
4000
4054
  const result = this.close();
4001
4055
  this.socket = { state: "stopped" };
4002
4056
  return result;
@@ -4021,6 +4075,7 @@ class WebSocketManager {
4021
4075
  this.socket;
4022
4076
  }
4023
4077
  }
4078
+ this.setupNetworkListener();
4024
4079
  this.connect();
4025
4080
  }
4026
4081
  pause() {
@@ -4040,6 +4095,25 @@ class WebSocketManager {
4040
4095
  }
4041
4096
  }
4042
4097
  }
4098
+ tryReconnectImmediately() {
4099
+ this._logVerbose("tryReconnectImmediately called");
4100
+ if (this.socket.state !== "disconnected") {
4101
+ this._logVerbose(`tryReconnectImmediately called but socket state is ${this.socket.state}, no action taken`);
4102
+ return;
4103
+ }
4104
+ let timeSavedMs = null;
4105
+ if (this.scheduledReconnect) {
4106
+ const elapsed = monotonicMillis() - this.scheduledReconnect.scheduledAt;
4107
+ timeSavedMs = Math.max(0, this.scheduledReconnect.backoffMs - elapsed);
4108
+ this._logVerbose(`would have waited ${Math.round(timeSavedMs)}ms more (backoff was ${Math.round(this.scheduledReconnect.backoffMs)}ms, elapsed ${Math.round(elapsed)}ms)`);
4109
+ clearTimeout(this.scheduledReconnect.timeout);
4110
+ this.scheduledReconnect = null;
4111
+ this._logVerbose("canceled scheduled reconnect");
4112
+ }
4113
+ this.logger.log("Network recovery detected, reconnecting immediately");
4114
+ this.pendingNetworkRecoveryInfo = timeSavedMs !== null ? { timeSavedMs } : null;
4115
+ this.connect();
4116
+ }
4043
4117
  resume() {
4044
4118
  switch (this.socket.state) {
4045
4119
  case "connecting":
@@ -4130,13 +4204,17 @@ var init_web_socket_manager = __esm(() => {
4130
4204
  VectorIndexesUnavailable: { timeout: 1000 },
4131
4205
  SearchIndexesUnavailable: { timeout: 1000 },
4132
4206
  TableSummariesUnavailable: { timeout: 1000 },
4207
+ ServiceUnavailable: { timeout: 3000 },
4208
+ WorkerOverloaded: { timeout: 3000 },
4209
+ IsolateNotClean: { timeout: 3000 },
4210
+ InitialPermitTimeoutError: { timeout: 3000 },
4133
4211
  VectorIndexTooLarge: { timeout: 3000 },
4134
4212
  SearchIndexTooLarge: { timeout: 3000 },
4135
4213
  TooManyWritesInTimePeriod: { timeout: 3000 }
4136
4214
  };
4137
4215
  });
4138
4216
 
4139
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/session.js
4217
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/session.js
4140
4218
  function newSessionId() {
4141
4219
  return uuidv4();
4142
4220
  }
@@ -4147,7 +4225,7 @@ function uuidv4() {
4147
4225
  });
4148
4226
  }
4149
4227
 
4150
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/vendor/jwt-decode/index.js
4228
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/vendor/jwt-decode/index.js
4151
4229
  function b64DecodeUnicode(str) {
4152
4230
  return decodeURIComponent(atob(str).replace(/(.)/g, (_m, p) => {
4153
4231
  let code2 = p.charCodeAt(0).toString(16).toUpperCase();
@@ -4206,7 +4284,7 @@ var init_jwt_decode = __esm(() => {
4206
4284
  InvalidTokenError.prototype.name = "InvalidTokenError";
4207
4285
  });
4208
4286
 
4209
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/authentication_manager.js
4287
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/authentication_manager.js
4210
4288
  class AuthenticationManager {
4211
4289
  constructor(syncState, callbacks, config) {
4212
4290
  __publicField9(this, "authState", { state: "noAuth" });
@@ -4470,7 +4548,7 @@ var init_authentication_manager = __esm(() => {
4470
4548
  MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1000;
4471
4549
  });
4472
4550
 
4473
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/metrics.js
4551
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/metrics.js
4474
4552
  function mark(name, sessionId) {
4475
4553
  const detail = { sessionId };
4476
4554
  if (typeof performance === "undefined" || !performance.mark)
@@ -4505,7 +4583,7 @@ var init_metrics = __esm(() => {
4505
4583
  ];
4506
4584
  });
4507
4585
 
4508
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/client.js
4586
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/client.js
4509
4587
  class BaseConvexClient {
4510
4588
  constructor(address, onTransition, options) {
4511
4589
  __publicField10(this, "address");
@@ -4549,7 +4627,7 @@ class BaseConvexClient {
4549
4627
  validateDeploymentUrl(address);
4550
4628
  }
4551
4629
  options = { ...options };
4552
- const authRefreshTokenLeewaySeconds = options.authRefreshTokenLeewaySeconds ?? 2;
4630
+ const authRefreshTokenLeewaySeconds = options.authRefreshTokenLeewaySeconds ?? 10;
4553
4631
  let webSocketConstructor = options.webSocketConstructor;
4554
4632
  if (!webSocketConstructor && typeof WebSocket === "undefined") {
4555
4633
  throw new Error("No WebSocket global variable defined! To use Convex in an environment without WebSocket try the HTTP client: https://docs.convex.dev/api/classes/browser.ConvexHttpClient");
@@ -4627,9 +4705,8 @@ class BaseConvexClient {
4627
4705
  sessionId: this._sessionId,
4628
4706
  maxObservedTimestamp: this.maxObservedTimestamp
4629
4707
  });
4630
- const oldRemoteQueryResults = new Set(this.remoteQuerySet.remoteQueryResults().keys());
4631
4708
  this.remoteQuerySet = new RemoteQuerySet((queryId) => this.state.queryPath(queryId), this.logger);
4632
- const [querySetModification, authModification] = this.state.restart(oldRemoteQueryResults);
4709
+ const [querySetModification, authModification] = this.state.restart();
4633
4710
  if (authModification) {
4634
4711
  this.webSocketManager.sendMessage(authModification);
4635
4712
  }
@@ -4708,7 +4785,7 @@ class BaseConvexClient {
4708
4785
  }
4709
4786
  }
4710
4787
  hasSyncedPastLastReconnect() {
4711
- const hasSyncedPastLastReconnect = this.requestManager.hasSyncedPastLastReconnect() || this.state.hasSyncedPastLastReconnect();
4788
+ const hasSyncedPastLastReconnect = this.requestManager.hasSyncedPastLastReconnect() && this.state.hasSyncedPastLastReconnect();
4712
4789
  return hasSyncedPastLastReconnect;
4713
4790
  }
4714
4791
  observedTimestamp(observedTs) {
@@ -5001,12 +5078,12 @@ var init_client = __esm(() => {
5001
5078
  __defProp11 = Object.defineProperty;
5002
5079
  });
5003
5080
 
5004
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/index.js
5081
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/index.js
5005
5082
  var init_browser = __esm(() => {
5006
5083
  init_client();
5007
5084
  });
5008
5085
 
5009
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/pagination.js
5086
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/pagination.js
5010
5087
  function asPaginationResult(value) {
5011
5088
  if (typeof value !== "object" || value === null || !Array.isArray(value.page) || typeof value.isDone !== "boolean" || typeof value.continueCursor !== "string") {
5012
5089
  throw new Error(`Not a valid paginated query result: ${value?.toString()}`);
@@ -5014,7 +5091,7 @@ function asPaginationResult(value) {
5014
5091
  return value;
5015
5092
  }
5016
5093
 
5017
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/sync/paginated_query_client.js
5094
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/sync/paginated_query_client.js
5018
5095
  class PaginatedQueryClient {
5019
5096
  constructor(client, onTransition) {
5020
5097
  this.client = client;
@@ -5293,7 +5370,7 @@ var init_paginated_query_client = __esm(() => {
5293
5370
  __defProp12 = Object.defineProperty;
5294
5371
  });
5295
5372
 
5296
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/simple_client.js
5373
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/simple_client.js
5297
5374
  function setDefaultWebSocketConstructor(ws) {
5298
5375
  defaultWebSocketConstructor = ws;
5299
5376
  }
@@ -5545,10 +5622,10 @@ var init_simple_client = __esm(() => {
5545
5622
  __defProp13 = Object.defineProperty;
5546
5623
  });
5547
5624
 
5548
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/simple_client-node.js
5625
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/simple_client-node.js
5549
5626
  import { createRequire as createRequire2 } from "module";
5550
5627
  import { resolve as nodePathResolve } from "path";
5551
- var __dirname = "/home/runner/work/chatroom/chatroom/node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser", require2, __create2, __defProp14, __getOwnPropDesc, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2 = (cb, mod) => function __require22() {
5628
+ var __dirname = "/home/runner/work/chatroom/chatroom/node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser", require2, __create2, __defProp14, __getOwnPropDesc, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2 = (cb, mod) => function __require22() {
5552
5629
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5553
5630
  }, __copyProps = (to, from, except, desc) => {
5554
5631
  if (from && typeof from === "object" || typeof from === "function") {
@@ -8630,7 +8707,7 @@ var init_simple_client_node = __esm(() => {
8630
8707
  setDefaultWebSocketConstructor(nodeWebSocket);
8631
8708
  });
8632
8709
 
8633
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/http_client.js
8710
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/http_client.js
8634
8711
  class ConvexHttpClient2 {
8635
8712
  constructor(address, options) {
8636
8713
  __publicField13(this, "address");
@@ -8950,7 +9027,7 @@ var init_http_client = __esm(() => {
8950
9027
  __defProp15 = Object.defineProperty;
8951
9028
  });
8952
9029
 
8953
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/browser/index-node.js
9030
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/browser/index-node.js
8954
9031
  var init_index_node = __esm(() => {
8955
9032
  init_simple_client_node();
8956
9033
  init_http_client();
@@ -9199,7 +9276,7 @@ var init_storage = __esm(() => {
9199
9276
  CHATROOM_DIR = join2(homedir(), ".chatroom");
9200
9277
  });
9201
9278
 
9202
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/validators.js
9279
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/validators.js
9203
9280
  function throwUndefinedValidatorError(context, fieldName) {
9204
9281
  const fieldInfo = fieldName !== undefined ? ` for field "${fieldName}"` : "";
9205
9282
  throw new Error(`A validator is undefined${fieldInfo} in ${context}. This is often caused by circular imports. See ${UNDEFINED_VALIDATOR_ERROR_URL} for details.`);
@@ -9534,7 +9611,7 @@ var init_validators = __esm(() => {
9534
9611
  };
9535
9612
  });
9536
9613
 
9537
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/values/validator.js
9614
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/values/validator.js
9538
9615
  var v2;
9539
9616
  var init_validator = __esm(() => {
9540
9617
  init_validators();
@@ -9603,7 +9680,7 @@ var init_validator = __esm(() => {
9603
9680
  };
9604
9681
  });
9605
9682
 
9606
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/pagination.js
9683
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/pagination.js
9607
9684
  var paginationOptsValidator;
9608
9685
  var init_pagination = __esm(() => {
9609
9686
  init_validator();
@@ -9617,10 +9694,10 @@ var init_pagination = __esm(() => {
9617
9694
  });
9618
9695
  });
9619
9696
 
9620
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/search_filter_builder.js
9697
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/search_filter_builder.js
9621
9698
  var init_search_filter_builder = () => {};
9622
9699
 
9623
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/components/index.js
9700
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/components/index.js
9624
9701
  function createChildComponents(root, pathParts) {
9625
9702
  const handler = {
9626
9703
  get(_, prop) {
@@ -9645,7 +9722,7 @@ var init_components = __esm(() => {
9645
9722
  init_paths();
9646
9723
  });
9647
9724
 
9648
- // ../../node_modules/.pnpm/convex@1.31.2_react@19.2.3/node_modules/convex/dist/esm/server/index.js
9725
+ // ../../node_modules/.pnpm/convex@1.34.0_react@19.2.3/node_modules/convex/dist/esm/server/index.js
9649
9726
  var init_server = __esm(() => {
9650
9727
  init_api();
9651
9728
  init_components();
@@ -10257,7 +10334,12 @@ ${options.prompt}` : options.prompt;
10257
10334
  cwd: options.workingDir,
10258
10335
  stdio: ["pipe", "pipe", "pipe"],
10259
10336
  shell: false,
10260
- detached: true
10337
+ detached: true,
10338
+ env: {
10339
+ ...process.env,
10340
+ GIT_EDITOR: "true",
10341
+ GIT_SEQUENCE_EDITOR: "true"
10342
+ }
10261
10343
  });
10262
10344
  childProcess.stdin?.write(fullPrompt);
10263
10345
  childProcess.stdin?.end();
@@ -10458,7 +10540,12 @@ var init_pi_agent_service = __esm(() => {
10458
10540
  cwd: options.workingDir,
10459
10541
  stdio: ["pipe", "pipe", "pipe"],
10460
10542
  shell: false,
10461
- detached: true
10543
+ detached: true,
10544
+ env: {
10545
+ ...process.env,
10546
+ GIT_EDITOR: "true",
10547
+ GIT_SEQUENCE_EDITOR: "true"
10548
+ }
10462
10549
  });
10463
10550
  childProcess.stdin?.write(JSON.stringify({ type: "prompt", message: prompt }) + `
10464
10551
  `);
@@ -10755,7 +10842,11 @@ ${options.prompt}` : options.prompt;
10755
10842
  stdio: ["pipe", "pipe", "pipe"],
10756
10843
  shell: false,
10757
10844
  detached: true,
10758
- env: { ...process.env }
10845
+ env: {
10846
+ ...process.env,
10847
+ GIT_EDITOR: "true",
10848
+ GIT_SEQUENCE_EDITOR: "true"
10849
+ }
10759
10850
  });
10760
10851
  childProcess.stdin?.write(fullPrompt);
10761
10852
  childProcess.stdin?.end();
@@ -10884,6 +10975,99 @@ var init_registry = __esm(() => {
10884
10975
  registry = new Map;
10885
10976
  });
10886
10977
 
10978
+ // src/infrastructure/services/remote-agents/claude/claude-code-agent-service.ts
10979
+ var CLAUDE_COMMAND = "claude", DEFAULT_MAX_TURNS = 200, ClaudeCodeAgentService;
10980
+ var init_claude_code_agent_service = __esm(() => {
10981
+ init_base_cli_agent_service();
10982
+ ClaudeCodeAgentService = class ClaudeCodeAgentService extends BaseCLIAgentService {
10983
+ id = "claude";
10984
+ displayName = "Claude Code";
10985
+ command = CLAUDE_COMMAND;
10986
+ constructor(deps) {
10987
+ super(deps);
10988
+ }
10989
+ isInstalled() {
10990
+ return this.checkInstalled(CLAUDE_COMMAND);
10991
+ }
10992
+ getVersion() {
10993
+ return this.checkVersion(CLAUDE_COMMAND);
10994
+ }
10995
+ async listModels() {
10996
+ return [
10997
+ "claude-sonnet-4-20250514",
10998
+ "claude-opus-4-20250514"
10999
+ ];
11000
+ }
11001
+ async spawn(options) {
11002
+ const { systemPrompt, model, prompt } = options;
11003
+ const args = ["-p"];
11004
+ args.push("--max-turns", String(DEFAULT_MAX_TURNS));
11005
+ if (model) {
11006
+ args.push("--model", model);
11007
+ }
11008
+ if (systemPrompt) {
11009
+ args.push("--system-prompt", systemPrompt);
11010
+ }
11011
+ args.push(prompt);
11012
+ const childProcess = this.deps.spawn(CLAUDE_COMMAND, args, {
11013
+ cwd: options.workingDir,
11014
+ stdio: ["pipe", "pipe", "pipe"],
11015
+ shell: false,
11016
+ detached: true,
11017
+ env: {
11018
+ ...process.env,
11019
+ GIT_EDITOR: "true",
11020
+ GIT_SEQUENCE_EDITOR: "true"
11021
+ }
11022
+ });
11023
+ await new Promise((resolve) => setTimeout(resolve, 500));
11024
+ if (childProcess.killed || childProcess.exitCode !== null) {
11025
+ throw new Error(`Agent process exited immediately (exit code: ${childProcess.exitCode})`);
11026
+ }
11027
+ if (!childProcess.pid) {
11028
+ throw new Error("Agent process started but has no PID");
11029
+ }
11030
+ const pid = childProcess.pid;
11031
+ const context = options.context;
11032
+ const entry = this.registerProcess(pid, context);
11033
+ const outputCallbacks = [];
11034
+ if (childProcess.stdout) {
11035
+ childProcess.stdout.pipe(process.stdout, { end: false });
11036
+ childProcess.stdout.on("data", () => {
11037
+ entry.lastOutputAt = Date.now();
11038
+ for (const cb of outputCallbacks)
11039
+ cb();
11040
+ });
11041
+ }
11042
+ if (childProcess.stderr) {
11043
+ childProcess.stderr.pipe(process.stderr, { end: false });
11044
+ childProcess.stderr.on("data", () => {
11045
+ entry.lastOutputAt = Date.now();
11046
+ for (const cb of outputCallbacks)
11047
+ cb();
11048
+ });
11049
+ }
11050
+ return {
11051
+ pid,
11052
+ onExit: (cb) => {
11053
+ childProcess.on("exit", (code2, signal) => {
11054
+ this.deleteProcess(pid);
11055
+ cb({ code: code2, signal, context });
11056
+ });
11057
+ },
11058
+ onOutput: (cb) => {
11059
+ outputCallbacks.push(cb);
11060
+ }
11061
+ };
11062
+ }
11063
+ };
11064
+ });
11065
+
11066
+ // src/infrastructure/services/remote-agents/claude/index.ts
11067
+ var init_claude = __esm(() => {
11068
+ init_claude_code_agent_service();
11069
+ });
11070
+
10887
11071
  // src/infrastructure/services/remote-agents/init-registry.ts
10888
11072
  function initHarnessRegistry() {
10889
11073
  if (initialized)
@@ -10891,10 +11075,12 @@ function initHarnessRegistry() {
10891
11075
  registerHarness(new OpenCodeAgentService);
10892
11076
  registerHarness(new PiAgentService);
10893
11077
  registerHarness(new CursorAgentService);
11078
+ registerHarness(new ClaudeCodeAgentService);
10894
11079
  initialized = true;
10895
11080
  }
10896
11081
  var initialized = false;
10897
11082
  var init_init_registry = __esm(() => {
11083
+ init_claude();
10898
11084
  init_cursor();
10899
11085
  init_opencode();
10900
11086
  init_pi();
@@ -11595,30 +11781,6 @@ var init_new_feature = () => {};
11595
11781
  var init_classification = __esm(() => {
11596
11782
  init_new_feature();
11597
11783
  });
11598
-
11599
- // ../../services/backend/prompts/cli/task-started/command.ts
11600
- function taskStartedCommand(params) {
11601
- const prefix = params.cliEnvPrefix || "";
11602
- const chatroomId = params.chatroomId || "<chatroom-id>";
11603
- const role = params.role || "<role>";
11604
- const taskId = params.taskId || "<task-id>";
11605
- const classification = params.classification || "<question|new_feature|follow_up>";
11606
- const baseCmd = `${prefix}chatroom task-started --chatroom-id="${chatroomId}" --role="${role}" --task-id="${taskId}" --origin-message-classification=${classification}`;
11607
- if (params.classification === "new_feature" || classification === "new_feature") {
11608
- const title = params.title || "[Feature title]";
11609
- const description = params.description || "[Feature description]";
11610
- const techSpecs = params.techSpecs || "[Technical specifications]";
11611
- return `${baseCmd} << 'EOF'
11612
- ---TITLE---
11613
- ${title}
11614
- ---DESCRIPTION---
11615
- ${description}
11616
- ---TECH_SPECS---
11617
- ${techSpecs}
11618
- EOF`;
11619
- }
11620
- return baseCmd;
11621
- }
11622
11784
  // ../../services/backend/prompts/cli/task-started/main-prompt.ts
11623
11785
  var init_main_prompt = () => {};
11624
11786
 
@@ -12085,10 +12247,10 @@ var init_get_next_task = __esm(() => {
12085
12247
  init_session();
12086
12248
  });
12087
12249
 
12088
- // src/commands/task-started/index.ts
12089
- var exports_task_started2 = {};
12090
- __export(exports_task_started2, {
12091
- taskStarted: () => taskStarted
12250
+ // src/commands/classify/index.ts
12251
+ var exports_classify = {};
12252
+ __export(exports_classify, {
12253
+ classify: () => classify
12092
12254
  });
12093
12255
  async function createDefaultDeps7() {
12094
12256
  const client2 = await getConvexClient();
@@ -12104,9 +12266,9 @@ async function createDefaultDeps7() {
12104
12266
  }
12105
12267
  };
12106
12268
  }
12107
- async function taskStarted(chatroomId, options, deps) {
12269
+ async function classify(chatroomId, options, deps) {
12108
12270
  const d = deps ?? await createDefaultDeps7();
12109
- const { role, originMessageClassification, rawStdin, taskId, noClassify } = options;
12271
+ const { role, originMessageClassification, rawStdin, taskId } = options;
12110
12272
  const convexUrl = d.session.getConvexUrl();
12111
12273
  const cliEnvPrefix = getCliEnvPrefix(convexUrl);
12112
12274
  const sessionId = d.session.getSessionId();
@@ -12121,7 +12283,7 @@ async function taskStarted(chatroomId, options, deps) {
12121
12283
  }
12122
12284
  console.error(`
12123
12285
  To use a different environment, set CHATROOM_CONVEX_URL:`);
12124
- console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom task-started ...`);
12286
+ console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom classify ...`);
12125
12287
  console.error(`
12126
12288
  Or to authenticate for the current environment:`);
12127
12289
  }
@@ -12132,28 +12294,24 @@ async function taskStarted(chatroomId, options, deps) {
12132
12294
  console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12133
12295
  process.exit(1);
12134
12296
  }
12135
- if (!noClassify && !originMessageClassification) {
12136
- console.error(`❌ Either --no-classify or --origin-message-classification is required`);
12137
- console.error("");
12138
- console.error(" For entry point roles (receiving user messages):");
12139
- console.error(` ${taskStartedCommand({
12140
- chatroomId,
12141
- role,
12142
- taskId: "<task-id>",
12143
- classification: "question",
12144
- cliEnvPrefix
12145
- })}`);
12146
- console.error("");
12147
- console.error(" For handoff recipients (receiving from other agents):");
12148
- console.error(` ${cliEnvPrefix}chatroom task-started --chatroom-id=${chatroomId} --role=${role} --task-id=<task-id> --no-classify`);
12297
+ const chatroom = await d.backend.query(api.chatrooms.get, {
12298
+ sessionId,
12299
+ chatroomId
12300
+ });
12301
+ if (!chatroom) {
12302
+ console.error(`❌ Chatroom not found: ${chatroomId}`);
12303
+ console.error(` Verify the chatroom ID is correct and you have access.`);
12149
12304
  process.exit(1);
12150
12305
  }
12151
- if (noClassify && originMessageClassification) {
12152
- console.error(`❌ Cannot use both --no-classify and --origin-message-classification`);
12153
- console.error(` Use --no-classify for handoffs, or --origin-message-classification for user messages`);
12306
+ const entryPoint = chatroom?.teamEntryPoint ?? chatroom?.teamRoles?.[0];
12307
+ if (entryPoint && role.toLowerCase() !== entryPoint.toLowerCase()) {
12308
+ console.error(`❌ \`classify\` is only available to the entry point role (${entryPoint}). Your role is ${role}.`);
12309
+ console.error("");
12310
+ console.error(" Entry point roles receive user messages and must classify them.");
12311
+ console.error(" Other roles receive handoffs — use `task read` to mark in_progress.");
12154
12312
  process.exit(1);
12155
12313
  }
12156
- if (!noClassify && originMessageClassification === "new_feature") {
12314
+ if (originMessageClassification === "new_feature") {
12157
12315
  if (!rawStdin || rawStdin.trim().length === 0) {
12158
12316
  console.error(`❌ new_feature classification requires stdin with feature metadata`);
12159
12317
  console.error(" Provide structured stdin with TITLE, DESCRIPTION, and TECH_SPECS");
@@ -12164,7 +12322,7 @@ Feature title
12164
12322
  ---DESCRIPTION---
12165
12323
  What this feature does
12166
12324
  ---TECH_SPECS---
12167
- How to implement it' | ${taskStartedCommand({
12325
+ How to implement it' | ${classifyCommand({
12168
12326
  chatroomId,
12169
12327
  role,
12170
12328
  taskId: "<task-id>",
@@ -12174,10 +12332,9 @@ How to implement it' | ${taskStartedCommand({
12174
12332
  process.exit(1);
12175
12333
  }
12176
12334
  }
12177
- let targetTask = null;
12178
12335
  if (!taskId) {
12179
- console.error(`❌ --task-id is required for task-started`);
12180
- console.error(` Usage: ${taskStartedCommand({
12336
+ console.error(`❌ --task-id is required for classify`);
12337
+ console.error(` Usage: ${classifyCommand({
12181
12338
  chatroomId: "<chatroomId>",
12182
12339
  role: "<role>",
12183
12340
  taskId: "<task-id>",
@@ -12186,7 +12343,7 @@ How to implement it' | ${taskStartedCommand({
12186
12343
  })}`);
12187
12344
  process.exit(1);
12188
12345
  }
12189
- targetTask = await d.backend.query(api.tasks.getTask, {
12346
+ const targetTask = await d.backend.query(api.tasks.getTask, {
12190
12347
  sessionId,
12191
12348
  chatroomId,
12192
12349
  taskId
@@ -12196,26 +12353,6 @@ How to implement it' | ${taskStartedCommand({
12196
12353
  console.error(` Verify the task ID is correct and you have access to this chatroom`);
12197
12354
  process.exit(1);
12198
12355
  }
12199
- try {
12200
- await d.backend.mutation(api.tasks.startTask, {
12201
- sessionId,
12202
- chatroomId,
12203
- role,
12204
- taskId
12205
- });
12206
- } catch (error) {
12207
- const err = error;
12208
- console.error(`❌ Failed to start task`);
12209
- console.error(` Error: ${err.message}`);
12210
- process.exit(1);
12211
- }
12212
- if (noClassify) {
12213
- console.log(`✅ Task started`);
12214
- console.log(` Task: ${targetTask.content}`);
12215
- console.log(`
12216
- \uD83D\uDCA1 Task is now in progress. Begin your work.`);
12217
- return;
12218
- }
12219
12356
  try {
12220
12357
  const result = await d.backend.mutation(api.messages.taskStarted, {
12221
12358
  sessionId,
@@ -12252,227 +12389,65 @@ How to implement it' | ${taskStartedCommand({
12252
12389
  process.exit(1);
12253
12390
  }
12254
12391
  }
12255
- var init_task_started2 = __esm(() => {
12392
+ var init_classify = __esm(() => {
12256
12393
  init_env();
12257
12394
  init_api3();
12258
12395
  init_storage();
12259
12396
  init_client2();
12260
12397
  });
12261
12398
 
12262
- // src/commands/classify/index.ts
12263
- var exports_classify = {};
12264
- __export(exports_classify, {
12265
- classify: () => classify
12399
+ // src/utils/serialization/decode/index.ts
12400
+ var exports_decode = {};
12401
+ __export(exports_decode, {
12402
+ formatDecodeError: () => formatDecodeError,
12403
+ detectDelimiterCollisions: () => detectDelimiterCollisions,
12404
+ decode: () => decode
12266
12405
  });
12267
- async function createDefaultDeps8() {
12268
- const client2 = await getConvexClient();
12269
- return {
12270
- backend: {
12271
- mutation: (endpoint, args) => client2.mutation(endpoint, args),
12272
- query: (endpoint, args) => client2.query(endpoint, args)
12273
- },
12274
- session: {
12275
- getSessionId,
12276
- getConvexUrl,
12277
- getOtherSessionUrls
12278
- }
12279
- };
12406
+ function decode(input, options = {}) {
12407
+ const { singleParam, expectedParams, requiredParams } = options;
12408
+ if (singleParam) {
12409
+ const trimmed = input.trim();
12410
+ const singleParamDelimiter = `---${singleParam.toUpperCase()}---`;
12411
+ const stripped = trimmed.startsWith(singleParamDelimiter) ? trimmed.slice(singleParamDelimiter.length).trim() : trimmed;
12412
+ return { [singleParam]: stripped };
12413
+ }
12414
+ return decodeMultiParam(input, expectedParams, requiredParams);
12280
12415
  }
12281
- async function classify(chatroomId, options, deps) {
12282
- const d = deps ?? await createDefaultDeps8();
12283
- const { role, originMessageClassification, rawStdin, taskId } = options;
12284
- const convexUrl = d.session.getConvexUrl();
12285
- const cliEnvPrefix = getCliEnvPrefix(convexUrl);
12286
- const sessionId = d.session.getSessionId();
12287
- if (!sessionId) {
12288
- const otherUrls = d.session.getOtherSessionUrls();
12289
- console.error(`❌ Not authenticated for: ${convexUrl}`);
12290
- if (otherUrls.length > 0) {
12291
- console.error(`
12292
- \uD83D\uDCA1 You have sessions for other environments:`);
12293
- for (const url of otherUrls) {
12294
- console.error(` • ${url}`);
12416
+ function decodeMultiParam(input, expectedParams, requiredParams) {
12417
+ const lines = input.split(`
12418
+ `);
12419
+ const result = {};
12420
+ const seenParams = new Set;
12421
+ const delimiterPattern = /^---([A-Z_]+)---$/;
12422
+ let currentParam = null;
12423
+ let currentContent = [];
12424
+ for (let i2 = 0;i2 < lines.length; i2++) {
12425
+ const line = lines[i2];
12426
+ const match = line.match(delimiterPattern);
12427
+ if (match) {
12428
+ const paramName = match[1];
12429
+ if (currentParam !== null) {
12430
+ const content = currentContent.join(`
12431
+ `).trim();
12432
+ result[currentParam] = content;
12295
12433
  }
12296
- console.error(`
12297
- To use a different environment, set CHATROOM_CONVEX_URL:`);
12298
- console.error(` CHATROOM_CONVEX_URL=${otherUrls[0]} chatroom classify ...`);
12299
- console.error(`
12300
- Or to authenticate for the current environment:`);
12301
- }
12302
- console.error(` chatroom auth login`);
12303
- process.exit(1);
12304
- }
12305
- if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
12306
- console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
12307
- process.exit(1);
12308
- }
12309
- const chatroom = await d.backend.query(api.chatrooms.get, {
12310
- sessionId,
12311
- chatroomId
12312
- });
12313
- if (!chatroom) {
12314
- console.error(`❌ Chatroom not found: ${chatroomId}`);
12315
- console.error(` Verify the chatroom ID is correct and you have access.`);
12316
- process.exit(1);
12317
- }
12318
- const entryPoint = chatroom?.teamEntryPoint ?? chatroom?.teamRoles?.[0];
12319
- if (entryPoint && role.toLowerCase() !== entryPoint.toLowerCase()) {
12320
- console.error(`❌ \`classify\` is only available to the entry point role (${entryPoint}). Your role is ${role}.`);
12321
- console.error("");
12322
- console.error(" Entry point roles receive user messages and must classify them.");
12323
- console.error(" Other roles receive handoffs — use `task read` to mark in_progress.");
12324
- process.exit(1);
12325
- }
12326
- if (originMessageClassification === "new_feature") {
12327
- if (!rawStdin || rawStdin.trim().length === 0) {
12328
- console.error(`❌ new_feature classification requires stdin with feature metadata`);
12329
- console.error(" Provide structured stdin with TITLE, DESCRIPTION, and TECH_SPECS");
12330
- console.error("");
12331
- console.error(" Example:");
12332
- console.error(` echo '---TITLE---
12333
- Feature title
12334
- ---DESCRIPTION---
12335
- What this feature does
12336
- ---TECH_SPECS---
12337
- How to implement it' | ${classifyCommand({
12338
- chatroomId,
12339
- role,
12340
- taskId: "<task-id>",
12341
- classification: "new_feature",
12342
- cliEnvPrefix
12343
- })}`);
12344
- process.exit(1);
12345
- }
12346
- }
12347
- if (!taskId) {
12348
- console.error(`❌ --task-id is required for classify`);
12349
- console.error(` Usage: ${classifyCommand({
12350
- chatroomId: "<chatroomId>",
12351
- role: "<role>",
12352
- taskId: "<task-id>",
12353
- classification: "question",
12354
- cliEnvPrefix
12355
- })}`);
12356
- process.exit(1);
12357
- }
12358
- const targetTask = await d.backend.query(api.tasks.getTask, {
12359
- sessionId,
12360
- chatroomId,
12361
- taskId
12362
- });
12363
- if (!targetTask) {
12364
- console.error(`❌ Task with ID "${taskId}" not found in this chatroom`);
12365
- console.error(` Verify the task ID is correct and you have access to this chatroom`);
12366
- process.exit(1);
12367
- }
12368
- try {
12369
- await d.backend.mutation(api.tasks.startTask, {
12370
- sessionId,
12371
- chatroomId,
12372
- role,
12373
- taskId
12374
- });
12375
- } catch (error) {
12376
- const err = error;
12377
- console.error(`❌ Failed to start task`);
12378
- console.error(` Error: ${err.message}`);
12379
- process.exit(1);
12380
- }
12381
- try {
12382
- const result = await d.backend.mutation(api.messages.taskStarted, {
12383
- sessionId,
12384
- chatroomId,
12385
- role,
12386
- taskId,
12387
- originMessageClassification,
12388
- convexUrl: d.session.getConvexUrl(),
12389
- ...rawStdin && { rawStdin }
12390
- });
12391
- console.log(`✅ Task acknowledged and classified`);
12392
- console.log(` Classification: ${originMessageClassification}`);
12393
- console.log(` Task: ${targetTask.content}`);
12394
- if (result.reminder) {
12395
- console.log(`
12396
- \uD83D\uDCA1 ${result.reminder}`);
12397
- }
12398
- } catch (error) {
12399
- const err = error;
12400
- console.error(`❌ Failed to acknowledge task`);
12401
- console.error(` Error: ${err.message}`);
12402
- if ("stack" in err && err.stack) {
12403
- const stackLines = err.stack.split(`
12404
- `).slice(0, 5);
12405
- console.error(` Stack trace:`);
12406
- stackLines.forEach((line) => console.error(` ${line}`));
12407
- }
12408
- if (typeof error === "object" && error !== null && "data" in error) {
12409
- const errData = error.data;
12410
- if (errData) {
12411
- console.error(` Server details:`, JSON.stringify(errData, null, 2));
12412
- }
12413
- }
12414
- process.exit(1);
12415
- }
12416
- }
12417
- var init_classify = __esm(() => {
12418
- init_env();
12419
- init_api3();
12420
- init_storage();
12421
- init_client2();
12422
- });
12423
-
12424
- // src/utils/serialization/decode/index.ts
12425
- var exports_decode = {};
12426
- __export(exports_decode, {
12427
- formatDecodeError: () => formatDecodeError,
12428
- detectDelimiterCollisions: () => detectDelimiterCollisions,
12429
- decode: () => decode
12430
- });
12431
- function decode(input, options = {}) {
12432
- const { singleParam, expectedParams, requiredParams } = options;
12433
- if (singleParam) {
12434
- const trimmed = input.trim();
12435
- const singleParamDelimiter = `---${singleParam.toUpperCase()}---`;
12436
- const stripped = trimmed.startsWith(singleParamDelimiter) ? trimmed.slice(singleParamDelimiter.length).trim() : trimmed;
12437
- return { [singleParam]: stripped };
12438
- }
12439
- return decodeMultiParam(input, expectedParams, requiredParams);
12440
- }
12441
- function decodeMultiParam(input, expectedParams, requiredParams) {
12442
- const lines = input.split(`
12443
- `);
12444
- const result = {};
12445
- const seenParams = new Set;
12446
- const delimiterPattern = /^---([A-Z_]+)---$/;
12447
- let currentParam = null;
12448
- let currentContent = [];
12449
- for (let i2 = 0;i2 < lines.length; i2++) {
12450
- const line = lines[i2];
12451
- const match = line.match(delimiterPattern);
12452
- if (match) {
12453
- const paramName = match[1];
12454
- if (currentParam !== null) {
12455
- const content = currentContent.join(`
12456
- `).trim();
12457
- result[currentParam] = content;
12458
- }
12459
- if (expectedParams && !expectedParams.includes(paramName)) {
12460
- throw createDecodeError("UNKNOWN_PARAM", `Unknown parameter '${paramName}'. Expected one of: ${expectedParams.join(", ")}`, i2 + 1, paramName);
12461
- }
12462
- if (seenParams.has(paramName)) {
12463
- throw createDecodeError("DUPLICATE_PARAM", `Duplicate parameter '${paramName}' found at line ${i2 + 1}`, i2 + 1, paramName);
12464
- }
12465
- seenParams.add(paramName);
12466
- currentParam = paramName;
12467
- currentContent = [];
12468
- } else {
12469
- if (currentParam === null) {
12470
- if (line.trim().length > 0) {
12471
- throw createDecodeError("INVALID_FORMAT", `Content found before first parameter delimiter at line ${i2 + 1}. Expected format: ---PARAM_NAME---`, i2 + 1);
12472
- }
12473
- continue;
12474
- }
12475
- currentContent.push(line);
12434
+ if (expectedParams && !expectedParams.includes(paramName)) {
12435
+ throw createDecodeError("UNKNOWN_PARAM", `Unknown parameter '${paramName}'. Expected one of: ${expectedParams.join(", ")}`, i2 + 1, paramName);
12436
+ }
12437
+ if (seenParams.has(paramName)) {
12438
+ throw createDecodeError("DUPLICATE_PARAM", `Duplicate parameter '${paramName}' found at line ${i2 + 1}`, i2 + 1, paramName);
12439
+ }
12440
+ seenParams.add(paramName);
12441
+ currentParam = paramName;
12442
+ currentContent = [];
12443
+ } else {
12444
+ if (currentParam === null) {
12445
+ if (line.trim().length > 0) {
12446
+ throw createDecodeError("INVALID_FORMAT", `Content found before first parameter delimiter at line ${i2 + 1}. Expected format: ---PARAM_NAME---`, i2 + 1);
12447
+ }
12448
+ continue;
12449
+ }
12450
+ currentContent.push(line);
12476
12451
  }
12477
12452
  }
12478
12453
  if (currentParam !== null) {
@@ -12561,7 +12536,7 @@ var exports_handoff = {};
12561
12536
  __export(exports_handoff, {
12562
12537
  handoff: () => handoff
12563
12538
  });
12564
- async function createDefaultDeps9() {
12539
+ async function createDefaultDeps8() {
12565
12540
  const client2 = await getConvexClient();
12566
12541
  return {
12567
12542
  backend: {
@@ -12576,7 +12551,7 @@ async function createDefaultDeps9() {
12576
12551
  };
12577
12552
  }
12578
12553
  async function handoff(chatroomId, options, deps) {
12579
- const d = deps ?? await createDefaultDeps9();
12554
+ const d = deps ?? await createDefaultDeps8();
12580
12555
  const { role, message, nextRole, attachedArtifactIds = [] } = options;
12581
12556
  const sessionId = d.session.getSessionId();
12582
12557
  if (!sessionId) {
@@ -12710,7 +12685,7 @@ var exports_report_progress = {};
12710
12685
  __export(exports_report_progress, {
12711
12686
  reportProgress: () => reportProgress
12712
12687
  });
12713
- async function createDefaultDeps10() {
12688
+ async function createDefaultDeps9() {
12714
12689
  const client2 = await getConvexClient();
12715
12690
  return {
12716
12691
  backend: {
@@ -12725,7 +12700,7 @@ async function createDefaultDeps10() {
12725
12700
  };
12726
12701
  }
12727
12702
  async function reportProgress(chatroomId, options, deps) {
12728
- const d = deps ?? await createDefaultDeps10();
12703
+ const d = deps ?? await createDefaultDeps9();
12729
12704
  const { role, message } = options;
12730
12705
  const sessionId = d.session.getSessionId();
12731
12706
  if (!sessionId) {
@@ -12824,7 +12799,7 @@ __export(exports_backlog, {
12824
12799
  });
12825
12800
  import { createHash } from "node:crypto";
12826
12801
  import * as nodePath from "node:path";
12827
- async function createDefaultDeps11() {
12802
+ async function createDefaultDeps10() {
12828
12803
  const client2 = await getConvexClient();
12829
12804
  const fs = await import("node:fs/promises");
12830
12805
  return {
@@ -12859,7 +12834,7 @@ function validateChatroomId(chatroomId) {
12859
12834
  }
12860
12835
  }
12861
12836
  async function listBacklog(chatroomId, options, deps) {
12862
- const d = deps ?? await createDefaultDeps11();
12837
+ const d = deps ?? await createDefaultDeps10();
12863
12838
  const sessionId = requireAuth2(d);
12864
12839
  validateChatroomId(chatroomId);
12865
12840
  const limit = options.limit ?? 100;
@@ -12918,7 +12893,7 @@ async function listBacklog(chatroomId, options, deps) {
12918
12893
  }
12919
12894
  }
12920
12895
  async function addBacklog(chatroomId, options, deps) {
12921
- const d = deps ?? await createDefaultDeps11();
12896
+ const d = deps ?? await createDefaultDeps10();
12922
12897
  const sessionId = requireAuth2(d);
12923
12898
  validateChatroomId(chatroomId);
12924
12899
  if (!options.content || options.content.trim().length === 0) {
@@ -12945,7 +12920,7 @@ async function addBacklog(chatroomId, options, deps) {
12945
12920
  }
12946
12921
  }
12947
12922
  async function completeBacklog(chatroomId, options, deps) {
12948
- const d = deps ?? await createDefaultDeps11();
12923
+ const d = deps ?? await createDefaultDeps10();
12949
12924
  const sessionId = requireAuth2(d);
12950
12925
  validateChatroomId(chatroomId);
12951
12926
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -12979,7 +12954,7 @@ async function completeBacklog(chatroomId, options, deps) {
12979
12954
  }
12980
12955
  }
12981
12956
  async function reopenBacklog(chatroomId, options, deps) {
12982
- const d = deps ?? await createDefaultDeps11();
12957
+ const d = deps ?? await createDefaultDeps10();
12983
12958
  const sessionId = requireAuth2(d);
12984
12959
  validateChatroomId(chatroomId);
12985
12960
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -13006,7 +12981,7 @@ async function reopenBacklog(chatroomId, options, deps) {
13006
12981
  }
13007
12982
  }
13008
12983
  async function patchBacklog(chatroomId, options, deps) {
13009
- const d = deps ?? await createDefaultDeps11();
12984
+ const d = deps ?? await createDefaultDeps10();
13010
12985
  const sessionId = requireAuth2(d);
13011
12986
  validateChatroomId(chatroomId);
13012
12987
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -13068,7 +13043,7 @@ async function patchBacklog(chatroomId, options, deps) {
13068
13043
  }
13069
13044
  }
13070
13045
  async function scoreBacklog(chatroomId, options, deps) {
13071
- const d = deps ?? await createDefaultDeps11();
13046
+ const d = deps ?? await createDefaultDeps10();
13072
13047
  const sessionId = requireAuth2(d);
13073
13048
  validateChatroomId(chatroomId);
13074
13049
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -13131,7 +13106,7 @@ async function scoreBacklog(chatroomId, options, deps) {
13131
13106
  }
13132
13107
  }
13133
13108
  async function markForReviewBacklog(chatroomId, options, deps) {
13134
- const d = deps ?? await createDefaultDeps11();
13109
+ const d = deps ?? await createDefaultDeps10();
13135
13110
  const sessionId = requireAuth2(d);
13136
13111
  validateChatroomId(chatroomId);
13137
13112
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -13158,7 +13133,7 @@ async function markForReviewBacklog(chatroomId, options, deps) {
13158
13133
  }
13159
13134
  }
13160
13135
  async function historyBacklog(chatroomId, options, deps) {
13161
- const d = deps ?? await createDefaultDeps11();
13136
+ const d = deps ?? await createDefaultDeps10();
13162
13137
  const sessionId = requireAuth2(d);
13163
13138
  validateChatroomId(chatroomId);
13164
13139
  const now = Date.now();
@@ -13253,7 +13228,7 @@ async function historyBacklog(chatroomId, options, deps) {
13253
13228
  }
13254
13229
  }
13255
13230
  async function closeBacklog(chatroomId, options, deps) {
13256
- const d = deps ?? await createDefaultDeps11();
13231
+ const d = deps ?? await createDefaultDeps10();
13257
13232
  const sessionId = requireAuth2(d);
13258
13233
  validateChatroomId(chatroomId);
13259
13234
  if (!options.backlogItemId || options.backlogItemId.trim().length === 0) {
@@ -13279,176 +13254,600 @@ async function closeBacklog(chatroomId, options, deps) {
13279
13254
  console.log(` Reason: ${options.reason}`);
13280
13255
  console.log("");
13281
13256
  } catch (error) {
13282
- console.error(`❌ Failed to close backlog item: ${error.message}`);
13257
+ console.error(`❌ Failed to close backlog item: ${error.message}`);
13258
+ process.exit(1);
13259
+ return;
13260
+ }
13261
+ }
13262
+ function getStatusEmoji(status) {
13263
+ switch (status) {
13264
+ case "pending":
13265
+ return "\uD83D\uDFE2";
13266
+ case "acknowledged":
13267
+ return "\uD83D\uDCEC";
13268
+ case "in_progress":
13269
+ return "\uD83D\uDD35";
13270
+ case "backlog":
13271
+ return "⚪";
13272
+ case "completed":
13273
+ return "✅";
13274
+ case "pending_user_review":
13275
+ return "\uD83D\uDC40";
13276
+ case "closed":
13277
+ return "\uD83D\uDD12";
13278
+ default:
13279
+ return "⚫";
13280
+ }
13281
+ }
13282
+ function computeContentHash(content) {
13283
+ return createHash("sha256").update(content).digest("hex");
13284
+ }
13285
+ async function exportBacklog(chatroomId, options, deps) {
13286
+ const d = deps ?? await createDefaultDeps10();
13287
+ const sessionId = requireAuth2(d);
13288
+ validateChatroomId(chatroomId);
13289
+ if (!d.fs) {
13290
+ console.error("❌ File system operations not available");
13291
+ process.exit(1);
13292
+ return;
13293
+ }
13294
+ try {
13295
+ const backlogItems = await d.backend.query(api.backlog.listBacklogItems, {
13296
+ sessionId,
13297
+ chatroomId,
13298
+ statusFilter: "backlog"
13299
+ });
13300
+ const exportData = {
13301
+ exportedAt: Date.now(),
13302
+ chatroomId,
13303
+ items: backlogItems.map((item) => {
13304
+ const exportItem = {
13305
+ contentHash: computeContentHash(item.content),
13306
+ content: item.content,
13307
+ status: item.status,
13308
+ createdBy: item.createdBy ?? "unknown",
13309
+ createdAt: item.createdAt
13310
+ };
13311
+ if (item.complexity)
13312
+ exportItem.complexity = item.complexity;
13313
+ if (item.value)
13314
+ exportItem.value = item.value;
13315
+ if (item.priority !== undefined)
13316
+ exportItem.priority = item.priority;
13317
+ return exportItem;
13318
+ })
13319
+ };
13320
+ const exportDir = options.path ?? nodePath.join(process.cwd(), DEFAULT_EXPORT_DIR);
13321
+ await d.fs.mkdir(exportDir, { recursive: true });
13322
+ const filePath = nodePath.join(exportDir, BACKLOG_EXPORT_FILENAME);
13323
+ await d.fs.writeFile(filePath, JSON.stringify(exportData, null, 2));
13324
+ console.log("");
13325
+ console.log(`✅ Exported ${exportData.items.length} backlog item(s)`);
13326
+ console.log(` File: ${filePath}`);
13327
+ console.log("");
13328
+ } catch (error) {
13329
+ console.error(`❌ Failed to export backlog items: ${error.message}`);
13330
+ process.exit(1);
13331
+ return;
13332
+ }
13333
+ }
13334
+ async function importBacklog(chatroomId, options, deps) {
13335
+ const d = deps ?? await createDefaultDeps10();
13336
+ const sessionId = requireAuth2(d);
13337
+ validateChatroomId(chatroomId);
13338
+ if (!d.fs) {
13339
+ console.error("❌ File system operations not available");
13340
+ process.exit(1);
13341
+ return;
13342
+ }
13343
+ try {
13344
+ const importDir = options.path ?? nodePath.join(process.cwd(), DEFAULT_EXPORT_DIR);
13345
+ const filePath = nodePath.join(importDir, BACKLOG_EXPORT_FILENAME);
13346
+ const raw = await d.fs.readFile(filePath, "utf-8");
13347
+ const exportData = JSON.parse(raw);
13348
+ const ageMs = Date.now() - exportData.exportedAt;
13349
+ if (ageMs > STALENESS_THRESHOLD_MS) {
13350
+ const ageDays = Math.floor(ageMs / 86400000);
13351
+ console.log(`⚠️ This export is ${ageDays} days old and may be stale.`);
13352
+ }
13353
+ const existingItems = await d.backend.query(api.backlog.listBacklogItems, {
13354
+ sessionId,
13355
+ chatroomId,
13356
+ statusFilter: "backlog"
13357
+ });
13358
+ const existingHashes = new Set(existingItems.map((item) => computeContentHash(item.content)));
13359
+ let imported = 0;
13360
+ let skipped = 0;
13361
+ for (const item of exportData.items) {
13362
+ const hash = computeContentHash(item.content);
13363
+ if (existingHashes.has(hash)) {
13364
+ skipped++;
13365
+ continue;
13366
+ }
13367
+ await d.backend.mutation(api.backlog.createBacklogItem, {
13368
+ sessionId,
13369
+ chatroomId,
13370
+ content: item.content,
13371
+ createdBy: item.createdBy,
13372
+ priority: item.priority,
13373
+ complexity: item.complexity,
13374
+ value: item.value
13375
+ });
13376
+ existingHashes.add(hash);
13377
+ imported++;
13378
+ }
13379
+ console.log("");
13380
+ console.log(`✅ Import complete`);
13381
+ console.log(` Total items in file: ${exportData.items.length}`);
13382
+ console.log(` Imported: ${imported}`);
13383
+ console.log(` Skipped (duplicate): ${skipped}`);
13384
+ console.log("");
13385
+ } catch (error) {
13386
+ console.error(`❌ Failed to import backlog items: ${error.message}`);
13387
+ process.exit(1);
13388
+ return;
13389
+ }
13390
+ }
13391
+ var BACKLOG_EXPORT_FILENAME = "backlog-export.json", STALENESS_THRESHOLD_MS, DEFAULT_EXPORT_DIR = ".chatroom/exports";
13392
+ var init_backlog = __esm(() => {
13393
+ init_api3();
13394
+ init_storage();
13395
+ init_client2();
13396
+ STALENESS_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
13397
+ });
13398
+
13399
+ // src/utils/file-content.ts
13400
+ var exports_file_content = {};
13401
+ __export(exports_file_content, {
13402
+ resolveContent: () => resolveContent,
13403
+ readFileContent: () => readFileContent
13404
+ });
13405
+ import { readFileSync as readFileSync5 } from "fs";
13406
+ import { resolve } from "path";
13407
+ function readFileContent(filePath, optionName) {
13408
+ const absolutePath = resolve(process.cwd(), filePath);
13409
+ try {
13410
+ return readFileSync5(absolutePath, "utf-8");
13411
+ } catch (err) {
13412
+ const nodeErr = err;
13413
+ throw new Error(`Cannot read file for --${optionName}: ${absolutePath}
13414
+ ` + `Reason: ${nodeErr.message}`);
13415
+ }
13416
+ }
13417
+ function resolveContent(content, filePath, optionName) {
13418
+ if (content && filePath) {
13419
+ throw new Error(`Cannot specify both --${optionName} and --${optionName}-file`);
13420
+ }
13421
+ if (filePath) {
13422
+ return readFileContent(filePath, `${optionName}-file`);
13423
+ }
13424
+ return content;
13425
+ }
13426
+ var init_file_content = () => {};
13427
+
13428
+ // src/commands/workflow/index.ts
13429
+ var exports_workflow = {};
13430
+ __export(exports_workflow, {
13431
+ specifyWorkflowStep: () => specifyWorkflowStep,
13432
+ parseSections: () => parseSections,
13433
+ getWorkflowStatus: () => getWorkflowStatus,
13434
+ exitWorkflow: () => exitWorkflow,
13435
+ executeWorkflow: () => executeWorkflow,
13436
+ createWorkflow: () => createWorkflow,
13437
+ completeStep: () => completeStep,
13438
+ cancelStep: () => cancelStep
13439
+ });
13440
+ async function createDefaultDeps11() {
13441
+ const client2 = await getConvexClient();
13442
+ return {
13443
+ backend: {
13444
+ mutation: (endpoint, args) => client2.mutation(endpoint, args),
13445
+ query: (endpoint, args) => client2.query(endpoint, args)
13446
+ },
13447
+ session: {
13448
+ getSessionId,
13449
+ getConvexUrl,
13450
+ getOtherSessionUrls
13451
+ }
13452
+ };
13453
+ }
13454
+ function requireAuth3(d) {
13455
+ const sessionId = d.session.getSessionId();
13456
+ if (!sessionId) {
13457
+ console.error(`❌ Not authenticated. Please run: chatroom auth login`);
13458
+ process.exit(1);
13459
+ }
13460
+ return sessionId;
13461
+ }
13462
+ function validateChatroomId2(chatroomId) {
13463
+ if (!chatroomId || typeof chatroomId !== "string" || chatroomId.length < 20 || chatroomId.length > 40) {
13464
+ console.error(`❌ Invalid chatroom ID format: ID must be 20-40 characters (got ${chatroomId?.length || 0})`);
13465
+ process.exit(1);
13466
+ }
13467
+ }
13468
+ function parseSections(input, requiredSections, optionalSections) {
13469
+ const allSections = [...requiredSections, ...optionalSections];
13470
+ const result = new Map;
13471
+ const markerPattern = allSections.map((s) => `---${s}---`).join("|");
13472
+ const regex = new RegExp(`(${markerPattern})`, "g");
13473
+ const markers = [];
13474
+ let match;
13475
+ while ((match = regex.exec(input)) !== null) {
13476
+ const sectionName = match[1].replace(/^---/, "").replace(/---$/, "");
13477
+ markers.push({ section: sectionName, index: match.index + match[0].length, matchStart: match.index });
13478
+ }
13479
+ for (let i2 = 0;i2 < markers.length; i2++) {
13480
+ const start = markers[i2].index;
13481
+ const end = i2 + 1 < markers.length ? markers[i2 + 1].matchStart : input.length;
13482
+ const content = input.substring(start, end).trim();
13483
+ result.set(markers[i2].section, content);
13484
+ }
13485
+ for (const section of requiredSections) {
13486
+ if (!result.has(section) || !result.get(section)) {
13487
+ console.error(`❌ Missing required section: ---${section}---`);
13488
+ console.error("");
13489
+ console.error("Expected format:");
13490
+ for (const s of allSections) {
13491
+ const isRequired = requiredSections.includes(s);
13492
+ console.error(` ---${s}---`);
13493
+ console.error(` [${s.toLowerCase()} content here${isRequired ? " (required)" : " (optional)"}]`);
13494
+ }
13495
+ process.exit(1);
13496
+ }
13497
+ }
13498
+ return result;
13499
+ }
13500
+ function getStepStatusEmoji(status) {
13501
+ switch (status) {
13502
+ case "pending":
13503
+ return "⏳";
13504
+ case "in_progress":
13505
+ return "\uD83D\uDD35";
13506
+ case "completed":
13507
+ return "✅";
13508
+ case "cancelled":
13509
+ return "❌";
13510
+ default:
13511
+ return "⚫";
13512
+ }
13513
+ }
13514
+ function getWorkflowStatusEmoji(status) {
13515
+ switch (status) {
13516
+ case "draft":
13517
+ return "\uD83D\uDCDD";
13518
+ case "active":
13519
+ return "▶️";
13520
+ case "completed":
13521
+ return "✅";
13522
+ case "cancelled":
13523
+ return "❌";
13524
+ default:
13525
+ return "⚫";
13526
+ }
13527
+ }
13528
+ async function createWorkflow(chatroomId, options, deps) {
13529
+ const d = deps ?? await createDefaultDeps11();
13530
+ const sessionId = requireAuth3(d);
13531
+ validateChatroomId2(chatroomId);
13532
+ let stepsData;
13533
+ try {
13534
+ stepsData = JSON.parse(options.stdinContent);
13535
+ } catch {
13536
+ console.error("❌ Invalid JSON input. Expected format:");
13537
+ console.error(' { "steps": [{ "stepKey": "...", "description": "...", "dependsOn": [...], "order": N }] }');
13538
+ process.exit(1);
13539
+ return;
13540
+ }
13541
+ if (!stepsData.steps || !Array.isArray(stepsData.steps)) {
13542
+ console.error('❌ JSON must contain a "steps" array');
13543
+ process.exit(1);
13544
+ return;
13545
+ }
13546
+ if (stepsData.steps.length === 0) {
13547
+ console.error("❌ Workflow must have at least one step");
13548
+ process.exit(1);
13549
+ return;
13550
+ }
13551
+ const ALLOWED_STEP_FIELDS = new Set(["stepKey", "description", "dependsOn", "order"]);
13552
+ for (let i2 = 0;i2 < stepsData.steps.length; i2++) {
13553
+ const step = stepsData.steps[i2];
13554
+ const stepLabel = step.stepKey ? `"${step.stepKey}"` : `at index ${i2}`;
13555
+ if (!step.stepKey || typeof step.stepKey !== "string") {
13556
+ console.error(`❌ Step ${stepLabel} must have a "stepKey" (string)`);
13557
+ console.error(" All steps require: stepKey, description, dependsOn, order");
13558
+ process.exit(1);
13559
+ return;
13560
+ }
13561
+ if (!step.description || typeof step.description !== "string") {
13562
+ console.error(`❌ Step ${stepLabel} must have a "description" (string)`);
13563
+ console.error(" All steps require: stepKey, description, dependsOn, order");
13564
+ process.exit(1);
13565
+ return;
13566
+ }
13567
+ if (!Array.isArray(step.dependsOn)) {
13568
+ console.error(`❌ Step ${stepLabel} must have a "dependsOn" (array of strings)`);
13569
+ console.error(" All steps require: stepKey, description, dependsOn, order");
13570
+ process.exit(1);
13571
+ return;
13572
+ }
13573
+ if (typeof step.order !== "number") {
13574
+ console.error(`❌ Step ${stepLabel} must have an "order" (number)`);
13575
+ console.error(" All steps require: stepKey, description, dependsOn, order");
13576
+ process.exit(1);
13577
+ return;
13578
+ }
13579
+ const extraFields = Object.keys(step).filter((k) => !ALLOWED_STEP_FIELDS.has(k));
13580
+ if (extraFields.length > 0) {
13581
+ console.error(`⚠️ Stripped unknown fields from step "${step.stepKey}": ${extraFields.join(", ")}`);
13582
+ }
13583
+ }
13584
+ const cleanSteps = stepsData.steps.map((step) => ({
13585
+ stepKey: step.stepKey,
13586
+ description: step.description,
13587
+ dependsOn: step.dependsOn,
13588
+ order: step.order
13589
+ }));
13590
+ try {
13591
+ const result = await d.backend.mutation(api.workflows.createWorkflow, {
13592
+ sessionId,
13593
+ chatroomId,
13594
+ workflowKey: options.workflowKey,
13595
+ steps: cleanSteps,
13596
+ createdBy: options.role
13597
+ });
13598
+ console.log("");
13599
+ console.log("✅ Workflow created");
13600
+ console.log(` Key: ${options.workflowKey}`);
13601
+ console.log(` Workflow ID: ${result.workflowId}`);
13602
+ console.log(` Steps: ${cleanSteps.length}`);
13603
+ console.log(` Status: draft`);
13604
+ console.log("");
13605
+ } catch (error) {
13606
+ console.error(`❌ Failed to create workflow: ${error.message}`);
13607
+ process.exit(1);
13608
+ return;
13609
+ }
13610
+ }
13611
+ async function specifyWorkflowStep(chatroomId, options, deps) {
13612
+ const d = deps ?? await createDefaultDeps11();
13613
+ const sessionId = requireAuth3(d);
13614
+ validateChatroomId2(chatroomId);
13615
+ const sections = parseSections(options.stdinContent, ["GOAL", "REQUIREMENTS"], ["WARNINGS"]);
13616
+ const goal = sections.get("GOAL");
13617
+ const requirements = sections.get("REQUIREMENTS");
13618
+ const warnings = sections.get("WARNINGS") || undefined;
13619
+ try {
13620
+ await d.backend.mutation(api.workflows.specifyStep, {
13621
+ sessionId,
13622
+ chatroomId,
13623
+ workflowKey: options.workflowKey,
13624
+ stepKey: options.stepKey,
13625
+ assigneeRole: options.assigneeRole,
13626
+ goal,
13627
+ requirements,
13628
+ warnings
13629
+ });
13630
+ console.log("");
13631
+ console.log("✅ Step specified");
13632
+ console.log(` Workflow: ${options.workflowKey}`);
13633
+ console.log(` Step: ${options.stepKey}`);
13634
+ console.log(` Assignee: ${options.assigneeRole}`);
13635
+ console.log("");
13636
+ } catch (error) {
13637
+ console.error(`❌ Failed to specify step: ${error.message}`);
13638
+ process.exit(1);
13639
+ return;
13640
+ }
13641
+ }
13642
+ async function executeWorkflow(chatroomId, options, deps) {
13643
+ const d = deps ?? await createDefaultDeps11();
13644
+ const sessionId = requireAuth3(d);
13645
+ validateChatroomId2(chatroomId);
13646
+ try {
13647
+ await d.backend.mutation(api.workflows.executeWorkflow, {
13648
+ sessionId,
13649
+ chatroomId,
13650
+ workflowKey: options.workflowKey
13651
+ });
13652
+ console.log("");
13653
+ console.log("✅ Workflow activated");
13654
+ console.log(` Key: ${options.workflowKey}`);
13655
+ console.log(` Status: active`);
13656
+ console.log("");
13657
+ console.log("\uD83D\uDCA1 Root steps (no dependencies) are now in_progress.");
13658
+ console.log("");
13659
+ } catch (error) {
13660
+ console.error(`❌ Failed to execute workflow: ${error.message}`);
13661
+ process.exit(1);
13662
+ return;
13663
+ }
13664
+ }
13665
+ async function getWorkflowStatus(chatroomId, options, deps) {
13666
+ const d = deps ?? await createDefaultDeps11();
13667
+ const sessionId = requireAuth3(d);
13668
+ validateChatroomId2(chatroomId);
13669
+ try {
13670
+ const result = await d.backend.query(api.workflows.getWorkflowStatus, {
13671
+ sessionId,
13672
+ chatroomId,
13673
+ workflowKey: options.workflowKey
13674
+ });
13675
+ const wf = result.workflow;
13676
+ console.log("");
13677
+ console.log("══════════════════════════════════════════════════");
13678
+ console.log(`${getWorkflowStatusEmoji(wf.status)} WORKFLOW: ${wf.workflowKey}`);
13679
+ console.log("══════════════════════════════════════════════════");
13680
+ console.log(`Status: ${wf.status.toUpperCase()}`);
13681
+ console.log(`Created by: ${wf.createdBy}`);
13682
+ const createdDate = new Date(wf.createdAt).toLocaleString("en-US", {
13683
+ month: "short",
13684
+ day: "numeric",
13685
+ hour: "2-digit",
13686
+ minute: "2-digit",
13687
+ hour12: false
13688
+ });
13689
+ console.log(`Created: ${createdDate}`);
13690
+ if (wf.completedAt) {
13691
+ const completedDate = new Date(wf.completedAt).toLocaleString("en-US", {
13692
+ month: "short",
13693
+ day: "numeric",
13694
+ hour: "2-digit",
13695
+ minute: "2-digit",
13696
+ hour12: false
13697
+ });
13698
+ console.log(`Completed: ${completedDate}`);
13699
+ }
13700
+ if (wf.cancelledAt) {
13701
+ const cancelledDate = new Date(wf.cancelledAt).toLocaleString("en-US", {
13702
+ month: "short",
13703
+ day: "numeric",
13704
+ hour: "2-digit",
13705
+ minute: "2-digit",
13706
+ hour12: false
13707
+ });
13708
+ console.log(`Cancelled: ${cancelledDate}`);
13709
+ if (wf.cancelReason) {
13710
+ console.log(`Reason: ${wf.cancelReason}`);
13711
+ }
13712
+ }
13713
+ console.log("");
13714
+ console.log("──────────────────────────────────────────────────");
13715
+ console.log("\uD83D\uDCCB STEPS");
13716
+ console.log("──────────────────────────────────────────────────");
13717
+ if (result.steps.length === 0) {
13718
+ console.log("No steps.");
13719
+ } else {
13720
+ for (const step of result.steps) {
13721
+ const emoji = getStepStatusEmoji(step.status);
13722
+ console.log(`${emoji} [${step.status.toUpperCase()}] ${step.stepKey}: ${step.description}`);
13723
+ const details = [];
13724
+ if (step.assigneeRole)
13725
+ details.push(`assignee=${step.assigneeRole}`);
13726
+ if (step.dependsOn.length > 0)
13727
+ details.push(`depends_on=[${step.dependsOn.join(", ")}]`);
13728
+ details.push(`order=${step.order}`);
13729
+ console.log(` ${details.join(" | ")}`);
13730
+ if (step.specification) {
13731
+ const spec = step.specification;
13732
+ if (spec.goal) {
13733
+ console.log(` \uD83D\uDCCE Goal: ${spec.goal}`);
13734
+ }
13735
+ if (spec.requirements) {
13736
+ console.log(` \uD83D\uDCCE Requirements: ${spec.requirements}`);
13737
+ }
13738
+ if (spec.warnings) {
13739
+ console.log(` ⚠️ Warnings: ${spec.warnings}`);
13740
+ }
13741
+ }
13742
+ if (step.cancelReason) {
13743
+ console.log(` Cancel reason: ${step.cancelReason}`);
13744
+ }
13745
+ console.log("");
13746
+ }
13747
+ }
13748
+ if (result.availableNextSteps.length > 0) {
13749
+ console.log("──────────────────────────────────────────────────");
13750
+ console.log("\uD83D\uDD1C AVAILABLE NEXT STEPS");
13751
+ console.log("──────────────────────────────────────────────────");
13752
+ for (const stepKey of result.availableNextSteps) {
13753
+ const step = result.steps.find((s) => s.stepKey === stepKey);
13754
+ if (step) {
13755
+ console.log(` → ${stepKey}: ${step.description}`);
13756
+ }
13757
+ }
13758
+ console.log("");
13759
+ }
13760
+ console.log("══════════════════════════════════════════════════");
13761
+ console.log("");
13762
+ } catch (error) {
13763
+ console.error(`❌ Failed to get workflow status: ${error.message}`);
13764
+ process.exit(1);
13765
+ return;
13766
+ }
13767
+ }
13768
+ async function completeStep(chatroomId, options, deps) {
13769
+ const d = deps ?? await createDefaultDeps11();
13770
+ const sessionId = requireAuth3(d);
13771
+ validateChatroomId2(chatroomId);
13772
+ try {
13773
+ await d.backend.mutation(api.workflows.completeStep, {
13774
+ sessionId,
13775
+ chatroomId,
13776
+ workflowKey: options.workflowKey,
13777
+ stepKey: options.stepKey
13778
+ });
13779
+ console.log("");
13780
+ console.log("✅ Step completed");
13781
+ console.log(` Workflow: ${options.workflowKey}`);
13782
+ console.log(` Step: ${options.stepKey}`);
13783
+ console.log("");
13784
+ } catch (error) {
13785
+ console.error(`❌ Failed to complete step: ${error.message}`);
13283
13786
  process.exit(1);
13284
13787
  return;
13285
13788
  }
13286
13789
  }
13287
- function getStatusEmoji(status) {
13288
- switch (status) {
13289
- case "pending":
13290
- return "\uD83D\uDFE2";
13291
- case "acknowledged":
13292
- return "\uD83D\uDCEC";
13293
- case "in_progress":
13294
- return "\uD83D\uDD35";
13295
- case "backlog":
13296
- return "⚪";
13297
- case "completed":
13298
- return "✅";
13299
- case "pending_user_review":
13300
- return "\uD83D\uDC40";
13301
- case "closed":
13302
- return "\uD83D\uDD12";
13303
- default:
13304
- return "⚫";
13305
- }
13306
- }
13307
- function computeContentHash(content) {
13308
- return createHash("sha256").update(content).digest("hex");
13309
- }
13310
- async function exportBacklog(chatroomId, options, deps) {
13790
+ async function cancelStep(chatroomId, options, deps) {
13311
13791
  const d = deps ?? await createDefaultDeps11();
13312
- const sessionId = requireAuth2(d);
13313
- validateChatroomId(chatroomId);
13314
- if (!d.fs) {
13315
- console.error("❌ File system operations not available");
13792
+ const sessionId = requireAuth3(d);
13793
+ validateChatroomId2(chatroomId);
13794
+ if (!options.reason || options.reason.trim().length === 0) {
13795
+ console.error("❌ Reason is required when cancelling a step");
13316
13796
  process.exit(1);
13317
13797
  return;
13318
13798
  }
13319
13799
  try {
13320
- const backlogItems = await d.backend.query(api.backlog.listBacklogItems, {
13800
+ await d.backend.mutation(api.workflows.cancelStep, {
13321
13801
  sessionId,
13322
13802
  chatroomId,
13323
- statusFilter: "backlog"
13803
+ workflowKey: options.workflowKey,
13804
+ stepKey: options.stepKey,
13805
+ reason: options.reason.trim()
13324
13806
  });
13325
- const exportData = {
13326
- exportedAt: Date.now(),
13327
- chatroomId,
13328
- items: backlogItems.map((item) => {
13329
- const exportItem = {
13330
- contentHash: computeContentHash(item.content),
13331
- content: item.content,
13332
- status: item.status,
13333
- createdBy: item.createdBy ?? "unknown",
13334
- createdAt: item.createdAt
13335
- };
13336
- if (item.complexity)
13337
- exportItem.complexity = item.complexity;
13338
- if (item.value)
13339
- exportItem.value = item.value;
13340
- if (item.priority !== undefined)
13341
- exportItem.priority = item.priority;
13342
- return exportItem;
13343
- })
13344
- };
13345
- const exportDir = options.path ?? nodePath.join(process.cwd(), DEFAULT_EXPORT_DIR);
13346
- await d.fs.mkdir(exportDir, { recursive: true });
13347
- const filePath = nodePath.join(exportDir, BACKLOG_EXPORT_FILENAME);
13348
- await d.fs.writeFile(filePath, JSON.stringify(exportData, null, 2));
13349
13807
  console.log("");
13350
- console.log(`✅ Exported ${exportData.items.length} backlog item(s)`);
13351
- console.log(` File: ${filePath}`);
13808
+ console.log("❌ Step cancelled");
13809
+ console.log(` Workflow: ${options.workflowKey}`);
13810
+ console.log(` Step: ${options.stepKey}`);
13811
+ console.log(` Reason: ${options.reason.trim()}`);
13352
13812
  console.log("");
13353
13813
  } catch (error) {
13354
- console.error(`❌ Failed to export backlog items: ${error.message}`);
13814
+ console.error(`❌ Failed to cancel step: ${error.message}`);
13355
13815
  process.exit(1);
13356
13816
  return;
13357
13817
  }
13358
13818
  }
13359
- async function importBacklog(chatroomId, options, deps) {
13819
+ async function exitWorkflow(chatroomId, options, deps) {
13360
13820
  const d = deps ?? await createDefaultDeps11();
13361
- const sessionId = requireAuth2(d);
13362
- validateChatroomId(chatroomId);
13363
- if (!d.fs) {
13364
- console.error("❌ File system operations not available");
13821
+ const sessionId = requireAuth3(d);
13822
+ validateChatroomId2(chatroomId);
13823
+ if (!options.reason || options.reason.trim().length === 0) {
13824
+ console.error("❌ Reason is required when exiting a workflow");
13365
13825
  process.exit(1);
13366
13826
  return;
13367
13827
  }
13368
13828
  try {
13369
- const importDir = options.path ?? nodePath.join(process.cwd(), DEFAULT_EXPORT_DIR);
13370
- const filePath = nodePath.join(importDir, BACKLOG_EXPORT_FILENAME);
13371
- const raw = await d.fs.readFile(filePath, "utf-8");
13372
- const exportData = JSON.parse(raw);
13373
- const ageMs = Date.now() - exportData.exportedAt;
13374
- if (ageMs > STALENESS_THRESHOLD_MS) {
13375
- const ageDays = Math.floor(ageMs / 86400000);
13376
- console.log(`⚠️ This export is ${ageDays} days old and may be stale.`);
13377
- }
13378
- const existingItems = await d.backend.query(api.backlog.listBacklogItems, {
13829
+ await d.backend.mutation(api.workflows.exitWorkflow, {
13379
13830
  sessionId,
13380
13831
  chatroomId,
13381
- statusFilter: "backlog"
13832
+ workflowKey: options.workflowKey,
13833
+ reason: options.reason.trim()
13382
13834
  });
13383
- const existingHashes = new Set(existingItems.map((item) => computeContentHash(item.content)));
13384
- let imported = 0;
13385
- let skipped = 0;
13386
- for (const item of exportData.items) {
13387
- const hash = computeContentHash(item.content);
13388
- if (existingHashes.has(hash)) {
13389
- skipped++;
13390
- continue;
13391
- }
13392
- await d.backend.mutation(api.backlog.createBacklogItem, {
13393
- sessionId,
13394
- chatroomId,
13395
- content: item.content,
13396
- createdBy: item.createdBy,
13397
- priority: item.priority,
13398
- complexity: item.complexity,
13399
- value: item.value
13400
- });
13401
- existingHashes.add(hash);
13402
- imported++;
13403
- }
13404
13835
  console.log("");
13405
- console.log(`✅ Import complete`);
13406
- console.log(` Total items in file: ${exportData.items.length}`);
13407
- console.log(` Imported: ${imported}`);
13408
- console.log(` Skipped (duplicate): ${skipped}`);
13836
+ console.log("❌ Workflow exited (cancelled)");
13837
+ console.log(` Key: ${options.workflowKey}`);
13838
+ console.log(` Reason: ${options.reason.trim()}`);
13409
13839
  console.log("");
13410
13840
  } catch (error) {
13411
- console.error(`❌ Failed to import backlog items: ${error.message}`);
13841
+ console.error(`❌ Failed to exit workflow: ${error.message}`);
13412
13842
  process.exit(1);
13413
13843
  return;
13414
13844
  }
13415
13845
  }
13416
- var BACKLOG_EXPORT_FILENAME = "backlog-export.json", STALENESS_THRESHOLD_MS, DEFAULT_EXPORT_DIR = ".chatroom/exports";
13417
- var init_backlog = __esm(() => {
13846
+ var init_workflow = __esm(() => {
13418
13847
  init_api3();
13419
13848
  init_storage();
13420
13849
  init_client2();
13421
- STALENESS_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
13422
- });
13423
-
13424
- // src/utils/file-content.ts
13425
- var exports_file_content = {};
13426
- __export(exports_file_content, {
13427
- resolveContent: () => resolveContent,
13428
- readFileContent: () => readFileContent
13429
13850
  });
13430
- import { readFileSync as readFileSync5 } from "fs";
13431
- import { resolve } from "path";
13432
- function readFileContent(filePath, optionName) {
13433
- const absolutePath = resolve(process.cwd(), filePath);
13434
- try {
13435
- return readFileSync5(absolutePath, "utf-8");
13436
- } catch (err) {
13437
- const nodeErr = err;
13438
- throw new Error(`Cannot read file for --${optionName}: ${absolutePath}
13439
- ` + `Reason: ${nodeErr.message}`);
13440
- }
13441
- }
13442
- function resolveContent(content, filePath, optionName) {
13443
- if (content && filePath) {
13444
- throw new Error(`Cannot specify both --${optionName} and --${optionName}-file`);
13445
- }
13446
- if (filePath) {
13447
- return readFileContent(filePath, `${optionName}-file`);
13448
- }
13449
- return content;
13450
- }
13451
- var init_file_content = () => {};
13452
13851
 
13453
13852
  // src/commands/task/read/index.ts
13454
13853
  var exports_read = {};
@@ -13582,7 +13981,7 @@ async function createDefaultDeps13() {
13582
13981
  }
13583
13982
  };
13584
13983
  }
13585
- function requireAuth3(d) {
13984
+ function requireAuth4(d) {
13586
13985
  const sessionId = d.session.getSessionId();
13587
13986
  if (!sessionId) {
13588
13987
  console.error(`❌ Not authenticated. Please run: chatroom auth login`);
@@ -13592,7 +13991,7 @@ function requireAuth3(d) {
13592
13991
  }
13593
13992
  async function listSkills(chatroomId, options, deps) {
13594
13993
  const d = deps ?? await createDefaultDeps13();
13595
- const sessionId = requireAuth3(d);
13994
+ const sessionId = requireAuth4(d);
13596
13995
  try {
13597
13996
  const skills = await d.backend.query(api.skills.list, {
13598
13997
  sessionId,
@@ -13620,7 +14019,7 @@ async function listSkills(chatroomId, options, deps) {
13620
14019
  }
13621
14020
  async function activateSkill(chatroomId, skillId, options, deps) {
13622
14021
  const d = deps ?? await createDefaultDeps13();
13623
- const sessionId = requireAuth3(d);
14022
+ const sessionId = requireAuth4(d);
13624
14023
  try {
13625
14024
  const convexUrl = d.session.getConvexUrl();
13626
14025
  const result = await d.backend.mutation(api.skills.activate, {
@@ -14456,6 +14855,15 @@ async function onRequestStartAgent(ctx, event) {
14456
14855
  });
14457
14856
  if (!result.success) {
14458
14857
  console.log(`[daemon] Agent start rejected for role=${event.role}: ${result.error ?? "unknown"}`);
14858
+ ctx.deps.backend.mutation(api.machines.emitAgentStartFailed, {
14859
+ sessionId: ctx.sessionId,
14860
+ machineId: ctx.machineId,
14861
+ chatroomId: event.chatroomId,
14862
+ role: event.role,
14863
+ error: result.error ?? "unknown"
14864
+ }).catch((err) => {
14865
+ console.log(` ⚠️ Failed to emit startFailed event: ${err.message}`);
14866
+ });
14459
14867
  } else {
14460
14868
  ctx.deps.backend.mutation(api.workspaces.registerWorkspace, {
14461
14869
  sessionId: ctx.sessionId,
@@ -15482,6 +15890,19 @@ class AgentProcessManager {
15482
15890
  const key = agentKey2(opts.chatroomId, opts.role);
15483
15891
  const slot = this.slots.get(key);
15484
15892
  if (!slot || slot.state === "idle") {
15893
+ this.deps.backend.mutation(api.machines.recordAgentExited, {
15894
+ sessionId: this.deps.sessionId,
15895
+ machineId: this.deps.machineId,
15896
+ chatroomId: opts.chatroomId,
15897
+ role: opts.role,
15898
+ pid: 0,
15899
+ stopReason: opts.reason,
15900
+ exitCode: undefined,
15901
+ signal: undefined,
15902
+ agentHarness: undefined
15903
+ }).catch((err) => {
15904
+ console.log(` ⚠️ Failed to record agent exit (idle cleanup): ${err.message}`);
15905
+ });
15485
15906
  return { success: true };
15486
15907
  }
15487
15908
  if (slot.state === "stopping" && slot.pendingOperation) {
@@ -15539,7 +15960,7 @@ class AgentProcessManager {
15539
15960
  for (const service of this.deps.agentServices.values()) {
15540
15961
  service.untrack(opts.pid);
15541
15962
  }
15542
- const isIntentionalStop = stopReason === "user.stop" || stopReason === "platform.team_switch";
15963
+ const isIntentionalStop = stopReason === "user.stop" || stopReason === "platform.team_switch" || stopReason === "daemon.shutdown";
15543
15964
  const isDaemonRespawn = stopReason === "daemon.respawn";
15544
15965
  if (isIntentionalStop) {
15545
15966
  this.deps.crashLoop.clear(opts.chatroomId, opts.role);
@@ -15934,11 +16355,12 @@ async function connectDaemon(client2, sessionId, machineId, convexUrl) {
15934
16355
  } catch (error) {
15935
16356
  if (isNetworkError(error)) {
15936
16357
  formatConnectivityError(error, convexUrl);
16358
+ throw error;
15937
16359
  } else {
15938
16360
  console.error(`❌ Failed to update daemon status: ${error.message}`);
16361
+ releaseLock();
16362
+ process.exit(1);
15939
16363
  }
15940
- releaseLock();
15941
- process.exit(1);
15942
16364
  }
15943
16365
  }
15944
16366
  function logStartup(ctx, availableModels) {
@@ -15968,45 +16390,59 @@ async function initDaemon() {
15968
16390
  const sessionId = validateAuthentication(convexUrl);
15969
16391
  const client2 = await getConvexClient();
15970
16392
  const typedSessionId = sessionId;
15971
- await validateSession(client2, typedSessionId, convexUrl);
15972
- const config3 = setupMachine();
15973
- const { machineId } = config3;
15974
- initHarnessRegistry();
15975
- const agentServices = new Map(getAllHarnesses().map((s) => [s.id, s]));
15976
- const availableModels = await registerCapabilities(client2, typedSessionId, config3, agentServices);
15977
- await connectDaemon(client2, typedSessionId, machineId, convexUrl);
15978
- const deps = createDefaultDeps19();
15979
- deps.backend.mutation = (endpoint, args) => client2.mutation(endpoint, args);
15980
- deps.backend.query = (endpoint, args) => client2.query(endpoint, args);
15981
- deps.agentProcessManager = new AgentProcessManager({
15982
- agentServices,
15983
- backend: deps.backend,
15984
- sessionId: typedSessionId,
15985
- machineId,
15986
- processes: deps.processes,
15987
- clock: deps.clock,
15988
- fs: deps.fs,
15989
- persistence: deps.machine,
15990
- spawning: deps.spawning,
15991
- crashLoop: new CrashLoopTracker,
15992
- convexUrl
15993
- });
15994
- const events = new DaemonEventBus;
15995
- const ctx = {
15996
- client: client2,
15997
- sessionId: typedSessionId,
15998
- machineId,
15999
- config: config3,
16000
- deps,
16001
- events,
16002
- agentServices,
16003
- lastPushedGitState: new Map
16004
- };
16005
- registerEventListeners(ctx);
16006
- logStartup(ctx, availableModels);
16007
- await recoverState(ctx);
16008
- return ctx;
16393
+ while (true) {
16394
+ try {
16395
+ await validateSession(client2, typedSessionId, convexUrl);
16396
+ const config3 = setupMachine();
16397
+ const { machineId } = config3;
16398
+ initHarnessRegistry();
16399
+ const agentServices = new Map(getAllHarnesses().map((s) => [s.id, s]));
16400
+ const availableModels = await registerCapabilities(client2, typedSessionId, config3, agentServices);
16401
+ await connectDaemon(client2, typedSessionId, machineId, convexUrl);
16402
+ const deps = createDefaultDeps19();
16403
+ deps.backend.mutation = (endpoint, args) => client2.mutation(endpoint, args);
16404
+ deps.backend.query = (endpoint, args) => client2.query(endpoint, args);
16405
+ deps.agentProcessManager = new AgentProcessManager({
16406
+ agentServices,
16407
+ backend: deps.backend,
16408
+ sessionId: typedSessionId,
16409
+ machineId,
16410
+ processes: deps.processes,
16411
+ clock: deps.clock,
16412
+ fs: deps.fs,
16413
+ persistence: deps.machine,
16414
+ spawning: deps.spawning,
16415
+ crashLoop: new CrashLoopTracker,
16416
+ convexUrl
16417
+ });
16418
+ const events = new DaemonEventBus;
16419
+ const ctx = {
16420
+ client: client2,
16421
+ sessionId: typedSessionId,
16422
+ machineId,
16423
+ config: config3,
16424
+ deps,
16425
+ events,
16426
+ agentServices,
16427
+ lastPushedGitState: new Map
16428
+ };
16429
+ registerEventListeners(ctx);
16430
+ logStartup(ctx, availableModels);
16431
+ await recoverState(ctx);
16432
+ return ctx;
16433
+ } catch (error) {
16434
+ if (isNetworkError(error)) {
16435
+ console.log(`
16436
+ Retrying in ${CONNECTION_RETRY_INTERVAL_MS / 1000}s...
16437
+ `);
16438
+ await new Promise((resolve2) => setTimeout(resolve2, CONNECTION_RETRY_INTERVAL_MS));
16439
+ } else {
16440
+ throw error;
16441
+ }
16442
+ }
16443
+ }
16009
16444
  }
16445
+ var CONNECTION_RETRY_INTERVAL_MS = 1000;
16010
16446
  var init_init2 = __esm(() => {
16011
16447
  init_state_recovery();
16012
16448
  init_api3();
@@ -16033,7 +16469,7 @@ async function onDaemonShutdown(ctx) {
16033
16469
  await ctx.deps.agentProcessManager.stop({
16034
16470
  chatroomId,
16035
16471
  role,
16036
- reason: "user.stop"
16472
+ reason: "daemon.shutdown"
16037
16473
  });
16038
16474
  console.log(` Stopped ${role} (PID ${slot.pid})`);
16039
16475
  } catch (e) {
@@ -16775,8 +17211,8 @@ async function maybeRequireAuth() {
16775
17211
  console.log("⚠️ Skipping authentication (--skip-auth flag)");
16776
17212
  return;
16777
17213
  }
16778
- const { requireAuth: requireAuth4 } = await Promise.resolve().then(() => (init_middleware(), exports_middleware));
16779
- await requireAuth4();
17214
+ const { requireAuth: requireAuth5 } = await Promise.resolve().then(() => (init_middleware(), exports_middleware));
17215
+ await requireAuth5();
16780
17216
  }
16781
17217
  var authCommand = program2.command("auth").description("Manage CLI authentication");
16782
17218
  authCommand.command("login").description("Authenticate the CLI via browser").option("-f, --force", "Re-authenticate even if already logged in").action(async (options) => {
@@ -16818,56 +17254,6 @@ program2.command("get-next-task").description("Join a chatroom and get the next
16818
17254
  role: options.role
16819
17255
  });
16820
17256
  });
16821
- program2.command("task-started").description("[LEGACY] Acknowledge a task and optionally classify the user message. Use classify instead for entry-point roles.").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").option("--origin-message-classification <type>", "Original message classification: question, new_feature, or follow_up (for entry point roles)").option("--no-classify", "Skip classification (for handoff recipients - classification already done by entry point)").requiredOption("--task-id <taskId>", "Task ID to acknowledge").action(async (options) => {
16822
- console.error("⚠️ DEPRECATED: task-started is legacy. Use chatroom classify for entry-point roles.");
16823
- await maybeRequireAuth();
16824
- const skipClassification = options.classify === false;
16825
- if (!skipClassification && !options.originMessageClassification) {
16826
- console.error(`❌ Either --no-classify or --origin-message-classification is required`);
16827
- console.error("");
16828
- console.error(" For entry point roles (receiving user messages):");
16829
- console.error(" Use --origin-message-classification=<type>");
16830
- console.error("");
16831
- console.error(" For handoff recipients (receiving from other agents):");
16832
- console.error(" Use --no-classify");
16833
- process.exit(1);
16834
- }
16835
- if (skipClassification && options.originMessageClassification) {
16836
- console.error(`❌ Cannot use both --no-classify and --origin-message-classification`);
16837
- console.error(" Use --no-classify for handoffs, or --origin-message-classification for user messages");
16838
- process.exit(1);
16839
- }
16840
- if (options.originMessageClassification) {
16841
- const validClassifications = ["question", "new_feature", "follow_up"];
16842
- if (!validClassifications.includes(options.originMessageClassification)) {
16843
- console.error(`❌ Invalid classification: ${options.originMessageClassification}. Must be one of: ${validClassifications.join(", ")}`);
16844
- process.exit(1);
16845
- }
16846
- }
16847
- let rawStdin;
16848
- if (options.originMessageClassification === "new_feature") {
16849
- const stdinContent = await readStdin();
16850
- if (!stdinContent.trim()) {
16851
- console.error(`❌ Stdin is empty. For new_feature classification, provide:
16852
- ---TITLE---
16853
- [title]
16854
- ---DESCRIPTION---
16855
- [description]
16856
- ---TECH_SPECS---
16857
- [specs]`);
16858
- process.exit(1);
16859
- }
16860
- rawStdin = stdinContent;
16861
- }
16862
- const { taskStarted: taskStarted2 } = await Promise.resolve().then(() => (init_task_started2(), exports_task_started2));
16863
- await taskStarted2(options.chatroomId, {
16864
- role: options.role,
16865
- originMessageClassification: options.originMessageClassification,
16866
- taskId: options.taskId,
16867
- rawStdin,
16868
- noClassify: skipClassification
16869
- });
16870
- });
16871
17257
  program2.command("classify").description("Classify a task's origin message (entry-point role only).").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (must be entry-point role)").requiredOption("--task-id <taskId>", "Task ID to acknowledge").requiredOption("--origin-message-classification <type>", "Original message classification: question, new_feature, or follow_up").action(async (options) => {
16872
17258
  await maybeRequireAuth();
16873
17259
  const validClassifications = ["question", "new_feature", "follow_up"];
@@ -16964,18 +17350,28 @@ backlogCommand.command("list").description("List backlog items").requiredOption(
16964
17350
  filter: options.filter
16965
17351
  });
16966
17352
  });
16967
- backlogCommand.command("add").description("Add a backlog item").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (creator)").requiredOption("--content-file <path>", "Path to file containing task content").action(async (options) => {
17353
+ backlogCommand.command("add").description("Add a backlog item").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (creator)").option("--content-file <path>", "Path to file containing task content (or use stdin/heredoc)").action(async (options) => {
16968
17354
  await maybeRequireAuth();
16969
- const { readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_file_content(), exports_file_content));
16970
17355
  let content;
16971
- try {
16972
- content = readFileContent2(options.contentFile, "content-file");
16973
- } catch (err) {
16974
- console.error(`❌ ${err.message}`);
16975
- process.exit(1);
17356
+ if (options.contentFile) {
17357
+ const { readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_file_content(), exports_file_content));
17358
+ try {
17359
+ content = readFileContent2(options.contentFile, "content-file");
17360
+ } catch (err) {
17361
+ console.error(`❌ ${err.message}`);
17362
+ process.exit(1);
17363
+ }
17364
+ } else {
17365
+ const stdinContent = await readStdin();
17366
+ content = stdinContent;
16976
17367
  }
16977
17368
  if (!content || content.trim().length === 0) {
16978
- console.error("❌ Content file is empty");
17369
+ console.error("❌ Content is empty. Provide content via --content-file or stdin (heredoc).");
17370
+ console.error("");
17371
+ console.error(" Example with heredoc:");
17372
+ console.error(" chatroom backlog add --chatroom-id=<id> --role=<role> << 'EOF'");
17373
+ console.error(" Your backlog item content here");
17374
+ console.error(" EOF");
16979
17375
  process.exit(1);
16980
17376
  }
16981
17377
  const { addBacklog: addBacklog2 } = await Promise.resolve().then(() => (init_backlog(), exports_backlog));
@@ -17026,6 +17422,104 @@ backlogCommand.command("import").description("Import backlog items from a JSON e
17026
17422
  const { importBacklog: importBacklog2 } = await Promise.resolve().then(() => (init_backlog(), exports_backlog));
17027
17423
  await importBacklog2(options.chatroomId, { role: options.role, path: options.path });
17028
17424
  });
17425
+ var workflowCommand = program2.command("workflow").description("Manage structured workflows");
17426
+ workflowCommand.command("create").description("Create a new workflow with steps (reads JSON from stdin)").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role (creator)").requiredOption("--workflow-key <key>", "Unique workflow key").action(async (options) => {
17427
+ await maybeRequireAuth();
17428
+ const stdinContent = await readStdin();
17429
+ if (!stdinContent || stdinContent.trim().length === 0) {
17430
+ console.error("❌ JSON input is required via stdin.");
17431
+ console.error("");
17432
+ console.error(" Example:");
17433
+ console.error(" chatroom workflow create --chatroom-id=<id> --role=<role> --workflow-key=<key> << 'EOF'");
17434
+ console.error(' { "steps": [{ "stepKey": "step1", "description": "First step", "dependsOn": [], "order": 1 }] }');
17435
+ console.error(" EOF");
17436
+ process.exit(1);
17437
+ }
17438
+ const { createWorkflow: createWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17439
+ await createWorkflow2(options.chatroomId, {
17440
+ role: options.role,
17441
+ workflowKey: options.workflowKey,
17442
+ stdinContent
17443
+ });
17444
+ });
17445
+ workflowCommand.command("specify").description("Specify a workflow step with goal, requirements, and optional warnings").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key").requiredOption("--step-key <stepKey>", "Step key to specify").requiredOption("--assignee-role <assigneeRole>", "Role to assign the step to").action(async (options) => {
17446
+ await maybeRequireAuth();
17447
+ const stdinContent = await readStdin();
17448
+ if (!stdinContent || stdinContent.trim().length === 0) {
17449
+ console.error("❌ Step specification is required via stdin.");
17450
+ console.error("");
17451
+ console.error(" Example:");
17452
+ console.error(" chatroom workflow specify --chatroom-id=<id> --role=<role> --workflow-key=<key> --step-key=<step> --assignee-role=<role> << 'EOF'");
17453
+ console.error(" ---GOAL---");
17454
+ console.error(" Your goal here");
17455
+ console.error(" ---REQUIREMENTS---");
17456
+ console.error(" Your requirements here");
17457
+ console.error(" ---WARNINGS---");
17458
+ console.error(" Optional warnings here");
17459
+ console.error(" EOF");
17460
+ process.exit(1);
17461
+ }
17462
+ const { specifyWorkflowStep: specifyWorkflowStep2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17463
+ await specifyWorkflowStep2(options.chatroomId, {
17464
+ role: options.role,
17465
+ workflowKey: options.workflowKey,
17466
+ stepKey: options.stepKey,
17467
+ assigneeRole: options.assigneeRole,
17468
+ stdinContent
17469
+ });
17470
+ });
17471
+ workflowCommand.command("execute").description("Activate a draft workflow (transitions to active, starts root steps)").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key to execute").action(async (options) => {
17472
+ await maybeRequireAuth();
17473
+ const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17474
+ await executeWorkflow2(options.chatroomId, {
17475
+ role: options.role,
17476
+ workflowKey: options.workflowKey
17477
+ });
17478
+ });
17479
+ workflowCommand.command("status").description("View the full status of a workflow including all steps").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key").action(async (options) => {
17480
+ await maybeRequireAuth();
17481
+ const { getWorkflowStatus: getWorkflowStatus2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17482
+ await getWorkflowStatus2(options.chatroomId, {
17483
+ role: options.role,
17484
+ workflowKey: options.workflowKey
17485
+ });
17486
+ });
17487
+ workflowCommand.command("step-complete").description("Mark a workflow step as completed").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key").requiredOption("--step-key <stepKey>", "Step key to mark as complete").action(async (options) => {
17488
+ await maybeRequireAuth();
17489
+ const { completeStep: completeStep2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17490
+ await completeStep2(options.chatroomId, {
17491
+ role: options.role,
17492
+ workflowKey: options.workflowKey,
17493
+ stepKey: options.stepKey
17494
+ });
17495
+ });
17496
+ workflowCommand.command("step-cancel").description("Cancel a workflow step with a reason").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key").requiredOption("--step-key <stepKey>", "Step key to cancel").requiredOption("--reason <text>", "Reason for cancellation (required)").action(async (options) => {
17497
+ await maybeRequireAuth();
17498
+ if (!options.reason || options.reason.trim().length === 0) {
17499
+ console.error("❌ --reason is required and cannot be empty");
17500
+ process.exit(1);
17501
+ }
17502
+ const { cancelStep: cancelStep2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17503
+ await cancelStep2(options.chatroomId, {
17504
+ role: options.role,
17505
+ workflowKey: options.workflowKey,
17506
+ stepKey: options.stepKey,
17507
+ reason: options.reason
17508
+ });
17509
+ });
17510
+ workflowCommand.command("exit").description("Exit (cancel) an entire workflow with a reason").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role").requiredOption("--workflow-key <key>", "Workflow key to exit").requiredOption("--reason <text>", "Reason for exiting the workflow (required)").action(async (options) => {
17511
+ await maybeRequireAuth();
17512
+ if (!options.reason || options.reason.trim().length === 0) {
17513
+ console.error("❌ --reason is required and cannot be empty");
17514
+ process.exit(1);
17515
+ }
17516
+ const { exitWorkflow: exitWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
17517
+ await exitWorkflow2(options.chatroomId, {
17518
+ role: options.role,
17519
+ workflowKey: options.workflowKey,
17520
+ reason: options.reason
17521
+ });
17522
+ });
17029
17523
  var taskCommand = program2.command("task").description("Manage tasks");
17030
17524
  taskCommand.command("read").description("Read a task and mark it as in_progress").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Your role in the chatroom").requiredOption("--task-id <taskId>", "Task ID to read").action(async (options) => {
17031
17525
  await maybeRequireAuth();