@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
@@ -54,8 +54,7 @@ export function createArtifact(
54
54
  export function getArtifact(id: string): Artifact | undefined {
55
55
  if (!ID.test(id)) return undefined;
56
56
  return db.prepare(`SELECT * FROM artifacts WHERE id = ?`).get(id) as
57
- | Artifact
58
- | undefined;
57
+ Artifact | undefined;
59
58
  }
60
59
 
61
60
  export function listArtifacts(sessionId: string): Artifact[] {
@@ -150,8 +150,7 @@ function merged(
150
150
  );
151
151
  return rows.map((r) => {
152
152
  const done = action.get(away.id, `deploy:${r.id}`) as
153
- | { kind: string; detail: string | null }
154
- | undefined;
153
+ { kind: string; detail: string | null } | undefined;
155
154
  const deploy: DeployState =
156
155
  done?.kind === "deploy_ok"
157
156
  ? "deployed"
package/lib/away/run.ts CHANGED
@@ -70,8 +70,7 @@ function orchestrated(): { workspaceId: string; orchestratorId: string }[] {
70
70
 
71
71
  const sessionRow = (id: string) =>
72
72
  db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
73
- | Session
74
- | undefined;
73
+ Session | undefined;
75
74
 
76
75
  // Away mode reaches a workspace only through an orchestrator that's been
77
76
  // working inside the window: one left alone isn't told or woken.
package/lib/away/state.ts CHANGED
@@ -43,8 +43,7 @@ export function activeAway(): AwayRow | null {
43
43
  export function getAway(id: number): AwayRow | null {
44
44
  return (
45
45
  (db.prepare(`SELECT * FROM away_periods WHERE id = ?`).get(id) as
46
- | AwayRow
47
- | undefined) ?? null
46
+ AwayRow | undefined) ?? null
48
47
  );
49
48
  }
50
49
 
@@ -83,8 +83,7 @@ function toBookmark(r: Joined): Bookmark {
83
83
 
84
84
  export function getBookmark(id: string): Bookmark | null {
85
85
  const r = db.prepare(`${SELECT} WHERE b.id = ?`).get(id) as
86
- | Joined
87
- | undefined;
86
+ Joined | undefined;
88
87
  return r ? toBookmark(r) : null;
89
88
  }
90
89
 
@@ -7,8 +7,7 @@ import { WORKING_LINE } from "../claude-working-line";
7
7
 
8
8
  export type DeliveryState = "delivered" | "queued" | "failed";
9
9
  export type Delivery =
10
- | { state: "delivered" | "queued" }
11
- | { state: "failed"; why: string };
10
+ { state: "delivered" | "queued" } | { state: "failed"; why: string };
12
11
 
13
12
  export interface PaneView {
14
13
  text: string;
package/lib/bus/index.ts CHANGED
@@ -70,8 +70,7 @@ function candidates(sessions: Session[]): Candidate[] {
70
70
  const sessionById = (id: string | null | undefined) =>
71
71
  id
72
72
  ? (db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
73
- | Session
74
- | undefined)
73
+ Session | undefined)
75
74
  : undefined;
76
75
 
77
76
  // A chat has no pane: its worker is the state, and it can always take a
@@ -31,8 +31,7 @@ export async function compactContext(sessionId: string): Promise<string> {
31
31
  `SELECT working_directory, task_prompt, role FROM session_rows WHERE id = ?`
32
32
  )
33
33
  .get(sessionId) as
34
- | Pick<Session, "working_directory" | "task_prompt" | "role">
35
- | undefined;
34
+ Pick<Session, "working_directory" | "task_prompt" | "role"> | undefined;
36
35
  // An orchestrator starts fresh instead of compacting.
37
36
  if (!session || session.role === "orchestrator") return "";
38
37
  const tools = lastItems(sessionId, 400, "tool").filter(
package/lib/chat/stop.ts CHANGED
@@ -70,8 +70,7 @@ export async function stopChatAtTurnEnd(
70
70
  }
71
71
 
72
72
  export type TurnStop =
73
- | { stopped: true; killed: boolean }
74
- | { stopped: false; reason: string };
73
+ { stopped: true; killed: boolean } | { stopped: false; reason: string };
75
74
 
76
75
  export interface TurnStopOptions {
77
76
  // How long the turn gets to end after the interrupt before its agent is
@@ -551,7 +551,7 @@ export class ChatHost {
551
551
  this.compactingNow
552
552
  )
553
553
  return;
554
- for (let next; (next = claimNext(this.session.id)); ) {
554
+ for (let next; (next = claimNext(this.session.id));) {
555
555
  this.emit({ type: "queue" });
556
556
  this.sendUser(next);
557
557
  }
@@ -20,17 +20,53 @@ interface NpmVuln {
20
20
  isDirect?: boolean;
21
21
  via?: (string | { title?: string; source?: number })[];
22
22
  fixAvailable?:
23
- | boolean
24
- | { name?: string; version?: string; isSemVerMajor?: boolean };
23
+ boolean | { name?: string; version?: string; isSemVerMajor?: boolean };
24
+ }
25
+
26
+ interface PnpmAdvisory {
27
+ module_name?: string;
28
+ title?: string;
29
+ severity?: string;
30
+ patched_versions?: string | null;
31
+ findings?: { paths?: string[] }[];
32
+ }
33
+
34
+ // pnpm audit lists advisories, not packages: one npm-style entry per module.
35
+ function fromAdvisories(
36
+ advisories: Record<string, PnpmAdvisory>
37
+ ): Record<string, NpmVuln> {
38
+ const out: Record<string, NpmVuln> = {};
39
+ for (const a of Object.values(advisories)) {
40
+ const name = a.module_name ?? "";
41
+ if (!name) continue;
42
+ const paths = (a.findings ?? []).flatMap((f) => f.paths ?? []);
43
+ const prev = out[name];
44
+ const rank = (v?: string) => (v === "critical" ? 2 : v === "high" ? 1 : 0);
45
+ out[name] = {
46
+ severity:
47
+ rank(a.severity) >= rank(prev?.severity) ? a.severity : prev?.severity,
48
+ isDirect:
49
+ Boolean(prev?.isDirect) || paths.some((p) => /^\.>[^>]+$/.test(p)),
50
+ via: [...(prev?.via ?? []), a.title ?? ""],
51
+ fixAvailable: a.patched_versions
52
+ ? { name, version: a.patched_versions }
53
+ : false,
54
+ };
55
+ }
56
+ return out;
25
57
  }
26
58
 
27
59
  export function summarizeNpmAudit(json: unknown): NpmAuditSummary {
28
60
  const data = (json ?? {}) as {
29
61
  metadata?: { vulnerabilities?: Record<string, number> };
30
62
  vulnerabilities?: Record<string, NpmVuln>;
63
+ advisories?: Record<string, PnpmAdvisory>;
31
64
  };
32
65
  const counts = { ...(data.metadata?.vulnerabilities ?? {}) };
33
- const serious = Object.entries(data.vulnerabilities ?? {})
66
+ const vulnerabilities = data.advisories
67
+ ? fromAdvisories(data.advisories)
68
+ : (data.vulnerabilities ?? {});
69
+ const serious = Object.entries(vulnerabilities)
34
70
  .filter(([, v]) => v.severity === "high" || v.severity === "critical")
35
71
  .map(([name, v]) => {
36
72
  const fix = v.fixAvailable;
@@ -16,7 +16,7 @@ const CODE = /\.(ts|tsx|mts|cts|js|mjs|cjs|jsx)$/;
16
16
  const TEST = /(\.test\.|\.spec\.|(^|\/)__tests__\/|(^|\/)vitest\.setup\.)/;
17
17
  // Generated, vendored or built: measured by other means or not ours.
18
18
  const NOT_OURS =
19
- /(^|\/)(node_modules|\.next[^/]*|dist|out|coverage|public|src-tauri)\/|\.min\.js$|(^|\/)next-env\.d\.ts$/;
19
+ /(^|\/)(node_modules|\.deps|\.next[^/]*|dist|out|coverage|public)\/|\.min\.js$|(^|\/)next-env\.d\.ts$/;
20
20
 
21
21
  export const isCode = (p: string) => CODE.test(p) && !NOT_OURS.test(p);
22
22
  export const isTest = (p: string) => TEST.test(p);
@@ -24,8 +24,7 @@ export const ATTEMPT_TTL_MINUTES = 10;
24
24
  export const lumifyhubQueries = {
25
25
  getConnection: (db: Database.Database) =>
26
26
  (db.prepare(`SELECT * FROM lumifyhub_connection WHERE id = 1`).get() as
27
- | LumifyHubConnection
28
- | undefined) ?? null,
27
+ LumifyHubConnection | undefined) ?? null,
29
28
 
30
29
  saveConnection: (
31
30
  db: Database.Database,
@@ -65,8 +64,7 @@ export const lumifyhubQueries = {
65
64
  FROM lumifyhub_connect_attempts WHERE state = ?`
66
65
  )
67
66
  .get(`-${ATTEMPT_TTL_MINUTES} minutes`, state) as
68
- | (LumifyHubConnectAttempt & { fresh: number })
69
- | undefined;
67
+ (LumifyHubConnectAttempt & { fresh: number }) | undefined;
70
68
  db.prepare(`DELETE FROM lumifyhub_connect_attempts WHERE state = ?`).run(
71
69
  state
72
70
  );
@@ -200,8 +200,7 @@ export function revertSchedulesMove(db: Database.Database): {
200
200
  for (const s of moved) {
201
201
  const r = s.recipe_id
202
202
  ? (recipe.get(s.recipe_id) as
203
- | { brief: string; project_id: string }
204
- | undefined)
203
+ { brief: string; project_id: string } | undefined)
205
204
  : undefined;
206
205
  restore.run(
207
206
  s.migrated_from_kind,
@@ -20,8 +20,7 @@ export function startTaskClock(
20
20
  const row = db
21
21
  .prepare(`SELECT created_at, task_since FROM session_rows WHERE id = ?`)
22
22
  .get(sessionId) as
23
- | { created_at: string; task_since: string | null }
24
- | undefined;
23
+ { created_at: string; task_since: string | null } | undefined;
25
24
  if (!row || row.task_since) return;
26
25
  setTask(db, sessionId, { task_since: at ?? row.created_at });
27
26
  }
package/lib/db/stacks.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import type Database from "better-sqlite3";
2
2
 
3
3
  export type StackStatus =
4
- | "running"
5
- | "paused"
6
- | "landing"
7
- | "landed"
8
- | "failed";
4
+ "running" | "paused" | "landing" | "landed" | "failed";
9
5
 
10
6
  export type StackItemStatus =
11
7
  | "planned"
@@ -131,8 +127,7 @@ export const stackQueries = {
131
127
 
132
128
  get: (db: Database.Database, id: string) =>
133
129
  (db.prepare(`SELECT * FROM stacks WHERE id = ?`).get(id) as
134
- | StackRow
135
- | undefined) ?? null,
130
+ StackRow | undefined) ?? null,
136
131
 
137
132
  // The board's stack that has not finished yet, if any.
138
133
  openForBoard: (db: Database.Database, boardId: string) =>
@@ -150,8 +145,7 @@ export const stackQueries = {
150
145
 
151
146
  item: (db: Database.Database, id: string) =>
152
147
  (db.prepare(`SELECT * FROM stack_items WHERE id = ?`).get(id) as
153
- | StackItemRow
154
- | undefined) ?? null,
148
+ StackItemRow | undefined) ?? null,
155
149
 
156
150
  itemForSession: (db: Database.Database, sessionId: string) =>
157
151
  (db
package/lib/db/types.ts CHANGED
@@ -42,13 +42,7 @@ export interface Session {
42
42
  // done: finished without a merge (no PR, or its PR closed).
43
43
  // moving: handing it to another machine (moved_to); moved: handed over.
44
44
  task_status:
45
- | "running"
46
- | "merged"
47
- | "dropped"
48
- | "done"
49
- | "moving"
50
- | "moved"
51
- | null;
45
+ "running" | "merged" | "dropped" | "done" | "moving" | "moved" | null;
52
46
  moved_to: string | null;
53
47
  // The session on another machine this task arrived from.
54
48
  moved_from: string | null;
@@ -20,12 +20,16 @@ export function spawnServer(opts: {
20
20
  }): ChildProcess {
21
21
  const { repo, port, env, stdio, detached, native } = opts;
22
22
  assertNotLivePort(port, process.env, readLiveRecord());
23
- const run = (script: string) =>
24
- execFileSync(process.execPath, [path.join(repo, "scripts", script)], {
25
- cwd: repo,
26
- stdio: "ignore",
27
- });
28
- if (native) run("ensure-native.mjs");
23
+ const run = (script: string, ...args: string[]) =>
24
+ execFileSync(
25
+ process.execPath,
26
+ [path.join(repo, "scripts", script), ...args],
27
+ {
28
+ cwd: repo,
29
+ stdio: "ignore",
30
+ }
31
+ );
32
+ if (native) run("check-native.mjs", "--rebuild");
29
33
  run("gen-migrations-index.mjs");
30
34
  return spawn(process.execPath, ["--import", "tsx", "server.ts"], {
31
35
  cwd: repo,
@@ -11,7 +11,7 @@ import { getAllProviderDefinitions } from "../providers/registry";
11
11
 
12
12
  // The oldest Node major AgentOS runs on; scripts/lib/prerequisites.sh must agree
13
13
  // (lib/doctor/drift.test.ts fails when they don't).
14
- export const MIN_NODE_MAJOR = 20;
14
+ export const MIN_NODE_MAJOR = 22;
15
15
 
16
16
  const TOOL_MS = 5_000;
17
17
  const CACHE_MS = 60 * 1000;
@@ -207,15 +207,13 @@ export function runDoctor(
207
207
  d
208
208
  .probe(fresh)
209
209
  .then(checkAgents)
210
- .catch(
211
- (): DoctorCheck => ({
212
- id: "agents",
213
- label: "Agent CLI",
214
- status: "warn",
215
- detail: "Couldn't check the agent CLIs",
216
- fix: "Check again",
217
- })
218
- ),
210
+ .catch((): DoctorCheck => ({
211
+ id: "agents",
212
+ label: "Agent CLI",
213
+ status: "warn",
214
+ detail: "Couldn't check the agent CLIs",
215
+ fix: "Check again",
216
+ })),
219
217
  ]);
220
218
  if (!custom) cache = { at: Date.now(), checks };
221
219
  return checks;
@@ -35,8 +35,7 @@ export function unarchiveSession(id: string): Session {
35
35
  )
36
36
  .run(id).changes;
37
37
  const row = db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
38
- | Session
39
- | undefined;
38
+ Session | undefined;
40
39
  if (!row) throw new Error("Session not found");
41
40
  if (!changed) throw new Error(`${row.name} isn't archived`);
42
41
  return row;
package/lib/env-setup.ts CHANGED
@@ -22,6 +22,8 @@ export interface SetupStep {
22
22
  success: boolean;
23
23
  output?: string;
24
24
  error?: string;
25
+ // The step worked, but the agent should know something about it.
26
+ warning?: string;
25
27
  }
26
28
 
27
29
  // The stages a worktree's setup goes through, in order, for progress.
@@ -18,13 +18,7 @@ import { experimentOn } from ".";
18
18
  import { getExperiment, type ExperimentId } from "./registry";
19
19
 
20
20
  export type ExperimentEvent =
21
- | "model"
22
- | "script"
23
- | "shown"
24
- | "accepted"
25
- | "dismissed"
26
- | "applied"
27
- | "error";
21
+ "model" | "script" | "shown" | "accepted" | "dismissed" | "applied" | "error";
28
22
 
29
23
  export interface MeterContext {
30
24
  trigger: string;
package/lib/files.ts CHANGED
@@ -17,6 +17,7 @@ const DEFAULT_EXCLUDES = [
17
17
  "node_modules",
18
18
  ".git",
19
19
  ".next",
20
+ ".deps",
20
21
  "dist",
21
22
  "build",
22
23
  "out",
@@ -7,8 +7,6 @@
7
7
 
8
8
  import type { EnvClass } from "./types";
9
9
 
10
- const OLD_PYTHON =
11
- /python3?\.[0-9]\b|Python3?\.framework\/Versions\/3\.[0-9]\b|Python\/3\.[0-9]\b/i;
12
10
  const USE_ENV =
13
11
  "run it through the repo's own environment (`uv run …` when it has a pyproject.toml or uv.lock, else its .venv/bin/python), not a bare `python3`";
14
12
 
@@ -19,8 +19,7 @@ export function recordHit(
19
19
  WHERE r.id = ?`
20
20
  )
21
21
  .get(sessionId) as
22
- | { project_id: string | null; ws: string | null }
23
- | undefined;
22
+ { project_id: string | null; ws: string | null } | undefined;
24
23
  db.prepare(
25
24
  `INSERT INTO env_failures
26
25
  (session_id, class, project_id, workspace_id, command, line,
@@ -45,10 +45,7 @@ export interface SpinSample {
45
45
  }
46
46
 
47
47
  export type SpinClass =
48
- | "warming"
49
- | "sleeping"
50
- | "busy-with-output"
51
- | "spinning";
48
+ "warming" | "sleeping" | "busy-with-output" | "spinning";
52
49
 
53
50
  export function classifySpin(samples: SpinSample[]): SpinClass {
54
51
  if (samples.length < 2) return "warming";
@@ -29,8 +29,7 @@ export interface RecordResult {
29
29
 
30
30
  const rowOf = (id: number) =>
31
31
  db.prepare(`SELECT * FROM health_anomalies WHERE id = ?`).get(id) as
32
- | AnomalyRow
33
- | undefined;
32
+ AnomalyRow | undefined;
34
33
 
35
34
  export function anomaly(id: number): AnomalyRow | null {
36
35
  return rowOf(id) ?? null;
@@ -39,8 +39,7 @@ export function getHost(hostId: string | null | undefined): Host | null {
39
39
  if (!isRemoteHost(hostId)) return LOCAL_HOST;
40
40
  return (
41
41
  (getDb().prepare(`SELECT * FROM hosts WHERE id = ?`).get(hostId) as
42
- | Host
43
- | undefined) ?? null
42
+ Host | undefined) ?? null
44
43
  );
45
44
  }
46
45
 
@@ -95,8 +95,7 @@ export function syncPeerMirrors(
95
95
  const [name, view, tmuxName, projectId, prUrl, prNumber, prStatus, at] =
96
96
  fields;
97
97
  const cur = current.get(peer.id, hostId) as
98
- | Record<string, string | number | null>
99
- | undefined;
98
+ Record<string, string | number | null> | undefined;
100
99
  if (!cur) return;
101
100
  const same =
102
101
  cur.name === name &&
@@ -126,8 +125,7 @@ export function syncPeerMirrors(
126
125
  db.transaction(() => {
127
126
  for (const peer of listed) {
128
127
  const row = owner.get(peer.id) as
129
- | { host_id: string; project_id: string | null }
130
- | undefined;
128
+ { host_id: string; project_id: string | null } | undefined;
131
129
  // One started here for a project here keeps it (lib/hosts/peer-actions.ts).
132
130
  const kept =
133
131
  row?.project_id && here.has(row.project_id) ? row.project_id : null;
@@ -21,8 +21,7 @@ export function hostLink(hostId: string | null | undefined): HostLink | null {
21
21
  JOIN hosts h ON h.id = l.host_id WHERE l.host_id = ?`
22
22
  )
23
23
  .get(hostId) as
24
- | { host_id: string; url: string; token: string; name: string }
25
- | undefined;
24
+ { host_id: string; url: string; token: string; name: string } | undefined;
26
25
  return row
27
26
  ? {
28
27
  hostId: row.host_id,
@@ -23,11 +23,7 @@ export interface LhBoard {
23
23
  }
24
24
 
25
25
  export type LhListCategory =
26
- | "backlog"
27
- | "unstarted"
28
- | "started"
29
- | "completed"
30
- | "canceled";
26
+ "backlog" | "unstarted" | "started" | "completed" | "canceled";
31
27
 
32
28
  export interface LhList {
33
29
  id: string;
@@ -39,9 +35,7 @@ export interface LhList {
39
35
 
40
36
  // A TipTap/ProseMirror document, or a bare string on older cards.
41
37
  export type LhRichText =
42
- | string
43
- | { type?: string; text?: string; content?: LhRichText[] }
44
- | null;
38
+ string | { type?: string; text?: string; content?: LhRichText[] } | null;
45
39
 
46
40
  export interface LhCard {
47
41
  id: string;
@@ -161,8 +161,7 @@ export async function notifyFrom(
161
161
  ): Promise<Outcome> {
162
162
  const session = from
163
163
  ? (db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(from) as
164
- | Session
165
- | undefined)
164
+ Session | undefined)
166
165
  : undefined;
167
166
  if (from && !session) throw new Error("Unknown sender session");
168
167
  const body = text.trim();
@@ -39,11 +39,7 @@ export function pushStatus(): { linked: boolean; unavailable: string | null } {
39
39
  }
40
40
 
41
41
  export type PushOutcome =
42
- | "queued"
43
- | "folded"
44
- | "dropped"
45
- | "muted"
46
- | "unavailable";
42
+ "queued" | "folded" | "dropped" | "muted" | "unavailable";
47
43
 
48
44
  export interface PushOptions {
49
45
  /** Held text that goes by Telegram if no phone is reached. */
@@ -59,8 +55,7 @@ function isMirrored(sessionId: string): boolean {
59
55
  const s = db
60
56
  .prepare(`SELECT host_id, peer_mirror FROM session_rows WHERE id = ?`)
61
57
  .get(sessionId) as
62
- | { host_id: string | null; peer_mirror: number | null }
63
- | undefined;
58
+ { host_id: string | null; peer_mirror: number | null } | undefined;
64
59
  return !!s && (!!s.peer_mirror || (!!s.host_id && s.host_id !== "local"));
65
60
  }
66
61
 
@@ -164,7 +164,7 @@ Acting:
164
164
 
165
165
  The MCP servers this workspace allows (Settings → Workspaces) are yours to use through their own tools, for reading and for additive, reversible changes. Anything outbound through them (sending a message, an email, a post) is a hard line: park it with \`ask_saad\`.
166
166
 
167
- Act through these tools, not the shell: they're scoped to this workspace and braked. ${full ? `The shell and your file writes are unchecked (Saad's decision, 2026-10-18: Orchestrator full access, switched in Settings → Workspaces): \`git push\`, \`gh pr merge\`, commits, \`stash\`, \`clean\`, edits in any project checkout or task worktree, AgentOS's own state, dotfiles, launch agents, .claude, CLAUDE.md, .mcp.json, hooks, \`sqlite3\` writes, \`aos sweep --arm\`, other workspaces' repositories. Use that to fix things directly instead of starting a task for every small job, but still prefer the tools for what they cover (they're scoped to this workspace and logged), and never use the shell to get around a hard line. One warning stays: never stop or restart AgentOS or its tmux; that ends every session it runs, this one included. If AgentOS itself needs a restart, \`ask_saad\`. Reading a secret file is allowed; its value is never printed, logged or sent off the machine. ` : `The shell runs anything without asking (Saad chose this on 2026-10-11), so use it to read and investigate yourself: \`gh pr list\`, \`gh run view --log-failed\`, \`gh api\` GETs, \`curl\`, \`sqlite3 -readonly\`, git in any repository, a skill's scripts, pipes and chains. Never use it to get around the tools. A deny list refuses, naming the tool to use instead: merging, closing or retargeting a PR (\`${TOOL_NAMES.sign_off}\`, \`${TOOL_NAMES.arm}\`, \`${TOOL_NAMES.land}\`, \`${TOOL_NAMES.drop}\`), \`git push\` (\`${TOOL_NAMES.quick_pr}\`), writing in a project repository or task worktree other than your own quick changes, except in a project checkout of your own workspace, where you may update and move the branch: \`git pull\` in any form, \`merge\`, \`reset\` (even \`--hard\`), \`checkout\`, \`switch\` and \`rebase\` (also as \`git -C <path>\`), then \`bundle install\`, \`npm install\` or \`bin/rails db:prepare\`. Commits, \`stash\`, \`clean\`, \`restore\` and file edits there still go through tasks and quick changes, and other workspaces' repositories, task worktrees and AgentOS's own live checkout (autodeploy deploys it) are refused for all of it are refused (\`${TOOL_NAMES.start_task}\`, \`${TOOL_NAMES.quick_change}\`), writing AgentOS's state (sqlite3 without \`-readonly\`, or files in ~/.agent-os outside your folder), stopping or restarting AgentOS or its tmux, and reading secret files (env files, keys, ~/.ssh, ~/.aws). `}Tokenvault is available through \`tv\`: use a token inside the command that needs it (e.g. \`FOO="\$(tv get <project> <desc>)" cmd\`), never print, echo or log a value, and never send one off the machine except to the service it's for. ${full ? `` : `The list catches honest mistakes, not a determined bypass: a script or \`python -c\` could do any of these unseen, and doing so would break Saad's trust in every gate. `}\`aos notify "<text>"\` pushes a message to Saad's phone. Use it for what he'd want to see now and asked for: the morning report, a real milestone (a stack landed, a blocker only he can clear). Never for routine status: one a minute at most, and he reads the rest here. ${full ? `You can read and write files anywhere (Read, Grep, Glob, Write, Edit, NotebookEdit); nothing is checked.` : `You can read files anywhere (Read, Grep, Glob), and write them (Write, Edit, NotebookEdit) anywhere except: a project repository or task worktree other than your own open quick changes' worktrees (start a task, or a quick change for a small edit, so the change goes through CI, review and scope), AgentOS's own state in ~/.agent-os outside your folder, secret files (.env, keys, ~/.ssh, ~/.aws, tokenvault and the like), and files that make something run: dotfiles and dot-folders at the top of your home (shell, git, Claude and tool config), ~/Library and /Library's launch agents, git's core.hooksPath folders, the folders on PATH, and any .git, .claude, .mcp.json, CLAUDE.md or AGENTS.md, which would hand you a shell or run code around the gates. Each write is checked before it runs, on the path with symlinks and \`..\` resolved; a refused one says why.`}
167
+ Act through these tools, not the shell: they're scoped to this workspace and braked. ${full ? `The shell and your file writes are unchecked (Saad's decision, 2026-10-18: Orchestrator full access, switched in Settings → Workspaces): \`git push\`, \`gh pr merge\`, commits, \`stash\`, \`clean\`, edits in any project checkout or task worktree, AgentOS's own state, dotfiles, launch agents, .claude, CLAUDE.md, .mcp.json, hooks, \`sqlite3\` writes, \`aos sweep --arm\`, other workspaces' repositories. Use that to fix things directly instead of starting a task for every small job, but still prefer the tools for what they cover (they're scoped to this workspace and logged), and never use the shell to get around a hard line. One warning stays: never stop or restart AgentOS or its tmux; that ends every session it runs, this one included. If AgentOS itself needs a restart, \`ask_saad\`. Reading a secret file is allowed; its value is never printed, logged or sent off the machine. ` : `The shell runs anything without asking (Saad chose this on 2026-10-11), so use it to read and investigate yourself: \`gh pr list\`, \`gh run view --log-failed\`, \`gh api\` GETs, \`curl\`, \`sqlite3 -readonly\`, git in any repository, a skill's scripts, pipes and chains. Never use it to get around the tools. A deny list refuses, naming the tool to use instead: merging, closing or retargeting a PR (\`${TOOL_NAMES.sign_off}\`, \`${TOOL_NAMES.arm}\`, \`${TOOL_NAMES.land}\`, \`${TOOL_NAMES.drop}\`), \`git push\` (\`${TOOL_NAMES.quick_pr}\`), writing in a project repository or task worktree other than your own quick changes, except in a project checkout of your own workspace, where you may update and move the branch: \`git pull\` in any form, \`merge\`, \`reset\` (even \`--hard\`), \`checkout\`, \`switch\` and \`rebase\` (also as \`git -C <path>\`), then \`bundle install\`, \`npm install\`, \`pnpm install\` or \`bin/rails db:prepare\`. Commits, \`stash\`, \`clean\`, \`restore\` and file edits there still go through tasks and quick changes, and other workspaces' repositories, task worktrees and AgentOS's own live checkout (autodeploy deploys it) are refused for all of it are refused (\`${TOOL_NAMES.start_task}\`, \`${TOOL_NAMES.quick_change}\`), writing AgentOS's state (sqlite3 without \`-readonly\`, or files in ~/.agent-os outside your folder), stopping or restarting AgentOS or its tmux, and reading secret files (env files, keys, ~/.ssh, ~/.aws). `}Tokenvault is available through \`tv\`: use a token inside the command that needs it (e.g. \`FOO="\$(tv get <project> <desc>)" cmd\`), never print, echo or log a value, and never send one off the machine except to the service it's for. ${full ? `` : `The list catches honest mistakes, not a determined bypass: a script or \`python -c\` could do any of these unseen, and doing so would break Saad's trust in every gate. `}\`aos notify "<text>"\` pushes a message to Saad's phone. Use it for what he'd want to see now and asked for: the morning report, a real milestone (a stack landed, a blocker only he can clear). Never for routine status: one a minute at most, and he reads the rest here. ${full ? `You can read and write files anywhere (Read, Grep, Glob, Write, Edit, NotebookEdit); nothing is checked.` : `You can read files anywhere (Read, Grep, Glob), and write them (Write, Edit, NotebookEdit) anywhere except: a project repository or task worktree other than your own open quick changes' worktrees (start a task, or a quick change for a small edit, so the change goes through CI, review and scope), AgentOS's own state in ~/.agent-os outside your folder, secret files (.env, keys, ~/.ssh, ~/.aws, tokenvault and the like), and files that make something run: dotfiles and dot-folders at the top of your home (shell, git, Claude and tool config), ~/Library and /Library's launch agents, git's core.hooksPath folders, the folders on PATH, and any .git, .claude, .mcp.json, CLAUDE.md or AGENTS.md, which would hand you a shell or run code around the gates. Each write is checked before it runs, on the path with symlinks and \`..\` resolved; a refused one says why.`}
168
168
 
169
169
  ## Untrusted text
170
170
 
@@ -59,8 +59,7 @@ export interface ExternalPR {
59
59
  type WorkspaceTask = Session & { project_name: string };
60
60
 
61
61
  export type GateTarget =
62
- | { kind: "task"; task: WorkspaceTask }
63
- | { kind: "external"; pr: ExternalPR };
62
+ { kind: "task"; task: WorkspaceTask } | { kind: "external"; pr: ExternalPR };
64
63
 
65
64
  const FIELDS =
66
65
  "number,url,state,headRefOid,headRefName,baseRefName,isDraft,labels,isCrossRepository,statusCheckRollup,body,title,mergeable";
@@ -19,12 +19,7 @@ import { untrusted } from "./untrusted";
19
19
  import { markStatusDirty } from "../status/dirty";
20
20
 
21
21
  export type GateName =
22
- | "ci"
23
- | "review"
24
- | "code-review"
25
- | "blocked"
26
- | "scope"
27
- | "stack";
22
+ "ci" | "review" | "code-review" | "blocked" | "scope" | "stack";
28
23
 
29
24
  export interface GateOutcome {
30
25
  gate: GateName;
@@ -72,26 +72,22 @@ export function needsYouItems(input: {
72
72
  }): NeedsYouItem[] {
73
73
  const hidden = new Set(input.dismissed);
74
74
  const items: NeedsYouItem[] = [
75
- ...input.asks.map(
76
- (ask): NeedsYouItem => ({
77
- key: `ask:${ask.id}`,
78
- kind: "ask",
79
- title: ask.title,
80
- sub: ask.kind,
81
- at: sqliteMs(ask.createdAt),
82
- ask,
83
- })
84
- ),
85
- ...input.questions.map(
86
- (question): NeedsYouItem => ({
87
- key: `question:${question.id}`,
88
- kind: "question",
89
- title: question.text,
90
- sub: question.taskName ?? "Question",
91
- at: sqliteMs(question.createdAt),
92
- question,
93
- })
94
- ),
75
+ ...input.asks.map((ask): NeedsYouItem => ({
76
+ key: `ask:${ask.id}`,
77
+ kind: "ask",
78
+ title: ask.title,
79
+ sub: ask.kind,
80
+ at: sqliteMs(ask.createdAt),
81
+ ask,
82
+ })),
83
+ ...input.questions.map((question): NeedsYouItem => ({
84
+ key: `question:${question.id}`,
85
+ kind: "question",
86
+ title: question.text,
87
+ sub: question.taskName ?? "Question",
88
+ at: sqliteMs(question.createdAt),
89
+ question,
90
+ })),
95
91
  ];
96
92
  for (const card of input.cards ?? [])
97
93
  items.push({
@@ -95,8 +95,7 @@ const failures = new Map<string, number>();
95
95
 
96
96
  const sessionRow = (id: string) =>
97
97
  db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
98
- | Session
99
- | undefined;
98
+ Session | undefined;
100
99
 
101
100
  // True when this call took the key; false when it was taken before.
102
101
  const claim = (task: string, key: string, kind: string) =>
@@ -74,8 +74,7 @@ const tried = new Map<string, string>();
74
74
 
75
75
  const sessionRow = (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
  // Every open task PR in the workspace that conflicts with its base.
81
80
  export async function rebaseConflicting(