@love-moon/conductor-sdk 0.7.6 → 0.7.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @love-moon/conductor-sdk
2
2
 
3
+ ## 0.7.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 400f3a7: Report a terminal task status when a stop request finds no active process, so a
8
+ task whose Fire already died converges instead of sitting in `killing` forever.
9
+
10
+ Drop queued terminal status events before an in-place restart reuses a working
11
+ directory. The durable upstream outbox lives inside that directory, so an
12
+ undelivered `KILLED` from the previous run was flushed on startup and killed the
13
+ task that had just finished resuming.
14
+
3
15
  ## 0.7.6
4
16
 
5
17
  ### Patch Changes
@@ -21,6 +21,30 @@ export declare class DurableUpstreamOutboxStore {
21
21
  payload: Record<string, unknown>;
22
22
  }): DurableUpstreamEvent;
23
23
  remove(stableId: string): void;
24
+ /**
25
+ * Discard queued terminal `task_status_update` events (KILLED / COMPLETED).
26
+ *
27
+ * This outbox lives INSIDE the fire's working directory, and an in-place
28
+ * restart deliberately re-uses that same directory (the daemon sets
29
+ * `CONDUCTOR_RESUME_CWD` so `--resume` finds the original project). That
30
+ * means a terminal status the PREVIOUS run failed to deliver — e.g. its
31
+ * websocket dropped while it was being stopped — is still sitting on disk
32
+ * when the NEXT run starts, and the startup flush delivers it against the
33
+ * task that was just brought back to life. The server marks the task
34
+ * `killed`, then answers the newly-attached fire with
35
+ * `stop_task(task_already_killed)` and shoots it down seconds after it
36
+ * finished resuming.
37
+ *
38
+ * A terminal status from a superseded run is definitionally obsolete: the
39
+ * task has since been restarted, so "that run ended" is no longer news the
40
+ * server can act on correctly. Drop those and keep everything else —
41
+ * `sdk_message` entries are real conversation content that must still be
42
+ * delivered, and `task_stop_ack` / `agent_command_ack` are idempotent
43
+ * bookkeeping whose delivery actually clears stale server-side outbox rows.
44
+ *
45
+ * Returns the dropped entries so callers can log exactly what was discarded.
46
+ */
47
+ dropPendingTerminalStatusEvents(): DurableUpstreamEvent[];
24
48
  markRetry(stableId: string, delayMs: number): DurableUpstreamEvent | null;
25
49
  listReady(nowMs?: number): DurableUpstreamEvent[];
26
50
  nextRetryDelay(nowMs?: number): number | null;
@@ -19,6 +19,16 @@ const sleepSync = (ms) => {
19
19
  }
20
20
  }
21
21
  };
22
+ // Terminal statuses as published by `conductor daemon` (see cli/src/daemon.js,
23
+ // which sends the upper-case wire form). Compared case-insensitively so a
24
+ // future lower-case producer does not silently bypass the check.
25
+ const TERMINAL_TASK_STATUSES = new Set(['killed', 'completed']);
26
+ const isTerminalStatusEvent = (entry) => {
27
+ if (entry.eventType !== 'task_status_update')
28
+ return false;
29
+ const status = entry.payload?.status;
30
+ return typeof status === 'string' && TERMINAL_TASK_STATUSES.has(status.trim().toLowerCase());
31
+ };
22
32
  const sanitizeScopeId = (scopeId) => scopeId
23
33
  .trim()
24
34
  .replace(/[^a-zA-Z0-9._-]+/g, '_')
@@ -66,6 +76,40 @@ export class DurableUpstreamOutboxStore {
66
76
  this.saveUnlocked(entries);
67
77
  });
68
78
  }
79
+ /**
80
+ * Discard queued terminal `task_status_update` events (KILLED / COMPLETED).
81
+ *
82
+ * This outbox lives INSIDE the fire's working directory, and an in-place
83
+ * restart deliberately re-uses that same directory (the daemon sets
84
+ * `CONDUCTOR_RESUME_CWD` so `--resume` finds the original project). That
85
+ * means a terminal status the PREVIOUS run failed to deliver — e.g. its
86
+ * websocket dropped while it was being stopped — is still sitting on disk
87
+ * when the NEXT run starts, and the startup flush delivers it against the
88
+ * task that was just brought back to life. The server marks the task
89
+ * `killed`, then answers the newly-attached fire with
90
+ * `stop_task(task_already_killed)` and shoots it down seconds after it
91
+ * finished resuming.
92
+ *
93
+ * A terminal status from a superseded run is definitionally obsolete: the
94
+ * task has since been restarted, so "that run ended" is no longer news the
95
+ * server can act on correctly. Drop those and keep everything else —
96
+ * `sdk_message` entries are real conversation content that must still be
97
+ * delivered, and `task_stop_ack` / `agent_command_ack` are idempotent
98
+ * bookkeeping whose delivery actually clears stale server-side outbox rows.
99
+ *
100
+ * Returns the dropped entries so callers can log exactly what was discarded.
101
+ */
102
+ dropPendingTerminalStatusEvents() {
103
+ return this.withLock(() => {
104
+ const entries = this.loadUnlocked();
105
+ const dropped = entries.filter(isTerminalStatusEvent);
106
+ if (dropped.length === 0) {
107
+ return [];
108
+ }
109
+ this.saveUnlocked(entries.filter((entry) => !isTerminalStatusEvent(entry)));
110
+ return dropped;
111
+ });
112
+ }
69
113
  markRetry(stableId, delayMs) {
70
114
  return this.withLock(() => {
71
115
  const entries = this.loadUnlocked();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/conductor-sdk",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/lovemoon-ai/conductor.git"
@@ -33,5 +33,5 @@
33
33
  "typescript": "^5.6.3",
34
34
  "vitest": "^2.1.4"
35
35
  },
36
- "gitCommitId": "c939df2"
36
+ "gitCommitId": "26b2dbb"
37
37
  }