@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.
- package/README.md +2 -2
- package/app/api/dev-servers/detect/route.ts +1 -2
- package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +2 -4
- package/app/page.tsx +1 -2
- package/components/Artifacts/ArtifactsPanel.tsx +1 -1
- package/components/FileExplorer/FileTabs.tsx +1 -1
- package/components/Pane/DesktopTabBar.tsx +2 -2
- package/components/QuickSwitcher.tsx +24 -21
- package/components/SessionList/SessionRowMenu.tsx +0 -1
- package/components/Settings/ExperimentsSection.test.ts +7 -1
- package/components/Settings/NoApprovalsSection.test.ts +7 -1
- package/components/Terminal/TerminalToolbar.tsx +1 -1
- package/components/Terminal/VirtualKeyboard.tsx +1 -1
- package/components/WorkerCard.tsx +1 -7
- package/components/Workspaces/WorkspaceNameDialog.tsx +5 -3
- package/components/a/AButton.tsx +1 -6
- package/components/a/AIconButton.tsx +1 -5
- package/contexts/PaneContext.tsx +1 -6
- package/data/lumifyhub/index.ts +2 -4
- package/hooks/useDirectoryBrowser.ts +5 -4
- package/hooks/useDrawerAnimation.ts +11 -14
- package/hooks/useFileDrop.ts +2 -6
- package/hooks/useKeybarVisibility.ts +10 -29
- package/hooks/useSidebarPinned.ts +7 -23
- package/hooks/useSpeechRecognition.ts +19 -5
- package/hooks/useStoredFlag.ts +47 -0
- package/hooks/useViewport.ts +11 -17
- package/lib/approvals/no-approvals.ts +1 -7
- package/lib/artifacts/store.ts +1 -2
- package/lib/away/report.ts +1 -2
- package/lib/away/run.ts +1 -2
- package/lib/away/state.ts +1 -2
- package/lib/bookmarks/store.ts +1 -2
- package/lib/bus/delivery.ts +1 -2
- package/lib/bus/index.ts +1 -2
- package/lib/chat/compact-context.ts +1 -2
- package/lib/chat/stop.ts +1 -2
- package/lib/chat/worker/host.ts +1 -1
- package/lib/code-health/reports.ts +39 -3
- package/lib/code-health/source.ts +1 -1
- package/lib/db/lumifyhub.ts +2 -4
- package/lib/db/schedules-move.ts +1 -2
- package/lib/db/session-parts/tasks.ts +1 -2
- package/lib/db/stacks.ts +3 -9
- package/lib/db/types.ts +1 -7
- package/lib/dev-instance/spawnServer.ts +10 -6
- package/lib/doctor/checks.ts +8 -10
- package/lib/done/archive.ts +1 -2
- package/lib/env-setup.ts +2 -0
- package/lib/experiments/meter.ts +1 -7
- package/lib/files.ts +1 -0
- package/lib/health/env/classes.ts +0 -2
- package/lib/health/env/recordHit.ts +1 -2
- package/lib/health/spin.ts +1 -4
- package/lib/health/store.ts +1 -2
- package/lib/hosts/index.ts +1 -2
- package/lib/hosts/peer-sync.ts +2 -4
- package/lib/hosts/remote-api.ts +1 -2
- package/lib/lumifyhub/types.ts +2 -8
- package/lib/notify/index.ts +1 -2
- package/lib/notify/push/index.ts +2 -7
- package/lib/orchestrator/brief.ts +1 -1
- package/lib/orchestrator/external-pr.ts +1 -2
- package/lib/orchestrator/gates.ts +1 -6
- package/lib/orchestrator/needs-you-items.ts +16 -20
- package/lib/orchestrator/pr-wake.ts +1 -2
- package/lib/orchestrator/rebase.ts +1 -2
- package/lib/orchestrator/review.ts +1 -2
- package/lib/orchestrator/usage.ts +1 -2
- package/lib/plans/claim.ts +1 -2
- package/lib/plans/features.ts +4 -11
- package/lib/plans/release.ts +2 -4
- package/lib/ports.ts +1 -2
- package/lib/program-status/records.ts +1 -1
- package/lib/project-config/database.ts +1 -2
- package/lib/projects.ts +1 -2
- package/lib/recipes/settle.ts +2 -4
- package/lib/recipes/store.ts +2 -4
- package/lib/schedules/store.ts +2 -4
- package/lib/search/conversations/backfill.ts +2 -4
- package/lib/security/auth.ts +1 -2
- package/lib/security/passkeys.ts +1 -2
- package/lib/security/presence.ts +1 -7
- package/lib/session-artifacts/types.ts +1 -8
- package/lib/session-audit/transcript.ts +1 -3
- package/lib/session-logs/index.ts +1 -2
- package/lib/sessions/launch.ts +1 -2
- package/lib/share/pdf.ts +32 -1
- package/lib/share/snapshot.ts +1 -7
- package/lib/share/store.ts +1 -2
- package/lib/sidebar/orchestrator-row.ts +1 -6
- package/lib/sidebar/row-menu.ts +1 -2
- package/lib/sidebar/shelves.ts +1 -6
- package/lib/simulator/index.ts +1 -2
- package/lib/simulator/lease.ts +1 -5
- package/lib/stacks/index.ts +1 -1
- package/lib/stacks/plan.ts +1 -1
- package/lib/stacks/restack.ts +1 -2
- package/lib/stacks/tick.ts +1 -2
- package/lib/status/socket.ts +1 -2
- package/lib/tasks/contract/authorize.ts +1 -2
- package/lib/tasks/contract/plan.ts +1 -2
- package/lib/tasks/contract/sync.ts +1 -2
- package/lib/tasks/holds.ts +1 -5
- package/lib/tasks/index.ts +1 -2
- package/lib/tasks/loop-types.ts +1 -6
- package/lib/tasks/move-guard.ts +1 -2
- package/lib/tasks/move.ts +1 -2
- package/lib/tasks/moved-away.ts +1 -2
- package/lib/tasks/pause-hold.ts +1 -2
- package/lib/tasks/pause.ts +3 -6
- package/lib/tasks/pr-activity.ts +1 -3
- package/lib/tasks/queue-rows.ts +2 -4
- package/lib/tasks/queue-types.ts +1 -5
- package/lib/tasks/queue.ts +1 -2
- package/lib/tasks/refresh-base.ts +1 -2
- package/lib/tasks/setup.ts +9 -2
- package/lib/tasks/start.ts +2 -4
- package/lib/tasks/starter.ts +1 -2
- package/lib/tasks/state.ts +1 -6
- package/lib/theme-config.ts +1 -8
- package/lib/updates/banner.ts +1 -6
- package/lib/updates/run.ts +1 -2
- package/lib/usage/aggregate.ts +1 -1
- package/lib/usage/nightly.ts +1 -2
- package/lib/usage/rate-limits.ts +1 -2
- package/lib/usage/turns.ts +1 -2
- package/lib/usage/windows.ts +5 -7
- package/lib/workspaces.ts +1 -2
- package/lib/worktree-deps/clone.ts +10 -0
- package/lib/worktree-deps/fastcopy.ts +8 -1
- package/lib/worktree-deps/index.ts +14 -19
- package/lib/worktree-deps/install.ts +17 -24
- package/lib/worktree-sweep/databases.ts +0 -1
- package/package.json +10 -17
- package/scripts/check-native.mjs +86 -0
- package/scripts/code-health/prepass.ts +3 -3
- package/scripts/install-deps +24 -0
- package/scripts/lib/commands.sh +2 -2
- package/scripts/lib/deps-swap.sh +68 -0
- package/scripts/lib/deps-swap.test.ts +115 -0
- package/scripts/lib/pnpm.sh +36 -0
- package/scripts/lib/prerequisites.sh +9 -9
- package/scripts/publish +3 -2
- package/scripts/redeploy +25 -8
- package/scripts/setup.sh +4 -4
- package/tsconfig.json +1 -0
- package/scripts/ensure-native.mjs +0 -23
package/lib/artifacts/store.ts
CHANGED
|
@@ -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
|
-
|
|
|
58
|
-
| undefined;
|
|
57
|
+
Artifact | undefined;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
export function listArtifacts(sessionId: string): Artifact[] {
|
package/lib/away/report.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
|
47
|
-
| undefined) ?? null
|
|
46
|
+
AwayRow | undefined) ?? null
|
|
48
47
|
);
|
|
49
48
|
}
|
|
50
49
|
|
package/lib/bookmarks/store.ts
CHANGED
|
@@ -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
|
-
|
|
|
87
|
-
| undefined;
|
|
86
|
+
Joined | undefined;
|
|
88
87
|
return r ? toBookmark(r) : null;
|
|
89
88
|
}
|
|
90
89
|
|
package/lib/bus/delivery.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/lib/chat/worker/host.ts
CHANGED
|
@@ -20,17 +20,53 @@ interface NpmVuln {
|
|
|
20
20
|
isDirect?: boolean;
|
|
21
21
|
via?: (string | { title?: string; source?: number })[];
|
|
22
22
|
fixAvailable?:
|
|
23
|
-
| boolean
|
|
24
|
-
|
|
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
|
|
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
|
|
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);
|
package/lib/db/lumifyhub.ts
CHANGED
|
@@ -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
|
-
|
|
|
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
|
-
|
|
69
|
-
| undefined;
|
|
67
|
+
(LumifyHubConnectAttempt & { fresh: number }) | undefined;
|
|
70
68
|
db.prepare(`DELETE FROM lumifyhub_connect_attempts WHERE state = ?`).run(
|
|
71
69
|
state
|
|
72
70
|
);
|
package/lib/db/schedules-move.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
| "
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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
|
-
| "
|
|
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(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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,
|
package/lib/doctor/checks.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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;
|
package/lib/done/archive.ts
CHANGED
|
@@ -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
|
-
|
|
|
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.
|
package/lib/experiments/meter.ts
CHANGED
|
@@ -18,13 +18,7 @@ import { experimentOn } from ".";
|
|
|
18
18
|
import { getExperiment, type ExperimentId } from "./registry";
|
|
19
19
|
|
|
20
20
|
export type ExperimentEvent =
|
|
21
|
-
| "
|
|
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
|
@@ -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
|
-
|
|
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,
|
package/lib/health/spin.ts
CHANGED
|
@@ -45,10 +45,7 @@ export interface SpinSample {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export type SpinClass =
|
|
48
|
-
| "
|
|
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";
|
package/lib/health/store.ts
CHANGED
|
@@ -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
|
-
|
|
|
33
|
-
| undefined;
|
|
32
|
+
AnomalyRow | undefined;
|
|
34
33
|
|
|
35
34
|
export function anomaly(id: number): AnomalyRow | null {
|
|
36
35
|
return rowOf(id) ?? null;
|
package/lib/hosts/index.ts
CHANGED
|
@@ -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
|
-
|
|
|
43
|
-
| undefined) ?? null
|
|
42
|
+
Host | undefined) ?? null
|
|
44
43
|
);
|
|
45
44
|
}
|
|
46
45
|
|
package/lib/hosts/peer-sync.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/lib/hosts/remote-api.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/lib/lumifyhub/types.ts
CHANGED
|
@@ -23,11 +23,7 @@ export interface LhBoard {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export type LhListCategory =
|
|
26
|
-
| "
|
|
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;
|
package/lib/notify/index.ts
CHANGED
|
@@ -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
|
-
|
|
|
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();
|
package/lib/notify/push/index.ts
CHANGED
|
@@ -39,11 +39,7 @@ export function pushStatus(): { linked: boolean; unavailable: string | null } {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export type PushOutcome =
|
|
42
|
-
| "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| "
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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(
|