@saadnvd1/agent-os 0.3.680 → 0.3.691

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 (148) hide show
  1. package/README.md +2 -2
  2. package/app/api/dev-servers/detect/route.ts +1 -2
  3. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +2 -4
  4. package/app/page.tsx +1 -2
  5. package/components/Artifacts/ArtifactsPanel.tsx +1 -1
  6. package/components/FileExplorer/FileTabs.tsx +1 -1
  7. package/components/Pane/DesktopTabBar.tsx +2 -2
  8. package/components/QuickSwitcher.tsx +24 -21
  9. package/components/SessionList/SessionRowMenu.tsx +0 -1
  10. package/components/Settings/ExperimentsSection.test.ts +7 -1
  11. package/components/Settings/NoApprovalsSection.test.ts +7 -1
  12. package/components/Terminal/TerminalToolbar.tsx +1 -1
  13. package/components/Terminal/VirtualKeyboard.tsx +1 -1
  14. package/components/WorkerCard.tsx +1 -7
  15. package/components/Workspaces/WorkspaceNameDialog.tsx +5 -3
  16. package/components/a/AButton.tsx +1 -6
  17. package/components/a/AIconButton.tsx +1 -5
  18. package/contexts/PaneContext.tsx +1 -6
  19. package/data/lumifyhub/index.ts +2 -4
  20. package/hooks/useDirectoryBrowser.ts +5 -4
  21. package/hooks/useDrawerAnimation.ts +11 -14
  22. package/hooks/useFileDrop.ts +2 -6
  23. package/hooks/useKeybarVisibility.ts +10 -29
  24. package/hooks/useSidebarPinned.ts +7 -23
  25. package/hooks/useSpeechRecognition.ts +19 -5
  26. package/hooks/useStoredFlag.ts +47 -0
  27. package/hooks/useViewport.ts +11 -17
  28. package/lib/approvals/no-approvals.ts +1 -7
  29. package/lib/artifacts/store.ts +1 -2
  30. package/lib/away/report.ts +1 -2
  31. package/lib/away/run.ts +1 -2
  32. package/lib/away/state.ts +1 -2
  33. package/lib/bookmarks/store.ts +1 -2
  34. package/lib/bus/delivery.ts +1 -2
  35. package/lib/bus/index.ts +1 -2
  36. package/lib/chat/compact-context.ts +1 -2
  37. package/lib/chat/stop.ts +1 -2
  38. package/lib/chat/worker/host.ts +1 -1
  39. package/lib/code-health/reports.ts +39 -3
  40. package/lib/code-health/source.ts +1 -1
  41. package/lib/db/lumifyhub.ts +2 -4
  42. package/lib/db/schedules-move.ts +1 -2
  43. package/lib/db/session-parts/tasks.ts +1 -2
  44. package/lib/db/stacks.ts +3 -9
  45. package/lib/db/types.ts +1 -7
  46. package/lib/dev-instance/spawnServer.ts +10 -6
  47. package/lib/doctor/checks.ts +8 -10
  48. package/lib/done/archive.ts +1 -2
  49. package/lib/env-setup.ts +2 -0
  50. package/lib/experiments/meter.ts +1 -7
  51. package/lib/files.ts +1 -0
  52. package/lib/health/env/classes.ts +0 -2
  53. package/lib/health/env/recordHit.ts +1 -2
  54. package/lib/health/spin.ts +1 -4
  55. package/lib/health/store.ts +1 -2
  56. package/lib/hosts/index.ts +1 -2
  57. package/lib/hosts/peer-sync.ts +2 -4
  58. package/lib/hosts/remote-api.ts +1 -2
  59. package/lib/lumifyhub/types.ts +2 -8
  60. package/lib/notify/index.ts +1 -2
  61. package/lib/notify/push/index.ts +2 -7
  62. package/lib/orchestrator/brief.ts +1 -1
  63. package/lib/orchestrator/external-pr.ts +1 -2
  64. package/lib/orchestrator/gates.ts +1 -6
  65. package/lib/orchestrator/needs-you-items.ts +16 -20
  66. package/lib/orchestrator/pr-wake.ts +1 -2
  67. package/lib/orchestrator/rebase.ts +1 -2
  68. package/lib/orchestrator/review.ts +1 -2
  69. package/lib/orchestrator/usage.ts +1 -2
  70. package/lib/plans/claim.ts +1 -2
  71. package/lib/plans/features.ts +4 -11
  72. package/lib/plans/release.ts +2 -4
  73. package/lib/ports.ts +1 -2
  74. package/lib/program-status/records.ts +1 -1
  75. package/lib/project-config/database.ts +1 -2
  76. package/lib/projects.ts +1 -2
  77. package/lib/recipes/settle.ts +2 -4
  78. package/lib/recipes/store.ts +2 -4
  79. package/lib/schedules/store.ts +2 -4
  80. package/lib/search/conversations/backfill.ts +2 -4
  81. package/lib/security/auth.ts +1 -2
  82. package/lib/security/passkeys.ts +1 -2
  83. package/lib/security/presence.ts +1 -7
  84. package/lib/session-artifacts/types.ts +1 -8
  85. package/lib/session-audit/transcript.ts +1 -3
  86. package/lib/session-logs/index.ts +1 -2
  87. package/lib/sessions/launch.ts +1 -2
  88. package/lib/share/pdf.ts +32 -1
  89. package/lib/share/snapshot.ts +1 -7
  90. package/lib/share/store.ts +1 -2
  91. package/lib/sidebar/orchestrator-row.ts +1 -6
  92. package/lib/sidebar/row-menu.ts +1 -2
  93. package/lib/sidebar/shelves.ts +1 -6
  94. package/lib/simulator/index.ts +1 -2
  95. package/lib/simulator/lease.ts +1 -5
  96. package/lib/stacks/index.ts +1 -1
  97. package/lib/stacks/plan.ts +1 -1
  98. package/lib/stacks/restack.ts +1 -2
  99. package/lib/stacks/tick.ts +1 -2
  100. package/lib/status/socket.ts +1 -2
  101. package/lib/tasks/contract/authorize.ts +1 -2
  102. package/lib/tasks/contract/plan.ts +1 -2
  103. package/lib/tasks/contract/sync.ts +1 -2
  104. package/lib/tasks/holds.ts +1 -5
  105. package/lib/tasks/index.ts +1 -2
  106. package/lib/tasks/loop-types.ts +1 -6
  107. package/lib/tasks/move-guard.ts +1 -2
  108. package/lib/tasks/move.ts +1 -2
  109. package/lib/tasks/moved-away.ts +1 -2
  110. package/lib/tasks/pause-hold.ts +1 -2
  111. package/lib/tasks/pause.ts +3 -6
  112. package/lib/tasks/pr-activity.ts +1 -3
  113. package/lib/tasks/queue-rows.ts +2 -4
  114. package/lib/tasks/queue-types.ts +1 -5
  115. package/lib/tasks/queue.ts +1 -2
  116. package/lib/tasks/refresh-base.ts +1 -2
  117. package/lib/tasks/setup.ts +9 -2
  118. package/lib/tasks/start.ts +2 -4
  119. package/lib/tasks/starter.ts +1 -2
  120. package/lib/tasks/state.ts +1 -6
  121. package/lib/theme-config.ts +1 -8
  122. package/lib/updates/banner.ts +1 -6
  123. package/lib/updates/run.ts +1 -2
  124. package/lib/usage/aggregate.ts +1 -1
  125. package/lib/usage/nightly.ts +1 -2
  126. package/lib/usage/rate-limits.ts +1 -2
  127. package/lib/usage/turns.ts +1 -2
  128. package/lib/usage/windows.ts +5 -7
  129. package/lib/workspaces.ts +1 -2
  130. package/lib/worktree-deps/clone.ts +10 -0
  131. package/lib/worktree-deps/fastcopy.ts +8 -1
  132. package/lib/worktree-deps/index.ts +14 -19
  133. package/lib/worktree-deps/install.ts +17 -24
  134. package/lib/worktree-sweep/databases.ts +0 -1
  135. package/package.json +10 -17
  136. package/scripts/check-native.mjs +86 -0
  137. package/scripts/code-health/prepass.ts +3 -3
  138. package/scripts/install-deps +24 -0
  139. package/scripts/lib/commands.sh +2 -2
  140. package/scripts/lib/deps-swap.sh +68 -0
  141. package/scripts/lib/deps-swap.test.ts +115 -0
  142. package/scripts/lib/pnpm.sh +36 -0
  143. package/scripts/lib/prerequisites.sh +9 -9
  144. package/scripts/publish +3 -2
  145. package/scripts/redeploy +25 -8
  146. package/scripts/setup.sh +4 -4
  147. package/tsconfig.json +1 -0
  148. package/scripts/ensure-native.mjs +0 -23
@@ -74,13 +74,11 @@ const nudge = () =>
74
74
 
75
75
  const sessionOf = (id: string) =>
76
76
  db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
77
- | Session
78
- | undefined;
77
+ Session | undefined;
79
78
 
80
79
  const pauseRow = (id: number) =>
81
80
  db.prepare(`SELECT * FROM task_pauses WHERE id = ?`).get(id) as
82
- | TaskPause
83
- | undefined;
81
+ TaskPause | undefined;
84
82
 
85
83
  // SQLite's datetime('now') is UTC without a zone.
86
84
  const ms = (v: string | null | undefined) =>
@@ -400,8 +398,7 @@ export function setPauseNote(sessionId: string, note: string | null): void {
400
398
  }
401
399
 
402
400
  export type ResumeOutcome =
403
- | { outcome: "resumed" | "cancelled" }
404
- | { outcome: "held"; note: string };
401
+ { outcome: "resumed" | "cancelled" } | { outcome: "held"; note: string };
405
402
 
406
403
  // A deliberate Resume, by a person or the orchestrator: starts at once, past
407
404
  // the slot limit, like Start now. A machine in the red still holds it: it is
@@ -59,9 +59,7 @@ export async function fingerprints(
59
59
  );
60
60
  for (const n of prs) {
61
61
  const pr = res.data?.repository?.[`p${n}`] as
62
- | { createdAt: string; reviews: Last; comments: Last }
63
- | null
64
- | undefined;
62
+ { createdAt: string; reviews: Last; comments: Last } | null | undefined;
65
63
  if (!pr) continue;
66
64
  const part = (l: Last) => `${l.totalCount}:${l.nodes[0]?.id ?? ""}`;
67
65
  out.set(n, {
@@ -14,13 +14,11 @@ export const IN_FLIGHT = ["running", "moving"];
14
14
 
15
15
  export const row = (id: string) =>
16
16
  db.prepare(`SELECT * FROM task_queue WHERE id = ?`).get(id) as
17
- | QueueRow
18
- | undefined;
17
+ QueueRow | undefined;
19
18
 
20
19
  export const sessionOf = (id: string) =>
21
20
  db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
22
- | Session
23
- | undefined;
21
+ Session | undefined;
24
22
 
25
23
  export function patch(id: string, fields: Partial<QueueRow>): void {
26
24
  const keys = Object.keys(fields);
@@ -6,11 +6,7 @@ import type { Priority, WaitCode } from "./schedule-types";
6
6
  import type { StartVia } from "./starter";
7
7
 
8
8
  export type QueueStatus =
9
- | "queued"
10
- | "starting"
11
- | "started"
12
- | "failed"
13
- | "removed";
9
+ "queued" | "starting" | "started" | "failed" | "removed";
14
10
 
15
11
  export interface QueueRow {
16
12
  id: string;
@@ -313,8 +313,7 @@ export function moveQueued(id: string, by: -1 | 1): void {
313
313
  ORDER BY q.position ${by < 0 ? "DESC" : "ASC"} LIMIT 1`
314
314
  )
315
315
  .get({ position: r.position, ws: workspaceId, project: r.project_id }) as
316
- | QueueRow
317
- | undefined;
316
+ QueueRow | undefined;
318
317
  if (!other) return;
319
318
  patch(r.id, { position: other.position });
320
319
  patch(other.id, { position: r.position });
@@ -16,8 +16,7 @@ import { restackBranch, outputOf, type Runner } from "../stacks/git";
16
16
  import { run } from "./gh";
17
17
 
18
18
  export type RefreshResult =
19
- | { moved: false; note?: string }
20
- | { moved: true; how: "reset" | "rebase" };
19
+ { moved: false; note?: string } | { moved: true; how: "reset" | "rebase" };
21
20
 
22
21
  export interface RefreshTarget {
23
22
  name: string;
@@ -20,8 +20,10 @@ const tail = (s: string, n: number) => (s.length > n ? `...${s.slice(-n)}` : s);
20
20
  export function setupOutcome(result: SetupResult | Error): TaskSetup {
21
21
  if (result instanceof Error)
22
22
  return { status: "failed", ms: null, error: redact(result.message) };
23
- if (result.success)
24
- return { status: "ok", ms: result.durationMs, error: null };
23
+ if (result.success) {
24
+ const warning = result.steps.find((s) => s.warning)?.warning;
25
+ return { status: "ok", ms: result.durationMs, error: warning ?? null };
26
+ }
25
27
  const failed = result.steps.filter((s) => !s.success);
26
28
  const last = failed[failed.length - 1];
27
29
  const error = last
@@ -31,6 +33,11 @@ export function setupOutcome(result: SetupResult | Error): TaskSetup {
31
33
  }
32
34
 
33
35
  export function setupNote(setup: TaskSetup): string {
36
+ if (setup.status === "ok" && setup.error)
37
+ return `
38
+
39
+ ---
40
+ Note from AgentOS: ${setup.error}`;
34
41
  if (setup.status !== "failed") return "";
35
42
  return `
36
43
 
@@ -60,8 +60,7 @@ export function launchHold(sessionId: string): string | null {
60
60
  LIMIT 1`
61
61
  )
62
62
  .get(sessionId, sessionId, sessionId) as
63
- | { workspace_id: string }
64
- | undefined;
63
+ { workspace_id: string } | undefined;
65
64
  if (!row) return null;
66
65
  if (isPaused(row.workspace_id)) return "the orchestrator is paused by Saad";
67
66
  const window = brakesEnabled() ? windowRefusal(readUsage()) : null;
@@ -135,8 +134,7 @@ export async function finishTaskStart(
135
134
  const now = db
136
135
  .prepare(`SELECT task_status, archived_at FROM session_rows WHERE id = ?`)
137
136
  .get(sessionId) as
138
- | { task_status: string | null; archived_at: string | null }
139
- | undefined;
137
+ { task_status: string | null; archived_at: string | null } | undefined;
140
138
  if (now?.task_status !== "running" || now.archived_at)
141
139
  return done({
142
140
  status: "failed",
@@ -102,8 +102,7 @@ export function orchestratorOf(startedBy: string | null | undefined) {
102
102
  const row = db
103
103
  .prepare(`SELECT role, started_by FROM sessions WHERE id = ?`)
104
104
  .get(id) as
105
- | { role: string | null; started_by: string | null }
106
- | undefined;
105
+ { role: string | null; started_by: string | null } | undefined;
107
106
  if (!row) return null;
108
107
  if (row.role === "orchestrator") return id;
109
108
  id = row.started_by;
@@ -1,12 +1,7 @@
1
1
  import type { AttributionKind } from "./code-review";
2
2
 
3
3
  export type TaskStatus =
4
- | "running"
5
- | "merged"
6
- | "dropped"
7
- | "done"
8
- | "moving"
9
- | "moved";
4
+ "running" | "merged" | "dropped" | "done" | "moving" | "moved";
10
5
 
11
6
  export type ChecksVerdict = "pass" | "fail" | "pending" | "none";
12
7
 
@@ -30,14 +30,7 @@ export type DarkThemeVariant =
30
30
  | "ocean"
31
31
  | "mocha";
32
32
  export type LightThemeVariant =
33
- | "default"
34
- | "warm"
35
- | "cool"
36
- | "soft"
37
- | "rose"
38
- | "lavender"
39
- | "mint"
40
- | "peach";
33
+ "default" | "warm" | "cool" | "soft" | "rose" | "lavender" | "mint" | "peach";
41
34
  export type Theme =
42
35
  | "light"
43
36
  | `light-${LightThemeVariant}`
@@ -68,12 +68,7 @@ export interface UpdateView {
68
68
  }
69
69
 
70
70
  export type BannerState =
71
- | "hidden"
72
- | "available"
73
- | "pending"
74
- | "running"
75
- | "failed"
76
- | "built";
71
+ "hidden" | "available" | "pending" | "running" | "failed" | "built";
77
72
 
78
73
  export const DISMISSED_VERSION_KEY = "agentos.update.dismissed";
79
74
  export const DISMISSED_RUN_KEY = "agentos.update.dismissedRun";
@@ -264,8 +264,7 @@ export async function preflight(
264
264
  // ── starting ─────────────────────────────────────────────────────────────
265
265
 
266
266
  export type StartResult =
267
- | { ok: true; pending: boolean }
268
- | ({ ok: false } & Refusal);
267
+ { ok: true; pending: boolean } | ({ ok: false } & Refusal);
269
268
 
270
269
  /** Takes the right to start, atomically, so two requests can't both run. */
271
270
  function claimStart(deps: Pick<RunDeps, "stateFile" | "now">): boolean {
@@ -76,7 +76,7 @@ export function aggregateUsage(
76
76
  const since = rangeStart(range, now);
77
77
  const total = acc();
78
78
  const days = new Map<string, Acc>();
79
- for (let t = since; t <= now; ) {
79
+ for (let t = since; t <= now;) {
80
80
  days.set(dayKey(t), acc());
81
81
  const next = new Date(t);
82
82
  next.setDate(next.getDate() + 1);
@@ -45,8 +45,7 @@ const addDays = (day: string, n: number) =>
45
45
 
46
46
  function setting(key: string): string | null {
47
47
  const r = db.prepare(`SELECT value FROM settings WHERE key = ?`).get(key) as
48
- | { value: string }
49
- | undefined;
48
+ { value: string } | undefined;
50
49
  return r?.value ?? null;
51
50
  }
52
51
 
@@ -160,8 +160,7 @@ export function lastReading(
160
160
  WHERE window = ? AND at <= ? ORDER BY at DESC, id DESC LIMIT 1`
161
161
  )
162
162
  .get(window, before) as
163
- | { at: number; utilization: number; resets_at: number | null }
164
- | undefined;
163
+ { at: number; utilization: number; resets_at: number | null } | undefined;
165
164
  return r
166
165
  ? { at: r.at, utilization: r.utilization, resetsAt: r.resets_at }
167
166
  : null;
@@ -111,8 +111,7 @@ export function recordCompaction(
111
111
  const current = db
112
112
  .prepare(`SELECT name, workspace_id FROM sessions WHERE id = ?`)
113
113
  .get(session.id) as
114
- | { name: string; workspace_id: string | null }
115
- | undefined;
114
+ { name: string; workspace_id: string | null } | undefined;
116
115
  db.prepare(
117
116
  `INSERT INTO chat_compactions (session_id, session_name, workspace_id, at, trigger, phase, before_tokens, after_tokens)
118
117
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
@@ -101,13 +101,11 @@ async function planLimits(): Promise<Cached> {
101
101
  if (cache && Date.now() - cache.at < TTL_MS) return cache;
102
102
  inflight ??= askAgent()
103
103
  .then((limits): Cached => ({ at: Date.now(), limits }))
104
- .catch(
105
- (error: unknown): Cached => ({
106
- at: Date.now(),
107
- limits: null,
108
- error: error instanceof Error ? error.message : String(error),
109
- })
110
- )
104
+ .catch((error: unknown): Cached => ({
105
+ at: Date.now(),
106
+ limits: null,
107
+ error: error instanceof Error ? error.message : String(error),
108
+ }))
111
109
  .then((c) => (cache = c))
112
110
  .finally(() => {
113
111
  inflight = null;
package/lib/workspaces.ts CHANGED
@@ -53,8 +53,7 @@ export function listWorkspaces(): Workspace[] {
53
53
 
54
54
  export function getWorkspace(id: string): Workspace | null {
55
55
  const row = db.prepare(`SELECT * FROM workspaces WHERE id = ?`).get(id) as
56
- | WorkspaceRow
57
- | undefined;
56
+ WorkspaceRow | undefined;
58
57
  return row ? toWorkspace(row) : null;
59
58
  }
60
59
 
@@ -236,6 +236,16 @@ export async function cloneDeps(opts: {
236
236
  // not, and cloning it would hand the worktree missing dependencies. Other
237
237
  // package managers have no hidden lockfile to check, so they pass.
238
238
  export async function nodeModulesMatchLock(dir: string): Promise<boolean> {
239
+ // pnpm copies the lockfile it installed from into node_modules/.pnpm.
240
+ const pnpmLock = await fs.promises
241
+ .readFile(path.join(dir, "pnpm-lock.yaml"), "utf-8")
242
+ .catch(() => null);
243
+ if (pnpmLock !== null) {
244
+ const installed = await fs.promises
245
+ .readFile(path.join(dir, "node_modules", ".pnpm", "lock.yaml"), "utf-8")
246
+ .catch(() => null);
247
+ return installed === pnpmLock;
248
+ }
239
249
  const read = (file: string) =>
240
250
  fs.promises
241
251
  .readFile(file, "utf-8")
@@ -22,7 +22,14 @@ export const STORE_ROOT = agentosHome("deps-store");
22
22
 
23
23
  // Files tools rewrite in place. A hardlink would carry the write back into
24
24
  // the store, so these are real copies in every worktree.
25
- export const MUTABLE_IN_MODULES = [".package-lock.json", ".cache", ".vite"];
25
+ export const MUTABLE_IN_MODULES = [
26
+ ".package-lock.json",
27
+ ".modules.yaml",
28
+ ".pnpm/lock.yaml",
29
+ ".pnpm-workspace-state-v1.json",
30
+ ".cache",
31
+ ".vite",
32
+ ];
26
33
 
27
34
  const probed = new Map<string, boolean>();
28
35
 
@@ -7,7 +7,7 @@ import { exec } from "child_process";
7
7
  import { promisify } from "util";
8
8
  import type { SetupResult } from "../env-setup";
9
9
  import { NO_FAST_COPY, cloneDeps } from "./clone";
10
- import { detectPackageManager, setupEnv } from "./install";
10
+ import { detectPackageManager, setupEnv, shellCommand } from "./install";
11
11
 
12
12
  const execAsync = promisify(exec);
13
13
 
@@ -47,8 +47,8 @@ export async function runCommand(
47
47
 
48
48
  /**
49
49
  * Clone the main checkout's node_modules (or exactly agentos.json's `clone`
50
- * paths), or else install: the lockfile's frozen install first, then a
51
- * plain one.
50
+ * paths), or else install: the lockfile's frozen install first, then (for
51
+ * a project other than AgentOS) a plain one.
52
52
  */
53
53
  export async function bringDependencies(
54
54
  result: SetupResult,
@@ -81,20 +81,15 @@ export async function bringDependencies(
81
81
  return;
82
82
  }
83
83
 
84
- let error: string | undefined;
85
- for (const command of pm.installs) {
86
- const run = await deps.run(command, worktreePath, envVars);
87
- result.steps.push({
88
- name: `Install dependencies (${pm.name})`,
89
- command,
90
- success: run.success,
91
- output: [clone.reason && `Not cloned: ${clone.reason}`, run.output]
92
- .filter(Boolean)
93
- .join("\n"),
94
- error: run.error,
95
- });
96
- if (run.success) return;
97
- error = run.error;
98
- }
99
- if (error !== undefined) result.success = false;
84
+ const run = await deps.run(shellCommand(pm.install), worktreePath, envVars);
85
+ result.steps.push({
86
+ name: `Install dependencies (${pm.name})`,
87
+ command: pm.install,
88
+ success: run.success,
89
+ output: [clone.reason && `Not cloned: ${clone.reason}`, run.output]
90
+ .filter(Boolean)
91
+ .join("\n"),
92
+ error: run.error,
93
+ });
94
+ if (!run.success) result.success = false;
100
95
  }
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Installing a worktree's dependencies when they can't be cloned: the
3
- * lockfile's own frozen install first, then a plain install.
3
+ * lockfile's own frozen install, and nothing else. A plain install would
4
+ * resolve ranges to versions newer than the lockfile, so a lockfile that
5
+ * disagrees with package.json fails setup and the agent is told.
4
6
  */
5
7
 
6
8
  import * as fs from "fs";
@@ -8,8 +10,7 @@ import * as path from "path";
8
10
 
9
11
  export interface PackageManager {
10
12
  name: "bun" | "pnpm" | "yarn" | "npm";
11
- // Tried in order until one succeeds: frozen to the lockfile, then plain.
12
- installs: string[];
13
+ install: string;
13
14
  }
14
15
 
15
16
  // Each install includes devDependencies: the agent needs typecheck, tests
@@ -17,44 +18,29 @@ export interface PackageManager {
17
18
  const BY_LOCKFILE: Array<{ file: string; pm: PackageManager }> = [
18
19
  {
19
20
  file: "bun.lock",
20
- pm: {
21
- name: "bun",
22
- installs: ["bun install --frozen-lockfile", "bun install"],
23
- },
21
+ pm: { name: "bun", install: "bun install --frozen-lockfile" },
24
22
  },
25
23
  {
26
24
  file: "bun.lockb",
27
- pm: {
28
- name: "bun",
29
- installs: ["bun install --frozen-lockfile", "bun install"],
30
- },
25
+ pm: { name: "bun", install: "bun install --frozen-lockfile" },
31
26
  },
32
27
  {
33
28
  file: "pnpm-lock.yaml",
34
29
  pm: {
35
30
  name: "pnpm",
36
- installs: [
37
- "pnpm install --frozen-lockfile --prod=false",
38
- "pnpm install --prod=false",
39
- ],
31
+ install: "pnpm install --frozen-lockfile --prod=false",
40
32
  },
41
33
  },
42
34
  {
43
35
  file: "yarn.lock",
44
36
  pm: {
45
37
  name: "yarn",
46
- installs: [
47
- "yarn install --frozen-lockfile --production=false",
48
- "yarn install --production=false",
49
- ],
38
+ install: "yarn install --frozen-lockfile --production=false",
50
39
  },
51
40
  },
52
41
  {
53
42
  file: "package-lock.json",
54
- pm: {
55
- name: "npm",
56
- installs: ["npm ci --include=dev", "npm install --include=dev"],
57
- },
43
+ pm: { name: "npm", install: "npm ci --include=dev" },
58
44
  },
59
45
  ];
60
46
 
@@ -68,11 +54,18 @@ export function detectPackageManager(
68
54
  }
69
55
  if (fs.existsSync(path.join(projectPath, "package.json"))) {
70
56
  // No lockfile, so nothing to be frozen to.
71
- return { name: "npm", installs: ["npm install --include=dev"] };
57
+ return { name: "npm", install: "npm install --include=dev" };
72
58
  }
73
59
  return null;
74
60
  }
75
61
 
62
+ // pnpm may exist only as a corepack shim that was never enabled; the shell
63
+ // defines `pnpm` through corepack when the command is missing.
64
+ export function shellCommand(command: string): string {
65
+ if (!command.startsWith("pnpm ")) return command;
66
+ return `if ! command -v pnpm >/dev/null 2>&1; then pnpm() { COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack pnpm "$@"; }; fi; ${command}`;
67
+ }
68
+
76
69
  // What setup commands run under: the server's environment without the
77
70
  // settings that make an install skip devDependencies.
78
71
  export function setupEnv(
@@ -22,7 +22,6 @@ import { loadProjectConfig } from "../project-config";
22
22
  import { expandHome } from "../project-config/session";
23
23
  import {
24
24
  LOCAL_HOSTS,
25
- MARK,
26
25
  SOCKET_DIR,
27
26
  pgEnv,
28
27
  pgRun,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.680",
3
+ "version": "0.3.691",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"
@@ -24,29 +24,24 @@
24
24
  "tsconfig.json",
25
25
  "postcss.config.mjs",
26
26
  "tailwind.config.ts",
27
- "package-lock.json",
28
27
  "README.md",
29
28
  "LICENSE"
30
29
  ],
31
30
  "scripts": {
32
- "predev": "node scripts/ensure-native.mjs && node scripts/gen-migrations-index.mjs",
31
+ "predev": "node scripts/check-native.mjs --rebuild && node scripts/gen-migrations-index.mjs",
33
32
  "dev": "AGENTOS_DEV=1 tsx server.ts",
34
33
  "dev:task": "tsx scripts/dev-task.ts",
35
34
  "dev:stop": "tsx scripts/dev-stop.ts",
36
35
  "disk:dry-run": "tsx scripts/disk-dry-run.ts",
37
36
  "prebuild": "node scripts/gen-migrations-index.mjs",
38
37
  "build": "next build",
39
- "prestart": "node scripts/ensure-native.mjs && node scripts/gen-migrations-index.mjs",
38
+ "prestart": "node scripts/check-native.mjs --rebuild && node scripts/gen-migrations-index.mjs",
40
39
  "start": "NODE_ENV=production AGENTOS_SERVER=live node --import tsx server.ts",
41
40
  "pretypecheck": "node scripts/gen-migrations-index.mjs",
42
41
  "typecheck": "tsc --noEmit",
43
42
  "setup": "bash scripts/setup.sh",
44
43
  "postinstall": "chmod +x node_modules/node-pty/prebuilds/*/spawn-helper 2>/dev/null || true",
45
44
  "prepare": "husky",
46
- "tauri": "tauri",
47
- "tauri:dev": "tauri dev",
48
- "tauri:build": "tauri build",
49
- "tauri:build:linux": "docker build -t agentos-linux -f src-tauri/Dockerfile.linux src-tauri && docker run --rm -v $(pwd)/src-tauri/target:/app/target agentos-linux",
50
45
  "prelint": "node scripts/gen-migrations-index.mjs",
51
46
  "lint": "eslint .",
52
47
  "format": "prettier --write .",
@@ -71,7 +66,7 @@
71
66
  "author": "Saad Naveed",
72
67
  "license": "MIT",
73
68
  "engines": {
74
- "node": ">=20"
69
+ "node": ">=22"
75
70
  },
76
71
  "bugs": {
77
72
  "url": "https://github.com/saadnvd1/agent-os/issues"
@@ -155,15 +150,15 @@
155
150
  "serwist": "^9.5.7",
156
151
  "sonner": "^2.0.7",
157
152
  "tailwind-merge": "^3.4.0",
153
+ "unified": "^11.0.5",
158
154
  "valtio": "^2.3.0",
159
155
  "ws": "^8.19.0",
160
156
  "zod": "^4.6.5"
161
157
  },
162
158
  "devDependencies": {
163
159
  "@tailwindcss/postcss": "^4.1.18",
164
- "@tauri-apps/api": "^2.12.1",
165
- "@tauri-apps/cli": "^2.12.1",
166
- "@types/better-sqlite3": "^7.6.13",
160
+ "@types/better-sqlite3": "^9.6.0",
161
+ "@types/hast": "^3.0.4",
167
162
  "@types/jsdom": "^30.0.0",
168
163
  "@types/mdast": "^4.0.4",
169
164
  "@types/node": "^20.19.27",
@@ -176,7 +171,8 @@
176
171
  "husky": "^9.1.7",
177
172
  "jsdom": "^30.1.2",
178
173
  "prettier": "^3.7.4",
179
- "prettier-plugin-tailwindcss": "^0.7.2",
174
+ "prettier-plugin-tailwindcss": "^0.8.1",
175
+ "remark-parse": "^11.0.0",
180
176
  "sharp": "^0.35.5",
181
177
  "tailwindcss": "^4.1.18",
182
178
  "tsx": "^4.23.15",
@@ -184,8 +180,5 @@
184
180
  "typescript": "^5.9.3",
185
181
  "vitest": "^4.1.11"
186
182
  },
187
- "overrides": {
188
- "dompurify": "^3.4.16",
189
- "postcss-selector-parser": "^7.1.6"
190
- }
183
+ "packageManager": "pnpm@11.28.5+sha512.484563d30b4c2ad27cb48aa94ff2b302dbcebc8ff6bf28816c2589886e146ce7f76f354f3bc1a438e6f5e9768c302aa0a6f7add8232903cd4cf81426cbc14d9e"
191
184
  }
@@ -0,0 +1,86 @@
1
+ // Proves the native modules work in a node_modules tree: open a SQLite
2
+ // database with better-sqlite3 and spawn a pty with node-pty. Used before a
3
+ // deploy swaps an install in, and by predev/prestart.
4
+ // node scripts/check-native.mjs [node_modules dir] (default: this checkout's)
5
+ // --rebuild on a Node ABI mismatch, run `pnpm rebuild` once and check again
6
+ import { createRequire } from "module";
7
+ import { execFileSync } from "child_process";
8
+ import fs from "fs";
9
+ import os from "os";
10
+ import path from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const here = path.dirname(fileURLToPath(import.meta.url));
14
+ const args = process.argv.slice(2);
15
+ const rebuild = args.includes("--rebuild");
16
+ const modules = path.resolve(
17
+ args.find((a) => !a.startsWith("--")) ?? path.join(here, "..", "node_modules")
18
+ );
19
+
20
+ async function sqlite(require) {
21
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "aos-native-"));
22
+ try {
23
+ const db = require("better-sqlite3")(path.join(dir, "check.db"));
24
+ db.exec("create table t (v text); insert into t values ('ok')");
25
+ const row = db.prepare("select v from t").get();
26
+ db.close();
27
+ if (row?.v !== "ok") throw new Error("better-sqlite3 read back nothing");
28
+ } finally {
29
+ fs.rmSync(dir, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ function pty(require) {
34
+ return new Promise((resolve, reject) => {
35
+ const p = require("node-pty").spawn("/bin/sh", ["-c", "echo pty-ok"], {});
36
+ let out = "";
37
+ const timer = setTimeout(() => {
38
+ p.kill();
39
+ reject(new Error("node-pty: no exit within 10s"));
40
+ }, 10_000);
41
+ p.onData((d) => (out += d));
42
+ p.onExit(({ exitCode }) => {
43
+ clearTimeout(timer);
44
+ if (exitCode === 0 && out.includes("pty-ok")) resolve();
45
+ else reject(new Error(`node-pty: exit ${exitCode}, output ${out}`));
46
+ });
47
+ });
48
+ }
49
+
50
+ async function failures() {
51
+ const require = createRequire(path.join(modules, "noop.js"));
52
+ const found = [];
53
+ for (const [name, run] of [
54
+ ["better-sqlite3", sqlite],
55
+ ["node-pty", pty],
56
+ ]) {
57
+ try {
58
+ await run(require);
59
+ } catch (err) {
60
+ found.push({ name, message: String(err?.message ?? err) });
61
+ }
62
+ }
63
+ return found;
64
+ }
65
+
66
+ let bad = await failures();
67
+ if (
68
+ bad.length &&
69
+ rebuild &&
70
+ bad.some((b) => b.message.includes("NODE_MODULE_VERSION"))
71
+ ) {
72
+ const names = bad.map((b) => b.name);
73
+ console.log(`Rebuilding ${names.join(", ")} for Node ${process.version}...`);
74
+ execFileSync("pnpm", ["rebuild", ...names], {
75
+ cwd: path.dirname(modules),
76
+ stdio: "inherit",
77
+ });
78
+ bad = await failures();
79
+ }
80
+ if (bad.length) {
81
+ for (const b of bad)
82
+ console.error(`native check failed: ${b.name}: ${b.message}`);
83
+ process.exit(1);
84
+ }
85
+ console.log(`native modules ok (${modules})`);
86
+ process.exit(0);
@@ -142,11 +142,11 @@ async function prepass(args: string[]) {
142
142
  const steps: Record<Collector, () => string | void> = {
143
143
  deps: () => {
144
144
  const prod = JSON.parse(
145
- run("npm", ["audit", "--omit=dev", "--json"], root) || "{}"
145
+ run("pnpm", ["audit", "--prod", "--json"], root) || "{}"
146
146
  );
147
- const all = JSON.parse(run("npm", ["audit", "--json"], root) || "{}");
147
+ const all = JSON.parse(run("pnpm", ["audit", "--json"], root) || "{}");
148
148
  const outdated = JSON.parse(
149
- run("npm", ["outdated", "--json"], root) || "{}"
149
+ run("pnpm", ["outdated", "--format", "json"], root) || "{}"
150
150
  );
151
151
  result.deps = {
152
152
  prod: summarizeNpmAudit(prod),