@duetso/agent 0.1.62 → 0.1.64

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
@@ -501,11 +501,13 @@ import { TurnRunner } from "@duetso/agent";
501
501
 
502
502
  const turnRunner = new TurnRunner({
503
503
  model: "opus-4.7",
504
- memoryDbPath: false, // Keep observational memory in process only.
504
+ memoryDbPath: false, // Disables observational memory and compaction.
505
505
  });
506
506
  ```
507
507
 
508
- By default, the CLI stores durable observations in `~/.duet/memory.db`; run it with `--incognito` (or `-i`) to keep observational memory in process only. Programmatic callers can pass `memoryDbPath: false` or provide a custom `memoryDbPath`. The CLI's `SessionManager` is a convenience layer that stores session snapshots under `~/.duet/sessions`, but the runner owns memory hydration, pi-turn observation/reflection, compaction, and observation persistence.
508
+ By default, the CLI stores durable observations in `~/.duet/memory.db`; run it with `--incognito` (or `-i`) to set `memoryDbPath: false`, which disables observational memory and compaction for that session. Programmatic callers can pass `memoryDbPath: false` or provide a custom `memoryDbPath`. The CLI's `SessionManager` is a convenience layer that stores session snapshots under `~/.duet/sessions`, but the runner owns memory hydration, pi-turn observation/reflection, compaction, and observation persistence.
509
+
510
+ **We strongly recommend running with a `memoryDbPath`, which is the default.** Compaction is implemented as part of the observational memory pipeline: raw transcript content is replaced by observations/reflections that the observer and reflector write to the durable store. Without a database, the runner skips that pipeline entirely — there is no compaction, the transcript grows unbounded against the raw model context window, and long sessions will eventually hit a provider context-length error. `memoryDbPath: false` is appropriate for short-lived scripts, tests, and incognito runs that stay well under the model window.
509
511
 
510
512
  You can also resume directly from saved state. The runner owns state
511
513
  internally after `start`, so resumed state is handed in through the start
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duetso/agent",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "description": "An opinionated full-stack agent turn runner with native memories, interrupts, and multi-agent orchestration",
5
5
  "keywords": [
6
6
  "agent",
@@ -0,0 +1,147 @@
1
+ import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
+ import { type PackageManager } from "./package-manager.js";
3
+ /**
4
+ * Why this exists: the CLI ships several versions per hour. Manual `duet
5
+ * upgrade` lags behind, so each launch probes the registry and — when a
6
+ * newer version exists — runs the package manager's global install in the
7
+ * background while the user is already using the TUI. The next launch is
8
+ * then already on latest.
9
+ *
10
+ * Concurrency safety:
11
+ * - The package manager mutates the global install path in place. Already-
12
+ * running CLI processes do not re-read those files, so they are unaffected.
13
+ * - A CLI that *starts* mid-install could see partial files. We minimize
14
+ * that window with an exclusive lockfile so only one process upgrades at
15
+ * a time; others fall through and observe whatever state currently exists.
16
+ * - Stale locks (worker crash, OS kill) older than STALE_LOCK_MS are
17
+ * reclaimed so a missed unlink does not permanently disable upgrades.
18
+ *
19
+ * Status stream: `runAutoUpgrade` emits each transition through `onStatus`
20
+ * so the TUI can render a live "Checking… / Upgrading… / Upgraded, restart"
21
+ * line in the header. The returned promise resolves once the final state
22
+ * is reached.
23
+ */
24
+ /** Env var users set to disable the auto-upgrade entirely. */
25
+ export declare const NO_AUTO_UPGRADE_ENV = "DUET_NO_AUTO_UPGRADE";
26
+ /**
27
+ * Live status stream emitted while the upgrade runs. Designed so the TUI
28
+ * can render one line that mutates in place; each variant carries the
29
+ * fields needed to phrase the line without re-deriving them.
30
+ */
31
+ export type UpgradeStatus = {
32
+ kind: "checking";
33
+ } | {
34
+ kind: "current";
35
+ version: string;
36
+ } | {
37
+ kind: "upgrading";
38
+ from: string;
39
+ to: string;
40
+ manager: PackageManager;
41
+ } | {
42
+ kind: "upgraded";
43
+ from: string;
44
+ to: string;
45
+ manager: PackageManager;
46
+ } | {
47
+ kind: "failed";
48
+ from: string;
49
+ to: string;
50
+ manager: PackageManager;
51
+ error: string;
52
+ } | {
53
+ kind: "locked";
54
+ } | {
55
+ kind: "skipped";
56
+ reason: "disabled" | "source-checkout" | "registry-unreachable";
57
+ };
58
+ export type UpgradeStatusListener = (status: UpgradeStatus) => void;
59
+ /**
60
+ * A pub/sub handle the TUI subscribes to so it can render the live upgrade
61
+ * line. `publish` is called once per intermediate status; `complete` is
62
+ * called with the final status so late subscribers (e.g. the TUI mounting
63
+ * after the upgrade already finished) still see the terminal state.
64
+ */
65
+ export interface UpgradeStatusStream {
66
+ subscribe(listener: UpgradeStatusListener): () => void;
67
+ publish(status: UpgradeStatus): void;
68
+ complete(final: UpgradeStatus): void;
69
+ /** Latest status emitted, or undefined before the first publish. */
70
+ current(): UpgradeStatus | undefined;
71
+ }
72
+ export declare function createUpgradeStatusStream(): UpgradeStatusStream;
73
+ export interface RunAutoUpgradeInput {
74
+ packageName: string;
75
+ currentVersion: string;
76
+ /** Callback fired on every status transition. Never throws back into caller. */
77
+ onStatus?: UpgradeStatusListener;
78
+ /** Suppresses the run regardless of env (used after `--no-auto-upgrade`). */
79
+ disabled?: boolean;
80
+ /** process.argv[1] — used to detect source-checkout invocations. */
81
+ scriptPath?: string;
82
+ /** process.env at call site; lets tests inject a controlled environment. */
83
+ env?: NodeJS.ProcessEnv;
84
+ /** Override for tests; defaults to fetchLatestPackageVersion. */
85
+ fetchLatest?: (packageName: string) => Promise<string | undefined>;
86
+ /** Override for tests; defaults to spawning the real package manager. */
87
+ runUpgrade?: (command: string[]) => Promise<{
88
+ ok: boolean;
89
+ stderr?: string;
90
+ }>;
91
+ /** Override for tests. */
92
+ now?: () => number;
93
+ /** Override for tests. */
94
+ detectManager?: () => PackageManager;
95
+ }
96
+ /**
97
+ * Run the auto-upgrade flow in-process. Emits status transitions through
98
+ * `onStatus` and resolves to the final status. Never throws — every error
99
+ * path produces a terminal status the caller can render.
100
+ */
101
+ export declare function runAutoUpgrade(input: RunAutoUpgradeInput): Promise<UpgradeStatus>;
102
+ /**
103
+ * Heuristic: only run auto-upgrade when the CLI looks like a globally
104
+ * installed binary. Source-checkout runs (`bun src/cli.ts`) and per-project
105
+ * installs are skipped so contributors never get their working copy
106
+ * overwritten by `npm install --global`.
107
+ */
108
+ export declare function isLikelyGlobalInstall(scriptPath: string): boolean;
109
+ /**
110
+ * Human-readable one-liner for any UpgradeStatus. Used by both the JSON
111
+ * stderr path and the TUI header so the wording stays consistent.
112
+ */
113
+ export declare function describeUpgradeStatus(packageName: string, status: UpgradeStatus): string | undefined;
114
+ /** Resolved lazily so tests can swap $HOME per case. */
115
+ declare function upgradePaths(): {
116
+ duetDir: string;
117
+ lockPath: string;
118
+ logPath: string;
119
+ };
120
+ /**
121
+ * Spawn options chosen so the install survives the parent CLI exiting:
122
+ * - `detached: true` puts npm in its own process group so a terminal
123
+ * Ctrl+C on the parent does not propagate and abort the install
124
+ * mid-write (which would leave the global node_modules tree corrupt).
125
+ * - The returned child is also `unref()`d (and so is its stderr pipe)
126
+ * so a clean `/exit` returns the shell immediately and the install
127
+ * continues independently. The parent still observes the child's exit
128
+ * event for as long as it is alive, so the TUI can render the
129
+ * "Updated… restart duet" line when the user stays.
130
+ */
131
+ export declare const PACKAGE_MANAGER_SPAWN_OPTIONS: SpawnOptions;
132
+ export type SpawnFn = (command: string, args: string[], options: SpawnOptions) => ChildProcess;
133
+ /**
134
+ * Default in-process runner for the package manager upgrade command.
135
+ * Exported so tests can verify the spawn contract (detached, unref'd) by
136
+ * injecting a fake spawn.
137
+ */
138
+ export declare function defaultRunUpgrade(command: string[], spawn?: SpawnFn): Promise<{
139
+ ok: boolean;
140
+ stderr?: string;
141
+ }>;
142
+ export declare const __testing: {
143
+ upgradePaths: typeof upgradePaths;
144
+ STALE_LOCK_MS: number;
145
+ };
146
+ export {};
147
+ //# sourceMappingURL=auto-upgrade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-upgrade.d.ts","sourceRoot":"","sources":["../../../src/cli/auto-upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAY9F,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAG9B;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAU1D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACxE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACvE;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,UAAU,GAAG,iBAAiB,GAAG,sBAAsB,CAAC;CACjE,CAAC;AAEN,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;IACvD,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,oEAAoE;IACpE,OAAO,IAAI,aAAa,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,yBAAyB,IAAI,mBAAmB,CA6B/D;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnE,yEAAyE;IACzE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9E,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,cAAc,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAyEvF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAiBjE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,GACpB,MAAM,GAAG,SAAS,CAiBpB;AAOD,wDAAwD;AACxD,iBAAS,YAAY,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAO9E;AAsED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B,EAAE,YAG3C,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;AAE/F;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EAAE,EACjB,KAAK,GAAE,OAAmB,GACzB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoB3C;AAGD,eAAO,MAAM,SAAS;;;CAGrB,CAAC"}
@@ -0,0 +1,314 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ import { appendFileSync, closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync, } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { detectPackageManagerFromContext, globalUpgradeCommand, } from "./package-manager.js";
6
+ import { compareSemverVersions, fetchLatestPackageVersion } from "./version-check.js";
7
+ /**
8
+ * Why this exists: the CLI ships several versions per hour. Manual `duet
9
+ * upgrade` lags behind, so each launch probes the registry and — when a
10
+ * newer version exists — runs the package manager's global install in the
11
+ * background while the user is already using the TUI. The next launch is
12
+ * then already on latest.
13
+ *
14
+ * Concurrency safety:
15
+ * - The package manager mutates the global install path in place. Already-
16
+ * running CLI processes do not re-read those files, so they are unaffected.
17
+ * - A CLI that *starts* mid-install could see partial files. We minimize
18
+ * that window with an exclusive lockfile so only one process upgrades at
19
+ * a time; others fall through and observe whatever state currently exists.
20
+ * - Stale locks (worker crash, OS kill) older than STALE_LOCK_MS are
21
+ * reclaimed so a missed unlink does not permanently disable upgrades.
22
+ *
23
+ * Status stream: `runAutoUpgrade` emits each transition through `onStatus`
24
+ * so the TUI can render a live "Checking… / Upgrading… / Upgraded, restart"
25
+ * line in the header. The returned promise resolves once the final state
26
+ * is reached.
27
+ */
28
+ /** Env var users set to disable the auto-upgrade entirely. */
29
+ export const NO_AUTO_UPGRADE_ENV = "DUET_NO_AUTO_UPGRADE";
30
+ /** A lock held longer than this is treated as abandoned and replaced. */
31
+ const STALE_LOCK_MS = 10 * 60 * 1000;
32
+ /** How long to wait on the registry. The TUI is already up and showing a
33
+ * "checking…" placeholder, so we can wait longer than the legacy inline
34
+ * notice without making the user feel the call. */
35
+ const REGISTRY_TIMEOUT_MS = 8_000;
36
+ export function createUpgradeStatusStream() {
37
+ const listeners = new Set();
38
+ let latest;
39
+ const publish = (status) => {
40
+ latest = status;
41
+ for (const listener of listeners) {
42
+ try {
43
+ listener(status);
44
+ }
45
+ catch {
46
+ // Listener errors must not break the upgrade flow.
47
+ }
48
+ }
49
+ };
50
+ return {
51
+ subscribe(listener) {
52
+ listeners.add(listener);
53
+ // Replay the most recent status so late subscribers do not miss the
54
+ // current state — e.g. if the upgrade finished before the TUI mounted.
55
+ if (latest)
56
+ listener(latest);
57
+ return () => listeners.delete(listener);
58
+ },
59
+ publish,
60
+ complete(final) {
61
+ if (latest !== final)
62
+ publish(final);
63
+ },
64
+ current() {
65
+ return latest;
66
+ },
67
+ };
68
+ }
69
+ /**
70
+ * Run the auto-upgrade flow in-process. Emits status transitions through
71
+ * `onStatus` and resolves to the final status. Never throws — every error
72
+ * path produces a terminal status the caller can render.
73
+ */
74
+ export async function runAutoUpgrade(input) {
75
+ const emit = (status) => {
76
+ try {
77
+ input.onStatus?.(status);
78
+ }
79
+ catch {
80
+ // Listener errors must never affect the upgrade flow.
81
+ }
82
+ return status;
83
+ };
84
+ const env = input.env ?? process.env;
85
+ if (input.disabled || env[NO_AUTO_UPGRADE_ENV] === "1") {
86
+ return emit({ kind: "skipped", reason: "disabled" });
87
+ }
88
+ const scriptPath = input.scriptPath ?? process.argv[1];
89
+ if (!scriptPath || !isLikelyGlobalInstall(scriptPath)) {
90
+ return emit({ kind: "skipped", reason: "source-checkout" });
91
+ }
92
+ const now = input.now ?? (() => Date.now());
93
+ const handle = acquireLock(now());
94
+ if (handle === null) {
95
+ appendLog(`skip locked package=${input.packageName} current=${input.currentVersion}`);
96
+ return emit({ kind: "locked" });
97
+ }
98
+ try {
99
+ emit({ kind: "checking" });
100
+ const fetchLatest = input.fetchLatest ?? ((name) => fetchLatestPackageVersion(name, REGISTRY_TIMEOUT_MS));
101
+ let latest;
102
+ try {
103
+ latest = await fetchLatest(input.packageName);
104
+ }
105
+ catch (error) {
106
+ appendLog(`fetch-error package=${input.packageName} error=${describeError(error)}`);
107
+ return emit({ kind: "skipped", reason: "registry-unreachable" });
108
+ }
109
+ if (!latest) {
110
+ appendLog(`no-latest package=${input.packageName}`);
111
+ return emit({ kind: "skipped", reason: "registry-unreachable" });
112
+ }
113
+ if (compareSemverVersions(latest, input.currentVersion) <= 0) {
114
+ appendLog(`current package=${input.packageName} version=${input.currentVersion}`);
115
+ return emit({ kind: "current", version: input.currentVersion });
116
+ }
117
+ const detect = input.detectManager ??
118
+ (() => detectPackageManagerFromContext({
119
+ userAgent: process.env.npm_config_user_agent,
120
+ runtimeExecutable: process.argv[0],
121
+ scriptPath: process.argv[1],
122
+ }));
123
+ const manager = detect();
124
+ emit({ kind: "upgrading", from: input.currentVersion, to: latest, manager });
125
+ const command = globalUpgradeCommand(manager, input.packageName, latest);
126
+ const run = input.runUpgrade ?? defaultRunUpgrade;
127
+ const result = await run(command);
128
+ if (result.ok) {
129
+ appendLog(`upgraded package=${input.packageName} from=${input.currentVersion} to=${latest} manager=${manager}`);
130
+ return emit({ kind: "upgraded", from: input.currentVersion, to: latest, manager });
131
+ }
132
+ const error = result.stderr ?? "package manager exited non-zero";
133
+ appendLog(`failed package=${input.packageName} from=${input.currentVersion} to=${latest} manager=${manager} stderr=${error}`);
134
+ return emit({ kind: "failed", from: input.currentVersion, to: latest, manager, error });
135
+ }
136
+ finally {
137
+ releaseLock(handle);
138
+ }
139
+ }
140
+ /**
141
+ * Heuristic: only run auto-upgrade when the CLI looks like a globally
142
+ * installed binary. Source-checkout runs (`bun src/cli.ts`) and per-project
143
+ * installs are skipped so contributors never get their working copy
144
+ * overwritten by `npm install --global`.
145
+ */
146
+ export function isLikelyGlobalInstall(scriptPath) {
147
+ const normalized = scriptPath.replace(/\\/g, "/");
148
+ if (normalized.includes("/.bun/install/global/") ||
149
+ normalized.includes("/.bun/bin/") ||
150
+ normalized.includes("/.pnpm/") ||
151
+ normalized.includes("/share/pnpm/") ||
152
+ normalized.includes("/.config/yarn/global/") ||
153
+ normalized.includes("/yarn/global/")) {
154
+ return true;
155
+ }
156
+ // npm global installs live under `<prefix>/lib/node_modules/<pkg>/...`.
157
+ if (/\/lib\/node_modules\//.test(normalized))
158
+ return true;
159
+ // Volta, fnm, nvm prefixes vary; treat any `node_modules/@duetso/` as global.
160
+ if (normalized.includes("/node_modules/@duetso/"))
161
+ return true;
162
+ return false;
163
+ }
164
+ /**
165
+ * Human-readable one-liner for any UpgradeStatus. Used by both the JSON
166
+ * stderr path and the TUI header so the wording stays consistent.
167
+ */
168
+ export function describeUpgradeStatus(packageName, status) {
169
+ switch (status.kind) {
170
+ case "checking":
171
+ return "Checking for updates…";
172
+ case "upgrading":
173
+ return `Updating ${packageName} ${status.from} → ${status.to}…`;
174
+ case "upgraded":
175
+ return `Updated ${packageName} to ${status.to}. Restart duet to use it.`;
176
+ case "failed":
177
+ return `Update to ${status.to} failed (${status.error}). Run: duet upgrade`;
178
+ case "locked":
179
+ case "current":
180
+ case "skipped":
181
+ // No notice — either we are on latest, another process is handling it,
182
+ // or auto-upgrade is intentionally off. Header stays clean.
183
+ return undefined;
184
+ }
185
+ }
186
+ /** Resolved lazily so tests can swap $HOME per case. */
187
+ function upgradePaths() {
188
+ const duetDir = join(homedir(), ".duet");
189
+ return {
190
+ duetDir,
191
+ lockPath: join(duetDir, "upgrade.lock"),
192
+ logPath: join(duetDir, "logs", "upgrade.log"),
193
+ };
194
+ }
195
+ function acquireLock(now) {
196
+ const { duetDir, lockPath } = upgradePaths();
197
+ ensureDir(duetDir);
198
+ for (let attempt = 0; attempt < 2; attempt++) {
199
+ try {
200
+ const fd = openSync(lockPath, "wx");
201
+ writeSync(fd, JSON.stringify({ pid: process.pid, startedAt: now }));
202
+ return { fd, lockPath };
203
+ }
204
+ catch (error) {
205
+ if (error?.code !== "EEXIST")
206
+ return null;
207
+ if (!isStaleLock(lockPath, now))
208
+ return null;
209
+ try {
210
+ unlinkSync(lockPath);
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+ function isStaleLock(lockPath, now) {
220
+ try {
221
+ const raw = readFileSync(lockPath, "utf8");
222
+ const payload = JSON.parse(raw);
223
+ if (typeof payload.startedAt !== "number")
224
+ return true;
225
+ return now - payload.startedAt > STALE_LOCK_MS;
226
+ }
227
+ catch {
228
+ return true;
229
+ }
230
+ }
231
+ function releaseLock(handle) {
232
+ try {
233
+ closeSync(handle.fd);
234
+ }
235
+ catch {
236
+ // ignore
237
+ }
238
+ try {
239
+ unlinkSync(handle.lockPath);
240
+ }
241
+ catch {
242
+ // ignore
243
+ }
244
+ }
245
+ function ensureDir(path) {
246
+ try {
247
+ mkdirSync(path, { recursive: true });
248
+ }
249
+ catch {
250
+ // ignore
251
+ }
252
+ }
253
+ function appendLog(line) {
254
+ try {
255
+ const { logPath } = upgradePaths();
256
+ ensureDir(join(logPath, ".."));
257
+ appendFileSync(logPath, `${new Date().toISOString()} ${line}\n`);
258
+ }
259
+ catch {
260
+ // ignore
261
+ }
262
+ }
263
+ function describeError(error) {
264
+ if (error instanceof Error)
265
+ return error.message;
266
+ return String(error);
267
+ }
268
+ /**
269
+ * Spawn options chosen so the install survives the parent CLI exiting:
270
+ * - `detached: true` puts npm in its own process group so a terminal
271
+ * Ctrl+C on the parent does not propagate and abort the install
272
+ * mid-write (which would leave the global node_modules tree corrupt).
273
+ * - The returned child is also `unref()`d (and so is its stderr pipe)
274
+ * so a clean `/exit` returns the shell immediately and the install
275
+ * continues independently. The parent still observes the child's exit
276
+ * event for as long as it is alive, so the TUI can render the
277
+ * "Updated… restart duet" line when the user stays.
278
+ */
279
+ export const PACKAGE_MANAGER_SPAWN_OPTIONS = {
280
+ stdio: ["ignore", "ignore", "pipe"],
281
+ detached: true,
282
+ };
283
+ /**
284
+ * Default in-process runner for the package manager upgrade command.
285
+ * Exported so tests can verify the spawn contract (detached, unref'd) by
286
+ * injecting a fake spawn.
287
+ */
288
+ export function defaultRunUpgrade(command, spawn = nodeSpawn) {
289
+ return new Promise((resolve) => {
290
+ const child = spawn(command[0], command.slice(1), PACKAGE_MANAGER_SPAWN_OPTIONS);
291
+ child.unref();
292
+ // The stderr pipe is a separate libuv handle that also pins the event
293
+ // loop alive; unref it so /exit returns even while the install is still
294
+ // streaming output. Node exposes `unref` on the underlying socket; cast
295
+ // because @types/node does not surface it on the Readable interface.
296
+ child.stderr?.unref?.();
297
+ let stderr = "";
298
+ child.stderr?.on("data", (chunk) => {
299
+ stderr += chunk.toString();
300
+ });
301
+ child.once("error", (error) => {
302
+ resolve({ ok: false, stderr: describeError(error) });
303
+ });
304
+ child.once("exit", (code) => {
305
+ resolve({ ok: code === 0, stderr: stderr.trim() || undefined });
306
+ });
307
+ });
308
+ }
309
+ // Test-only access to internal paths and constants.
310
+ export const __testing = {
311
+ upgradePaths,
312
+ STALE_LOCK_MS,
313
+ };
314
+ //# sourceMappingURL=auto-upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-upgrade.js","sourceRoot":"","sources":["../../../src/cli/auto-upgrade.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/cli/help.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA2EtD;AAED,wBAAgB,cAAc,IAAI,IAAI,CA0BrC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAsBnC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAkBtC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAoBtC;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAa1D"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/cli/help.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA4EtD;AAED,wBAAgB,cAAc,IAAI,IAAI,CA0BrC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAsBnC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAkBtC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAoBtC;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAa1D"}
@@ -42,6 +42,7 @@ OPTIONS
42
42
  Load a file into the system prompt; repeatable
43
43
  --no-system-prompt-files Disable default AGENTS.md system prompt loading
44
44
  --env-file <path> Shared env file to load after <workdir>/.env (default: ${DEFAULT_DUET_ENV_FILE})
45
+ --no-auto-upgrade Skip the auto-upgrade probe for this run (also: DUET_NO_AUTO_UPGRADE=1)
45
46
  --json Force JSONL event output instead of the TUI
46
47
  -v, --version Print the installed duet version and exit
47
48
  -h, --help Show this help
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,CAAC;AAEjD,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;gDAiBkC,WAAW;;;;;;;;;;;;gHAYqD,+BAA+B;;;;;oFAK3D,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCxG,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;wEAW0D,qBAAqB;;;;;;;;;;;;;CAa5F,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;yDAa2C,qBAAqB;;;;;;IAM1E,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;CAChC,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;CAgBb,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;CAkBb,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,CAAC,GAAG,CAAC;oCACsB,WAAW;;;;;;;;;;CAU9C,CAAC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,CAAC;AAEjD,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;gDAiBkC,WAAW;;;;;;;;;;;;gHAYqD,+BAA+B;;;;;oFAK3D,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCxG,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;wEAW0D,qBAAqB;;;;;;;;;;;;;CAa5F,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;yDAa2C,qBAAqB;;;;;;IAM1E,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;CAChC,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;CAgBb,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;CAkBb,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,CAAC,GAAG,CAAC;oCACsB,WAAW;;;;;;;;;;CAU9C,CAAC,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/run.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,iCAAiC,CAAC;AAGzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAO3D,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,qBAAqB,EAAE,eAAe,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAEV;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,uBAAuB,CAgBzB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA2PvF"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/run.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,iCAAiC,CAAC;AAGzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,qBAAqB,EAAE,eAAe,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAEV;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,uBAAuB,CAgBzB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA2QvF"}
@@ -9,7 +9,7 @@ import { DEFAULT_RESUME_HISTORY_MESSAGES, printRunHelp } from "./help.js";
9
9
  import { resumeCommand } from "./resume-hint.js";
10
10
  import { fail, isInteractive, loadCliEnvFiles, parseResumeHistoryMessages } from "./shared.js";
11
11
  import { installShutdownHandlers } from "./shutdown.js";
12
- import { getNewVersionNotice } from "./version-check.js";
12
+ import { createUpgradeStatusStream, describeUpgradeStatus, runAutoUpgrade, } from "./auto-upgrade.js";
13
13
  /**
14
14
  * Decide whether to render the interactive TUI vs JSONL events.
15
15
  *
@@ -60,13 +60,9 @@ export async function runRunCommand(args, pkg) {
60
60
  let jsonOutput = false;
61
61
  let envFilePath;
62
62
  let incognito = false;
63
+ let noAutoUpgrade = false;
63
64
  const promptParts = [];
64
65
  const interactive = isInteractive();
65
- // Kick off the npm registry probe immediately so it overlaps with env
66
- // loading, model resolution, skill discovery, and session bootstrap. JSON
67
- // callers await the result; the TUI path only consumes it if it has already
68
- // settled by the time we render, so a slow registry never delays first paint.
69
- const versionNoticePromise = getNewVersionNotice(pkg.name, pkg.version).catch(() => undefined);
70
66
  for (let i = 0; i < args.length; i++) {
71
67
  switch (args[i]) {
72
68
  case "--model":
@@ -133,6 +129,9 @@ export async function runRunCommand(args, pkg) {
133
129
  fail(`Missing value for ${args[i]}`);
134
130
  envFilePath = args[++i];
135
131
  break;
132
+ case "--no-auto-upgrade":
133
+ noAutoUpgrade = true;
134
+ break;
136
135
  case "--version":
137
136
  case "-v":
138
137
  console.log(pkg.version);
@@ -183,6 +182,20 @@ export async function runRunCommand(args, pkg) {
183
182
  }
184
183
  const dotenvKeys = loadCliEnvFiles(workDir, envFilePath);
185
184
  shimDuetApiKeyToAiGateway();
185
+ // Probe the registry and (if newer) run the package manager in-process
186
+ // while the TUI is already mounted. The TUI subscribes to upgradeStatus$
187
+ // to render a live header line; the JSON path awaits the final status
188
+ // and prints one summary line to stderr.
189
+ const upgradeStatus$ = createUpgradeStatusStream();
190
+ const upgradePromise = runAutoUpgrade({
191
+ packageName: pkg.name,
192
+ currentVersion: pkg.version,
193
+ disabled: noAutoUpgrade,
194
+ onStatus: (status) => upgradeStatus$.publish(status),
195
+ }).then((status) => {
196
+ upgradeStatus$.complete(status);
197
+ return status;
198
+ });
186
199
  // Refresh the gateway-managed default skills when the user has previously
187
200
  // opted in via `duet login` (i.e. `~/.duet/.skills-hash` exists). Logging
188
201
  // in with --skip-skill-sync leaves no hash, so this stays a no-op until
@@ -203,13 +216,15 @@ export async function runRunCommand(args, pkg) {
203
216
  memoryModelName = memoryModelResolution.modelName;
204
217
  const useTui = shouldUseTui({ interactive, jsonOutput, prompt });
205
218
  const useJson = !useTui;
206
- // JSON consumers are already waiting for stderr output, so blocking on the
207
- // probe is fine. The TUI never blocks here it consumes the promise
208
- // directly and swaps in the notice once the probe settles.
219
+ // JSON consumers want a single summary line, not a streaming status.
220
+ // Await the final upgrade status and print the human-readable form (if any)
221
+ // before the regular boot lines. The TUI subscribes to the live stream
222
+ // instead and renders intermediate "Checking…/Updating…" states inline.
209
223
  if (useJson) {
210
- const newVersionNotice = await versionNoticePromise;
211
- if (newVersionNotice)
212
- process.stderr.write(`${newVersionNotice}\n`);
224
+ const finalStatus = await upgradePromise;
225
+ const notice = describeUpgradeStatus(pkg.name, finalStatus);
226
+ if (notice)
227
+ process.stderr.write(`${notice}\n`);
213
228
  process.stderr.write(`Model: ${modelName}\n`);
214
229
  process.stderr.write(`Source: ${describeModelResolution(modelResolution)}\n`);
215
230
  process.stderr.write(`Memory model: ${memoryModelName}\n`);
@@ -263,8 +278,9 @@ export async function runRunCommand(args, pkg) {
263
278
  memoryModelSource: describeModelResolution(memoryModelResolution),
264
279
  workDir,
265
280
  sessionId: session.id,
281
+ packageName: pkg.name,
266
282
  packageVersion: pkg.version,
267
- versionNoticePromise,
283
+ upgradeStatus$,
268
284
  });
269
285
  }
270
286
  process.stderr.write(`To resume this session:\n${resumeCommand(session.id, {