@mindstudio-ai/remy 0.1.200 → 0.1.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -308,14 +308,24 @@ Return the full conversation history.
308
308
 
309
309
  Messages with `hidden: true` were generated by `runCommand` actions and should not be displayed in the UI.
310
310
 
311
+ The response also includes `queuedMessages` — the current pending-queue snapshot (possibly empty) — so a client can render the queue immediately on connect/reconnect. Live changes after that arrive via `queue_changed`.
312
+
311
313
  #### `cancel`
312
314
 
313
- Cancel the current turn. The cancel command gets `completed(success:true)`. The in-flight message command (if any) gets its own `completed(success:false, error:"cancelled")`.
315
+ Cancel the current turn. The cancel command gets `completed(success:true)`; any queued messages are flushed and returned on it as `cancelledMessages`. The in-flight message command (if any) gets its own `completed(success:false, error:"cancelled")`.
314
316
 
315
317
  ```json
316
318
  {"action": "cancel", "requestId": "r3"}
317
319
  ```
318
320
 
321
+ #### `cancelQueued`
322
+
323
+ Cancel pending **queued** messages without touching the in-flight turn. Omit `id` to cancel all queued user messages, or pass `id` (the `requestId` of a queued message) to cancel just that one. Only user messages are cancellable — chained and background messages are part of a system chain and are never removed. Responds with `completed(success:true, cancelledQueued:[...])` listing what was removed; a `queue_changed` event also fires with the updated queue.
324
+
325
+ ```json
326
+ {"action": "cancelQueued", "requestId": "r6", "id": "r2"}
327
+ ```
328
+
319
329
  #### `clear`
320
330
 
321
331
  Clear conversation history and delete the session file.
@@ -342,6 +352,7 @@ Events are emitted as newline-delimited JSON. Command responses include `request
342
352
  |-------|--------|-------------|
343
353
  | `ready` | | Headless mode initialized, ready for input |
344
354
  | `session_restored` | `messageCount` | Previous session loaded |
355
+ | `queue_changed` | `queuedMessages` | Queue contents changed (any push/shift/drain/cancel). Carries the full current snapshot — an empty array when the queue drains. The live queue-state signal; for the initial snapshot on connect/reconnect, read `queuedMessages` from `get_history`. |
345
356
  | `stopping` | | Shutdown initiated |
346
357
  | `stopped` | | Shutdown complete |
347
358
 
@@ -68,14 +68,9 @@ declare class HeadlessSession {
68
68
  start(): Promise<void>;
69
69
  private shutdown;
70
70
  private emit;
71
- /**
72
- * Emit a `completed` event and mark completedEmitted. Includes
73
- * `queuedMessages` if the queue has items (sandbox uses this to know the
74
- * agent is still busy with pipeline work).
75
- */
71
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
72
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
76
73
  private emitCompleted;
77
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
78
- private queueFields;
79
74
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
80
75
  private dispatchSimple;
81
76
  /** Persist sessionStats + queue snapshot to .remy-stats.json. */
@@ -138,6 +133,13 @@ declare class HeadlessSession {
138
133
  private handleChangeModels;
139
134
  /** Cancel the running turn and drain the queue. Returns the drained items. */
140
135
  private handleCancel;
136
+ /**
137
+ * Remove pending queued messages — all user messages, or one by id.
138
+ * Only `source: 'user'` items are removable; chained and background
139
+ * messages are part of a system chain and are never cancellable. Does
140
+ * not affect the in-flight turn (use `cancel` for that).
141
+ */
142
+ private handleCancelQueued;
141
143
  private handleStdinLine;
142
144
  }
143
145
 
package/dist/headless.js CHANGED
@@ -7248,6 +7248,24 @@ var MessageQueue = class {
7248
7248
  this.onChange?.();
7249
7249
  return all;
7250
7250
  }
7251
+ /**
7252
+ * Remove all items matching `predicate`. Fires onChange only if something
7253
+ * was removed. Returns the removed items.
7254
+ */
7255
+ removeWhere(predicate) {
7256
+ const removed = [];
7257
+ this.items = this.items.filter((item) => {
7258
+ if (predicate(item)) {
7259
+ removed.push(item);
7260
+ return false;
7261
+ }
7262
+ return true;
7263
+ });
7264
+ if (removed.length > 0) {
7265
+ this.onChange?.();
7266
+ }
7267
+ return removed;
7268
+ }
7251
7269
  /** Copy of current queue contents (for surfacing on events). */
7252
7270
  snapshot() {
7253
7271
  return [...this.items];
@@ -7298,6 +7316,24 @@ function resolveAction(text) {
7298
7316
  next
7299
7317
  };
7300
7318
  }
7319
+ function getActionChain(startName) {
7320
+ const chain = [];
7321
+ const seen = /* @__PURE__ */ new Set();
7322
+ let name = startName;
7323
+ while (name && !seen.has(name)) {
7324
+ seen.add(name);
7325
+ chain.push(name);
7326
+ let body;
7327
+ try {
7328
+ body = readAsset("automatedActions", `${name}.md`);
7329
+ } catch {
7330
+ break;
7331
+ }
7332
+ const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
7333
+ name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
7334
+ }
7335
+ return chain;
7336
+ }
7301
7337
 
7302
7338
  // src/headless/index.ts
7303
7339
  var log14 = createLogger("headless");
@@ -7368,14 +7404,16 @@ var HeadlessSession = class {
7368
7404
  baseUrl: this.opts.baseUrl
7369
7405
  });
7370
7406
  const resumed = loadSession(this.state);
7371
- this.queue = new MessageQueue(loadQueue(), () => this.persistStats());
7407
+ this.queue = new MessageQueue(loadQueue(), () => {
7408
+ this.persistStats();
7409
+ this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
7410
+ });
7372
7411
  if (resumed) {
7373
7412
  this.emit("session_restored", {
7374
7413
  messageCount: this.state.messages.length,
7375
7414
  ...this.state.models && { models: this.state.models },
7376
7415
  modelSurfaces: MODEL_SURFACES,
7377
- allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
7378
- ...this.queueFields()
7416
+ allowedModelsByType: ALLOWED_MODELS_BY_TYPE
7379
7417
  });
7380
7418
  }
7381
7419
  triggerBrandExtraction(
@@ -7425,7 +7463,7 @@ var HeadlessSession = class {
7425
7463
  });
7426
7464
  process.on("SIGTERM", this.shutdown);
7427
7465
  process.on("SIGINT", this.shutdown);
7428
- this.emit("ready", this.queueFields());
7466
+ this.emit("ready");
7429
7467
  }
7430
7468
  shutdown = () => {
7431
7469
  this.emit("stopping");
@@ -7449,19 +7487,12 @@ var HeadlessSession = class {
7449
7487
  }
7450
7488
  process.stdout.write(line);
7451
7489
  }
7452
- /**
7453
- * Emit a `completed` event and mark completedEmitted. Includes
7454
- * `queuedMessages` if the queue has items (sandbox uses this to know the
7455
- * agent is still busy with pipeline work).
7456
- */
7490
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
7491
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
7457
7492
  emitCompleted(rid, data) {
7458
- this.emit("completed", { ...data, ...this.queueFields() }, rid);
7493
+ this.emit("completed", { ...data }, rid);
7459
7494
  this.completedEmitted = true;
7460
7495
  }
7461
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
7462
- queueFields() {
7463
- return this.queue.length > 0 ? { queuedMessages: this.queue.snapshot() } : {};
7464
- }
7465
7496
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
7466
7497
  dispatchSimple(requestId, eventName, handler) {
7467
7498
  try {
@@ -7639,11 +7670,7 @@ var HeadlessSession = class {
7639
7670
  });
7640
7671
  return;
7641
7672
  case "turn_cancelled": {
7642
- this.emit(
7643
- "completed",
7644
- { success: false, error: "cancelled", ...this.queueFields() },
7645
- rid
7646
- );
7673
+ this.emit("completed", { success: false, error: "cancelled" }, rid);
7647
7674
  this.completedEmitted = true;
7648
7675
  return;
7649
7676
  }
@@ -7767,7 +7794,7 @@ var HeadlessSession = class {
7767
7794
  * message — `running` stays held across the queue drain so no user
7768
7795
  * message can slip in mid-pipeline.
7769
7796
  */
7770
- async runSingleTurn(parsed, requestId) {
7797
+ async runSingleTurn(parsed, requestId, fromChain = false) {
7771
7798
  this.currentRequestId = requestId;
7772
7799
  this.currentAbort = new AbortController();
7773
7800
  this.completedEmitted = false;
@@ -7815,16 +7842,18 @@ var HeadlessSession = class {
7815
7842
  onboardingState,
7816
7843
  parsed.viewContext
7817
7844
  );
7818
- if (resolved?.next) {
7819
- this.queue.push({
7820
- command: {
7821
- action: "message",
7822
- text: sentinel(resolved.next),
7823
- onboardingState
7824
- },
7825
- source: "chain",
7826
- enqueuedAt: Date.now()
7827
- });
7845
+ if (resolved?.next && !fromChain) {
7846
+ for (const step of getActionChain(resolved.next)) {
7847
+ this.queue.push({
7848
+ command: {
7849
+ action: "message",
7850
+ text: sentinel(step),
7851
+ onboardingState
7852
+ },
7853
+ source: "chain",
7854
+ enqueuedAt: Date.now()
7855
+ });
7856
+ }
7828
7857
  }
7829
7858
  try {
7830
7859
  await runTurn({
@@ -7882,11 +7911,6 @@ var HeadlessSession = class {
7882
7911
  source: "user",
7883
7912
  enqueuedAt: Date.now()
7884
7913
  });
7885
- this.emit(
7886
- "queued",
7887
- { position: this.queue.length, ...this.queueFields() },
7888
- requestId
7889
- );
7890
7914
  return;
7891
7915
  }
7892
7916
  this.running = true;
@@ -7934,7 +7958,7 @@ var HeadlessSession = class {
7934
7958
  continue;
7935
7959
  }
7936
7960
  const nextRid = next.command.requestId ?? `${next.source}-${Date.now()}`;
7937
- await this.runSingleTurn(next.command, nextRid);
7961
+ await this.runSingleTurn(next.command, nextRid, next.source === "chain");
7938
7962
  }
7939
7963
  }
7940
7964
  /**
@@ -7998,6 +8022,17 @@ var HeadlessSession = class {
7998
8022
  }
7999
8023
  return this.queue.drain();
8000
8024
  }
8025
+ /**
8026
+ * Remove pending queued messages — all user messages, or one by id.
8027
+ * Only `source: 'user'` items are removable; chained and background
8028
+ * messages are part of a system chain and are never cancellable. Does
8029
+ * not affect the in-flight turn (use `cancel` for that).
8030
+ */
8031
+ handleCancelQueued(id) {
8032
+ return this.queue.removeWhere(
8033
+ (item) => item.source === "user" && (id === void 0 || item.command.requestId === id)
8034
+ );
8035
+ }
8001
8036
  //////////////////////////////////////////////////////////////////////////////
8002
8037
  // Stdin router
8003
8038
  //////////////////////////////////////////////////////////////////////////////
@@ -8062,7 +8097,11 @@ var HeadlessSession = class {
8062
8097
  ...this.state.models && { models: this.state.models },
8063
8098
  modelSurfaces: MODEL_SURFACES,
8064
8099
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8065
- ...this.queueFields()
8100
+ // Current queue snapshot for connect/reconnect — get_history is the
8101
+ // on-demand "current state" query. Always an array (possibly empty),
8102
+ // matching the queue_changed convention so the client reconciles the
8103
+ // same way from both; live mutations are pushed via queue_changed.
8104
+ queuedMessages: this.queue.snapshot()
8066
8105
  }));
8067
8106
  return;
8068
8107
  }
@@ -8106,6 +8145,16 @@ var HeadlessSession = class {
8106
8145
  );
8107
8146
  return;
8108
8147
  }
8148
+ if (action === "cancelQueued") {
8149
+ const id = parsed.id;
8150
+ const removed = this.handleCancelQueued(id);
8151
+ this.emit(
8152
+ "completed",
8153
+ { success: true, cancelledQueued: removed },
8154
+ requestId
8155
+ );
8156
+ return;
8157
+ }
8109
8158
  if (action === "stop_tool") {
8110
8159
  const id = parsed.id;
8111
8160
  const mode = parsed.mode ?? "hard";
package/dist/index.js CHANGED
@@ -8013,6 +8013,24 @@ var init_messageQueue = __esm({
8013
8013
  this.onChange?.();
8014
8014
  return all;
8015
8015
  }
8016
+ /**
8017
+ * Remove all items matching `predicate`. Fires onChange only if something
8018
+ * was removed. Returns the removed items.
8019
+ */
8020
+ removeWhere(predicate) {
8021
+ const removed = [];
8022
+ this.items = this.items.filter((item) => {
8023
+ if (predicate(item)) {
8024
+ removed.push(item);
8025
+ return false;
8026
+ }
8027
+ return true;
8028
+ });
8029
+ if (removed.length > 0) {
8030
+ this.onChange?.();
8031
+ }
8032
+ return removed;
8033
+ }
8016
8034
  /** Copy of current queue contents (for surfacing on events). */
8017
8035
  snapshot() {
8018
8036
  return [...this.items];
@@ -8064,6 +8082,24 @@ function resolveAction(text) {
8064
8082
  next
8065
8083
  };
8066
8084
  }
8085
+ function getActionChain(startName) {
8086
+ const chain = [];
8087
+ const seen = /* @__PURE__ */ new Set();
8088
+ let name = startName;
8089
+ while (name && !seen.has(name)) {
8090
+ seen.add(name);
8091
+ chain.push(name);
8092
+ let body;
8093
+ try {
8094
+ body = readAsset("automatedActions", `${name}.md`);
8095
+ } catch {
8096
+ break;
8097
+ }
8098
+ const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
8099
+ name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
8100
+ }
8101
+ return chain;
8102
+ }
8067
8103
  var NON_ACTION_SENTINELS;
8068
8104
  var init_resolve = __esm({
8069
8105
  "src/automatedActions/resolve.ts"() {
@@ -8168,14 +8204,16 @@ var init_headless = __esm({
8168
8204
  baseUrl: this.opts.baseUrl
8169
8205
  });
8170
8206
  const resumed = loadSession(this.state);
8171
- this.queue = new MessageQueue(loadQueue(), () => this.persistStats());
8207
+ this.queue = new MessageQueue(loadQueue(), () => {
8208
+ this.persistStats();
8209
+ this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
8210
+ });
8172
8211
  if (resumed) {
8173
8212
  this.emit("session_restored", {
8174
8213
  messageCount: this.state.messages.length,
8175
8214
  ...this.state.models && { models: this.state.models },
8176
8215
  modelSurfaces: MODEL_SURFACES,
8177
- allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8178
- ...this.queueFields()
8216
+ allowedModelsByType: ALLOWED_MODELS_BY_TYPE
8179
8217
  });
8180
8218
  }
8181
8219
  triggerBrandExtraction(
@@ -8225,7 +8263,7 @@ var init_headless = __esm({
8225
8263
  });
8226
8264
  process.on("SIGTERM", this.shutdown);
8227
8265
  process.on("SIGINT", this.shutdown);
8228
- this.emit("ready", this.queueFields());
8266
+ this.emit("ready");
8229
8267
  }
8230
8268
  shutdown = () => {
8231
8269
  this.emit("stopping");
@@ -8249,19 +8287,12 @@ var init_headless = __esm({
8249
8287
  }
8250
8288
  process.stdout.write(line);
8251
8289
  }
8252
- /**
8253
- * Emit a `completed` event and mark completedEmitted. Includes
8254
- * `queuedMessages` if the queue has items (sandbox uses this to know the
8255
- * agent is still busy with pipeline work).
8256
- */
8290
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
8291
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
8257
8292
  emitCompleted(rid, data) {
8258
- this.emit("completed", { ...data, ...this.queueFields() }, rid);
8293
+ this.emit("completed", { ...data }, rid);
8259
8294
  this.completedEmitted = true;
8260
8295
  }
8261
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
8262
- queueFields() {
8263
- return this.queue.length > 0 ? { queuedMessages: this.queue.snapshot() } : {};
8264
- }
8265
8296
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
8266
8297
  dispatchSimple(requestId, eventName, handler) {
8267
8298
  try {
@@ -8439,11 +8470,7 @@ var init_headless = __esm({
8439
8470
  });
8440
8471
  return;
8441
8472
  case "turn_cancelled": {
8442
- this.emit(
8443
- "completed",
8444
- { success: false, error: "cancelled", ...this.queueFields() },
8445
- rid
8446
- );
8473
+ this.emit("completed", { success: false, error: "cancelled" }, rid);
8447
8474
  this.completedEmitted = true;
8448
8475
  return;
8449
8476
  }
@@ -8567,7 +8594,7 @@ var init_headless = __esm({
8567
8594
  * message — `running` stays held across the queue drain so no user
8568
8595
  * message can slip in mid-pipeline.
8569
8596
  */
8570
- async runSingleTurn(parsed, requestId) {
8597
+ async runSingleTurn(parsed, requestId, fromChain = false) {
8571
8598
  this.currentRequestId = requestId;
8572
8599
  this.currentAbort = new AbortController();
8573
8600
  this.completedEmitted = false;
@@ -8615,16 +8642,18 @@ var init_headless = __esm({
8615
8642
  onboardingState,
8616
8643
  parsed.viewContext
8617
8644
  );
8618
- if (resolved?.next) {
8619
- this.queue.push({
8620
- command: {
8621
- action: "message",
8622
- text: sentinel(resolved.next),
8623
- onboardingState
8624
- },
8625
- source: "chain",
8626
- enqueuedAt: Date.now()
8627
- });
8645
+ if (resolved?.next && !fromChain) {
8646
+ for (const step of getActionChain(resolved.next)) {
8647
+ this.queue.push({
8648
+ command: {
8649
+ action: "message",
8650
+ text: sentinel(step),
8651
+ onboardingState
8652
+ },
8653
+ source: "chain",
8654
+ enqueuedAt: Date.now()
8655
+ });
8656
+ }
8628
8657
  }
8629
8658
  try {
8630
8659
  await runTurn({
@@ -8682,11 +8711,6 @@ var init_headless = __esm({
8682
8711
  source: "user",
8683
8712
  enqueuedAt: Date.now()
8684
8713
  });
8685
- this.emit(
8686
- "queued",
8687
- { position: this.queue.length, ...this.queueFields() },
8688
- requestId
8689
- );
8690
8714
  return;
8691
8715
  }
8692
8716
  this.running = true;
@@ -8734,7 +8758,7 @@ var init_headless = __esm({
8734
8758
  continue;
8735
8759
  }
8736
8760
  const nextRid = next.command.requestId ?? `${next.source}-${Date.now()}`;
8737
- await this.runSingleTurn(next.command, nextRid);
8761
+ await this.runSingleTurn(next.command, nextRid, next.source === "chain");
8738
8762
  }
8739
8763
  }
8740
8764
  /**
@@ -8798,6 +8822,17 @@ var init_headless = __esm({
8798
8822
  }
8799
8823
  return this.queue.drain();
8800
8824
  }
8825
+ /**
8826
+ * Remove pending queued messages — all user messages, or one by id.
8827
+ * Only `source: 'user'` items are removable; chained and background
8828
+ * messages are part of a system chain and are never cancellable. Does
8829
+ * not affect the in-flight turn (use `cancel` for that).
8830
+ */
8831
+ handleCancelQueued(id) {
8832
+ return this.queue.removeWhere(
8833
+ (item) => item.source === "user" && (id === void 0 || item.command.requestId === id)
8834
+ );
8835
+ }
8801
8836
  //////////////////////////////////////////////////////////////////////////////
8802
8837
  // Stdin router
8803
8838
  //////////////////////////////////////////////////////////////////////////////
@@ -8862,7 +8897,11 @@ var init_headless = __esm({
8862
8897
  ...this.state.models && { models: this.state.models },
8863
8898
  modelSurfaces: MODEL_SURFACES,
8864
8899
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8865
- ...this.queueFields()
8900
+ // Current queue snapshot for connect/reconnect — get_history is the
8901
+ // on-demand "current state" query. Always an array (possibly empty),
8902
+ // matching the queue_changed convention so the client reconciles the
8903
+ // same way from both; live mutations are pushed via queue_changed.
8904
+ queuedMessages: this.queue.snapshot()
8866
8905
  }));
8867
8906
  return;
8868
8907
  }
@@ -8906,6 +8945,16 @@ var init_headless = __esm({
8906
8945
  );
8907
8946
  return;
8908
8947
  }
8948
+ if (action === "cancelQueued") {
8949
+ const id = parsed.id;
8950
+ const removed = this.handleCancelQueued(id);
8951
+ this.emit(
8952
+ "completed",
8953
+ { success: true, cancelledQueued: removed },
8954
+ requestId
8955
+ );
8956
+ return;
8957
+ }
8909
8958
  if (action === "stop_tool") {
8910
8959
  const id = parsed.id;
8911
8960
  const mode = parsed.mode ?? "hard";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.200",
3
+ "version": "0.1.201",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",