@saadnvd1/agent-os 0.3.700 → 0.3.715
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 +1 -0
- package/app/api/bus/groups/route.ts +44 -0
- package/app/api/bus/spawn/route.ts +12 -1
- package/app/api/claims/[id]/route.ts +20 -0
- package/app/api/claims/check/route.ts +23 -0
- package/app/api/claims/check.test.ts +113 -0
- package/app/api/claims/exchange/route.ts +42 -0
- package/app/api/claims/outside.test.ts +90 -0
- package/app/api/claims/route.test.ts +193 -0
- package/app/api/claims/route.ts +73 -0
- package/app/api/hosts/[id]/route.ts +9 -1
- package/app/api/peer/unlink/route.ts +17 -0
- package/app/api/peers/[hostId]/audit/route.ts +14 -0
- package/app/api/peers/[hostId]/route.ts +20 -0
- package/app/api/peers/gate.test.ts +64 -1
- package/app/api/peers/pending/route.test.ts +37 -0
- package/app/api/peers/pending/route.ts +11 -4
- package/app/api/session-groups/[id]/restore/route.ts +11 -0
- package/app/api/session-groups/[id]/route.ts +25 -0
- package/app/api/session-groups/order/route.ts +15 -0
- package/app/api/session-groups/route.ts +21 -0
- package/app/api/sessions/[id]/fork/route.ts +3 -0
- package/app/api/sessions/[id]/group/route.ts +18 -0
- package/app/api/sessions/[id]/route.ts +14 -4
- package/app/api/sessions/[id]/summarize/route.ts +2 -0
- package/app/api/sessions/claims.test.ts +122 -0
- package/app/api/sessions/group/route.ts +17 -0
- package/app/api/sessions/route.ts +15 -14
- package/app/page.tsx +5 -0
- package/components/Claims/ClaimFiles.tsx +38 -0
- package/components/Claims/ClaimRow.tsx +82 -0
- package/components/Claims/ClaimsSheet.tsx +176 -0
- package/components/Claims/OverlapPill.tsx +31 -0
- package/components/Claims/StartClaimsNotice.tsx +70 -0
- package/components/Claims/claims-ui.test.ts +161 -0
- package/components/Claims/format.ts +57 -0
- package/components/Claims/index.ts +3 -0
- package/components/Draft/DraftPanel.tsx +40 -2
- package/components/Hosts/AccountMachines.tsx +32 -18
- package/components/Hosts/HostRow.tsx +155 -14
- package/components/Hosts/IncomingLinkSheet.tsx +28 -10
- package/components/Hosts/PeerActivitySheet.tsx +111 -0
- package/components/SessionList/GroupShelves.tsx +195 -0
- package/components/SessionList/MoveToGroupMenu.tsx +105 -0
- package/components/SessionList/RowSubtitle.tsx +7 -1
- package/components/SessionList/SelectionToolbar.tsx +52 -1
- package/components/SessionList/SessionRow.tsx +1 -0
- package/components/SessionList/SessionRowMenu.tsx +17 -0
- package/components/SessionList/SessionShelves.tsx +5 -0
- package/components/SessionList/SidebarBody.tsx +6 -0
- package/components/SessionList/SidebarContextMenu.test.ts +104 -0
- package/components/SessionList/SidebarContextMenu.tsx +59 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
- package/components/SessionList/index.tsx +18 -14
- package/components/SessionList/useSidebarData.ts +10 -0
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- package/data/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/peers/keys.ts +1 -0
- package/data/peers/queries.ts +31 -0
- package/data/push/topics.ts +4 -0
- package/data/session-groups/index.ts +156 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/pin.ts +2 -6
- package/data/sessions/queries.ts +10 -29
- package/data/sessions/shelf.ts +2 -6
- package/data/tasks/index.ts +2 -0
- package/hooks/useSessions.ts +0 -12
- package/lib/__fixtures__/peer-rpc.ts +65 -33
- package/lib/agents/spawn.ts +4 -0
- package/lib/bus/index.ts +8 -0
- package/lib/claims/collectLocal.ts +121 -0
- package/lib/claims/describe.ts +41 -0
- package/lib/claims/exchange.ts +91 -0
- package/lib/claims/goal.ts +45 -0
- package/lib/claims/index.ts +10 -0
- package/lib/claims/loop.ts +51 -0
- package/lib/claims/machine.ts +21 -0
- package/lib/claims/mine.ts +43 -0
- package/lib/claims/notifyOverlaps.ts +101 -0
- package/lib/claims/onMerged.ts +85 -0
- package/lib/claims/outside.ts +36 -0
- package/lib/claims/overlap.ts +87 -0
- package/lib/claims/overlapCache.ts +86 -0
- package/lib/claims/overlapText.ts +36 -0
- package/lib/claims/peers.ts +56 -0
- package/lib/claims/startCheck.ts +100 -0
- package/lib/claims/startWarning.ts +29 -0
- package/lib/claims/store.ts +366 -0
- package/lib/claims/types.ts +102 -0
- package/lib/claims/view.ts +80 -0
- package/lib/claims/yield.ts +24 -0
- package/lib/db/changes.ts +6 -0
- package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
- package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
- package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
- package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
- package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
- package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
- package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
- package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -0
- package/lib/db/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +1 -0
- package/lib/db/types.ts +16 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- package/lib/orchestration.ts +2 -0
- package/lib/orchestrator/act.ts +54 -4
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claim-wait.ts +71 -0
- package/lib/orchestrator/conditions.ts +28 -1
- package/lib/orchestrator/describe.ts +6 -1
- package/lib/orchestrator/facts.ts +5 -0
- package/lib/orchestrator/serve.ts +37 -2
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +31 -1
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/orchestrator/watcher.ts +19 -1
- package/lib/peer/account.ts +74 -2
- package/lib/peer/allowlist.ts +1 -1
- package/lib/peer/client.ts +26 -1
- package/lib/peer/link-cli.ts +19 -4
- package/lib/peer/links.ts +117 -5
- package/lib/peer/machines.ts +14 -2
- package/lib/peer/name.ts +32 -0
- package/lib/peer/pairing.ts +34 -5
- package/lib/peer/pin.ts +10 -3
- package/lib/peer/reasons.ts +7 -0
- package/lib/peer/revoke.ts +75 -0
- package/lib/peer/rpc-server.ts +53 -5
- package/lib/peer/unlink.ts +62 -0
- package/lib/peer/unlinked-proof.ts +44 -0
- package/lib/projects.ts +5 -2
- package/lib/security/demo.ts +7 -0
- package/lib/session-groups/assignSessions.ts +81 -0
- package/lib/session-groups/cli.ts +61 -0
- package/lib/session-groups/createGroup.ts +28 -0
- package/lib/session-groups/index.ts +21 -0
- package/lib/session-groups/removeGroup.ts +36 -0
- package/lib/session-groups/reorderGroups.ts +31 -0
- package/lib/session-groups/resolveGroup.ts +19 -0
- package/lib/session-groups/route.ts +32 -0
- package/lib/session-groups/store.ts +85 -0
- package/lib/session-groups/types.ts +25 -0
- package/lib/session-groups/updateGroup.ts +35 -0
- package/lib/session-groups/workspace.ts +21 -0
- package/lib/sessions/launch.ts +5 -2
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +2 -0
- package/lib/sidebar/shelves.ts +90 -15
- package/lib/sidebar/visible-order.ts +5 -0
- package/lib/tasks/hold-checks.ts +3 -1
- package/lib/tasks/holds.ts +3 -0
- package/lib/tasks/index.ts +5 -0
- package/lib/tasks/queue-rows.ts +8 -1
- package/lib/tasks/queue-start.ts +1 -0
- package/lib/tasks/queue-types.ts +9 -1
- package/lib/tasks/queue.ts +15 -5
- package/lib/tasks/schedule-types.ts +1 -0
- package/lib/test-temp.ts +19 -3
- package/lib/workspaces.ts +11 -3
- package/lib/worktree-sweep/testTemp.ts +23 -106
- package/package.json +1 -1
- package/scripts/agent-os +1 -1
- package/scripts/autodeploy +2 -0
- package/scripts/autodeploy.test.ts +31 -1
- package/scripts/claim-hook.sh +31 -0
- package/scripts/claim-hook.test.ts +230 -0
- package/scripts/lib/cli-links.sh +36 -0
- package/scripts/lib/commands.sh +3 -0
- package/scripts/lib/machine.sh +27 -2
- package/scripts/lib/machine.test.ts +25 -3
- package/scripts/sync-cli-links +7 -0
- package/scripts/sync-cli-links.test.ts +69 -0
- package/server.ts +3 -0
- package/stores/claimsUi.ts +22 -0
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
package/lib/sidebar/shelves.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The sidebar's one flat list, sorted onto shelves: the workspace's pinned
|
|
2
|
-
// orchestrator, Pinned, Needs you, Working, In review,
|
|
3
|
-
// Done. Client-safe: no database.
|
|
2
|
+
// orchestrator, Pinned, the user's groups, Needs you, Working, In review,
|
|
3
|
+
// Ongoing, Snoozed, then Done. Client-safe: no database.
|
|
4
4
|
import type { Session } from "@/lib/db/types";
|
|
5
5
|
import { fromSqliteTime } from "@/lib/session-meta";
|
|
6
6
|
import type { PrStage } from "@/lib/tasks/pr-stage";
|
|
@@ -42,6 +42,25 @@ export interface RowStatus {
|
|
|
42
42
|
detail?: string | null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// A named shelf the user files sessions into (lib/session-groups). Native
|
|
46
|
+
// reads the same shape from GET /api/sessions.
|
|
47
|
+
export interface SessionGroup {
|
|
48
|
+
id: string;
|
|
49
|
+
workspace_id: string | null;
|
|
50
|
+
name: string;
|
|
51
|
+
sort_order: number;
|
|
52
|
+
collapsed: boolean;
|
|
53
|
+
created_at: string;
|
|
54
|
+
updated_at: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface GroupShelf {
|
|
58
|
+
group: SessionGroup;
|
|
59
|
+
rows: SidebarRow[];
|
|
60
|
+
// What a folded header still has to say.
|
|
61
|
+
rollup: { needsYou: number };
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
export interface SidebarRow {
|
|
46
65
|
session: Session;
|
|
47
66
|
status: RowStatus | undefined;
|
|
@@ -60,8 +79,11 @@ export interface SidebarRow {
|
|
|
60
79
|
activeAt: number;
|
|
61
80
|
// While snoozed: when it wakes (ms).
|
|
62
81
|
wakesAt?: number;
|
|
63
|
-
// A plain session in Done: marked by hand, or settled by itself.
|
|
82
|
+
// A plain session in Done: marked by hand, or settled by itself. In a
|
|
83
|
+
// group, "auto" only mutes the row: it stays where it was put.
|
|
64
84
|
settled?: "manual" | "auto";
|
|
85
|
+
// The group it belongs to, even while Done holds it (Reopen goes back).
|
|
86
|
+
groupName?: string;
|
|
65
87
|
// Its agent is being stopped, is stopped, or is starting again
|
|
66
88
|
// (lib/tasks/pause): until is when it resumes by itself (ms), null: when
|
|
67
89
|
// someone resumes it.
|
|
@@ -76,6 +98,8 @@ export interface Shelves {
|
|
|
76
98
|
// Each shown workspace's current orchestrator, while it's pinned.
|
|
77
99
|
orchestrators: SidebarRow[];
|
|
78
100
|
pinned: SidebarRow[];
|
|
101
|
+
// The shown groups in order, empty ones too.
|
|
102
|
+
groups: GroupShelf[];
|
|
79
103
|
needsYou: SidebarRow[];
|
|
80
104
|
working: SidebarRow[];
|
|
81
105
|
inReview: SidebarRow[];
|
|
@@ -145,6 +169,9 @@ export interface ShelfInput {
|
|
|
145
169
|
currentOrchestrators?: ReadonlySet<string>;
|
|
146
170
|
// Only the sessions this orchestrator session started.
|
|
147
171
|
startedBy?: string | null;
|
|
172
|
+
// The live groups to show, in the order to draw them. A session in any
|
|
173
|
+
// other group id is filed by status as if it had none.
|
|
174
|
+
groups?: readonly SessionGroup[];
|
|
148
175
|
// The clock (ms): wakes and 3-day settles are read from it.
|
|
149
176
|
now?: number;
|
|
150
177
|
}
|
|
@@ -274,6 +301,24 @@ function toRow(input: ShelfInput, session: Session, now: number): SidebarRow {
|
|
|
274
301
|
};
|
|
275
302
|
}
|
|
276
303
|
|
|
304
|
+
// Where a row goes when nothing the user did holds it: what it needs, what
|
|
305
|
+
// it's doing, then how finished it is. Sets settled on a plain session in Done.
|
|
306
|
+
function statusShelf(
|
|
307
|
+
row: SidebarRow,
|
|
308
|
+
now: number
|
|
309
|
+
): "needsYou" | "working" | "inReview" | "ongoing" | "snoozed" | "done" {
|
|
310
|
+
if (row.need) return "needsYou";
|
|
311
|
+
// A stopped agent is not working, whatever its last status said.
|
|
312
|
+
if (row.paused?.state === "paused") return "snoozed";
|
|
313
|
+
if (row.working) return "working";
|
|
314
|
+
if (row.wakesAt) return "snoozed";
|
|
315
|
+
if (row.task || row.session.task_status) return quietShelf(row.task);
|
|
316
|
+
const settled = settledHow(row.session, now);
|
|
317
|
+
if (!settled) return "ongoing";
|
|
318
|
+
row.settled = settled;
|
|
319
|
+
return "done";
|
|
320
|
+
}
|
|
321
|
+
|
|
277
322
|
export function buildShelves(input: ShelfInput): Shelves {
|
|
278
323
|
const now = input.now ?? Date.now();
|
|
279
324
|
const rows = input.sessions
|
|
@@ -314,6 +359,7 @@ export function buildShelves(input: ShelfInput): Shelves {
|
|
|
314
359
|
const shelves: Shelves = {
|
|
315
360
|
orchestrators: [],
|
|
316
361
|
pinned: [],
|
|
362
|
+
groups: [],
|
|
317
363
|
needsYou: [],
|
|
318
364
|
working: [],
|
|
319
365
|
inReview: [],
|
|
@@ -321,26 +367,36 @@ export function buildShelves(input: ShelfInput): Shelves {
|
|
|
321
367
|
snoozed: [],
|
|
322
368
|
done: [],
|
|
323
369
|
};
|
|
370
|
+
const filtered = !!input.query?.trim() || !!input.projectId;
|
|
371
|
+
const grouped = new Map<string, GroupShelf>(
|
|
372
|
+
(input.groups ?? []).map((group) => [
|
|
373
|
+
group.id,
|
|
374
|
+
{ group, rows: [], rollup: { needsYou: 0 } },
|
|
375
|
+
])
|
|
376
|
+
);
|
|
324
377
|
for (const row of top) {
|
|
325
378
|
const { pinned, role, id } = row.session;
|
|
326
379
|
const current = !!input.currentOrchestrators?.has(id);
|
|
380
|
+
const mine = grouped.get(row.session.session_group_id ?? "");
|
|
381
|
+
// Named on a row outside its group (Pinned, Done); inside, the header says it.
|
|
382
|
+
if (mine) row.groupName = mine.group.name;
|
|
327
383
|
if (pinned && current) shelves.orchestrators.push(row);
|
|
328
384
|
else if (pinned && role !== "orchestrator") shelves.pinned.push(row);
|
|
329
|
-
else if (row.need) shelves.needsYou.push(row);
|
|
330
|
-
// A stopped agent is not working, whatever its last status said.
|
|
331
|
-
else if (row.paused?.state === "paused") shelves.snoozed.push(row);
|
|
332
|
-
else if (row.working) shelves.working.push(row);
|
|
333
|
-
else if (row.wakesAt) shelves.snoozed.push(row);
|
|
334
|
-
else if (row.task || row.session.task_status)
|
|
335
|
-
shelves[quietShelf(row.task)].push(row);
|
|
336
385
|
else {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
386
|
+
const shelf = statusShelf(row, now);
|
|
387
|
+
// Membership beats status. Only finished work goes on to Done (it keeps
|
|
388
|
+
// its group); a session that merely went quiet stays, muted.
|
|
389
|
+
if (mine && (shelf !== "done" || row.settled === "auto")) {
|
|
390
|
+
delete row.groupName;
|
|
391
|
+
mine.rows.push(row);
|
|
392
|
+
if (row.need) mine.rollup.needsYou++;
|
|
393
|
+
} else shelves[shelf].push(row);
|
|
342
394
|
}
|
|
343
395
|
}
|
|
396
|
+
for (const shelf of grouped.values()) {
|
|
397
|
+
shelf.rows.sort((a, b) => b.activeAt - a.activeAt);
|
|
398
|
+
if (!filtered || shelf.rows.length > 0) shelves.groups.push(shelf);
|
|
399
|
+
}
|
|
344
400
|
shelves.orchestrators.sort(oldestFirst);
|
|
345
401
|
shelves.pinned.sort(oldestFirst);
|
|
346
402
|
shelves.needsYou.sort(newestFirst);
|
|
@@ -357,6 +413,24 @@ export function buildShelves(input: ShelfInput): Shelves {
|
|
|
357
413
|
return shelves;
|
|
358
414
|
}
|
|
359
415
|
|
|
416
|
+
// The groups to draw, in the order to draw them: by workspace (in the order
|
|
417
|
+
// given; groups of no workspace last), then by their own order.
|
|
418
|
+
export function orderGroups(
|
|
419
|
+
groups: readonly SessionGroup[],
|
|
420
|
+
workspaceOrder: readonly string[]
|
|
421
|
+
): SessionGroup[] {
|
|
422
|
+
const rank = (g: SessionGroup) => {
|
|
423
|
+
const at = g.workspace_id ? workspaceOrder.indexOf(g.workspace_id) : -1;
|
|
424
|
+
return at < 0 ? workspaceOrder.length : at;
|
|
425
|
+
};
|
|
426
|
+
return [...groups].sort(
|
|
427
|
+
(a, b) =>
|
|
428
|
+
rank(a) - rank(b) ||
|
|
429
|
+
a.sort_order - b.sort_order ||
|
|
430
|
+
a.created_at.localeCompare(b.created_at)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
360
434
|
// Done shows 10 at first, then 25 more per "Show more".
|
|
361
435
|
export const doneLimit = (pages: number) =>
|
|
362
436
|
DONE_FIRST_PAGE + Math.max(0, pages) * DONE_PAGE;
|
|
@@ -424,6 +498,7 @@ export function sameRow(a: SidebarRow, b: SidebarRow): boolean {
|
|
|
424
498
|
a.paused?.until === b.paused?.until &&
|
|
425
499
|
a.paused?.note === b.paused?.note &&
|
|
426
500
|
a.settled === b.settled &&
|
|
501
|
+
a.groupName === b.groupName &&
|
|
427
502
|
a.workers.length === b.workers.length &&
|
|
428
503
|
a.workers.every((w, i) => sameRow(w, b.workers[i])) &&
|
|
429
504
|
(a.quick?.length ?? 0) === (b.quick?.length ?? 0) &&
|
|
@@ -11,6 +11,10 @@ export interface ShelfFolds {
|
|
|
11
11
|
// to the pages shown. SessionShelves renders from this too, so the two agree.
|
|
12
12
|
export function shownShelves(shelves: Shelves, folds: ShelfFolds) {
|
|
13
13
|
return {
|
|
14
|
+
groups: shelves.groups.map((g) => ({
|
|
15
|
+
...g,
|
|
16
|
+
rows: g.group.collapsed ? [] : g.rows,
|
|
17
|
+
})),
|
|
14
18
|
snoozed: folds.snoozedCollapsed ? [] : shelves.snoozed,
|
|
15
19
|
done: folds.doneCollapsed
|
|
16
20
|
? []
|
|
@@ -32,6 +36,7 @@ export function visibleSessionIds(
|
|
|
32
36
|
return flatten([
|
|
33
37
|
...shelves.orchestrators,
|
|
34
38
|
...shelves.pinned,
|
|
39
|
+
...shown.groups.flatMap((g) => g.rows),
|
|
35
40
|
...shelves.needsYou,
|
|
36
41
|
...shelves.working,
|
|
37
42
|
...shelves.inReview,
|
package/lib/tasks/hold-checks.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { DEFAULT_PRIORITY, type Priority } from "./schedule-types";
|
|
|
16
16
|
import { machineHolds, holdReason, type Hold } from "./holds";
|
|
17
17
|
import { heavyNeeds, meetsNeeds, needsOf, type Need } from "./placement";
|
|
18
18
|
import { expandHome } from "./session";
|
|
19
|
-
import { afterLabel, hasFinished } from "./queue-rows";
|
|
19
|
+
import { afterLabel, afterMet, hasFinished } from "./queue-rows";
|
|
20
20
|
import type { QueueRow } from "./queue-types";
|
|
21
21
|
import type { PauseBy } from "./schedule-types";
|
|
22
22
|
|
|
@@ -46,6 +46,8 @@ export const autoHold = (
|
|
|
46
46
|
|
|
47
47
|
// `after X`: the task it waits on hasn't finished.
|
|
48
48
|
export function afterHold(r: QueueRow): Hold | null {
|
|
49
|
+
if (r.after_claim && !afterMet(r))
|
|
50
|
+
return { code: "after_claim", text: `After ${afterLabel(r)}` };
|
|
49
51
|
if (!r.after_task || hasFinished(r.after_task)) return null;
|
|
50
52
|
return {
|
|
51
53
|
code: "after",
|
package/lib/tasks/holds.ts
CHANGED
|
@@ -64,6 +64,7 @@ export type MachineCode =
|
|
|
64
64
|
export type Hold =
|
|
65
65
|
| { code: "after"; text: string }
|
|
66
66
|
| { code: "after_any"; text: string }
|
|
67
|
+
| { code: "after_claim"; text: string }
|
|
67
68
|
| { code: "orch_paused"; text: string }
|
|
68
69
|
| { code: "window"; text: string }
|
|
69
70
|
| {
|
|
@@ -80,6 +81,7 @@ export type Hold =
|
|
|
80
81
|
export const HOLD_ORDER: readonly WaitCode[] = [
|
|
81
82
|
"after",
|
|
82
83
|
"after_any",
|
|
84
|
+
"after_claim",
|
|
83
85
|
"orch_paused",
|
|
84
86
|
"window",
|
|
85
87
|
"needs",
|
|
@@ -95,6 +97,7 @@ export const HOLD_ORDER: readonly WaitCode[] = [
|
|
|
95
97
|
const CLEARS_BY_ITSELF: Record<WaitCode, boolean> = {
|
|
96
98
|
after: true,
|
|
97
99
|
after_any: true,
|
|
100
|
+
after_claim: true,
|
|
98
101
|
orch_paused: false,
|
|
99
102
|
window: true,
|
|
100
103
|
needs: false,
|
package/lib/tasks/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
type TaskState,
|
|
48
48
|
} from "./state";
|
|
49
49
|
import { expandHome, prFor, storedPR, taskSessions } from "./session";
|
|
50
|
+
import { fileInGroup } from "../session-groups/assignSessions";
|
|
50
51
|
import { InProgressError, type OrchestratedBundle } from "./move-bundle";
|
|
51
52
|
import { holdFor, mayHold } from "./held";
|
|
52
53
|
import { orchestratorOf, recordStart, type StartVia } from "./starter";
|
|
@@ -169,6 +170,8 @@ export async function createTask(opts: {
|
|
|
169
170
|
// How its agent runs: as a chat (the default, DEFAULT_START_VIEW) or in
|
|
170
171
|
// a terminal.
|
|
171
172
|
view?: StartView;
|
|
173
|
+
// The user's group it joins as it starts (lib/session-groups).
|
|
174
|
+
groupId?: string;
|
|
172
175
|
// Started by another machine's orchestrator, which this one holds it for
|
|
173
176
|
// (held.ts): that machine's paired device and its description of the task.
|
|
174
177
|
held?: { device: string; orchestrated: OrchestratedBundle };
|
|
@@ -292,6 +295,7 @@ async function startTask(
|
|
|
292
295
|
startedByDevice: opts.held?.device ?? opts.startedByDevice,
|
|
293
296
|
});
|
|
294
297
|
if (opts.pieceId) claimPiece(opts.pieceId, session.id);
|
|
298
|
+
if (opts.groupId) fileInGroup(session.id, opts.groupId);
|
|
295
299
|
opts.onCreated?.(session.id);
|
|
296
300
|
return session;
|
|
297
301
|
}
|
|
@@ -416,6 +420,7 @@ async function startTask(
|
|
|
416
420
|
throw error;
|
|
417
421
|
}
|
|
418
422
|
if (opts.held) holdFor(id, opts.held.device, opts.held.orchestrated);
|
|
423
|
+
if (opts.groupId) fileInGroup(id, opts.groupId);
|
|
419
424
|
opts.onCreated?.(id);
|
|
420
425
|
void naming.refine?.(id);
|
|
421
426
|
|
package/lib/tasks/queue-rows.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { db, type Session } from "../db";
|
|
7
7
|
import { getProject } from "../projects";
|
|
8
8
|
import { getWorkspace } from "../workspaces";
|
|
9
|
+
import { liveClaim } from "../claims/store";
|
|
9
10
|
import { queuedLabel } from "./queue-held";
|
|
10
11
|
import type { Group } from "./schedule";
|
|
11
12
|
import type { QueueRow } from "./queue-types";
|
|
@@ -106,6 +107,8 @@ export function stillWorking(taskId: string): boolean {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
export function afterMet(r: QueueRow): boolean {
|
|
110
|
+
// A claim is over when it is gone, released or expired.
|
|
111
|
+
if (r.after_claim && liveClaim(r.after_claim)) return false;
|
|
109
112
|
if (r.after_task && !hasFinished(r.after_task)) return false;
|
|
110
113
|
if (r.after_any && !r.after_any_used) {
|
|
111
114
|
const ids = JSON.parse(r.after_any) as string[];
|
|
@@ -171,7 +174,7 @@ export const waitingIn = (workspaceId: string) =>
|
|
|
171
174
|
.prepare(
|
|
172
175
|
`SELECT COUNT(*) AS n FROM task_queue q JOIN projects p ON p.id = q.project_id
|
|
173
176
|
WHERE q.status = 'queued' AND p.workspace_id = ?
|
|
174
|
-
AND q.after_task IS NULL AND q.after_any IS NULL
|
|
177
|
+
AND q.after_task IS NULL AND q.after_any IS NULL AND q.after_claim IS NULL
|
|
175
178
|
AND q.plan_only IS NULL`
|
|
176
179
|
)
|
|
177
180
|
.get(workspaceId) as { n: number }
|
|
@@ -180,6 +183,10 @@ export const waitingIn = (workspaceId: string) =>
|
|
|
180
183
|
export const label = queuedLabel;
|
|
181
184
|
|
|
182
185
|
export function afterLabel(r: QueueRow): string | null {
|
|
186
|
+
if (r.after_claim) {
|
|
187
|
+
const c = liveClaim(r.after_claim);
|
|
188
|
+
return c ? `${c.goal} on ${c.originName}` : "a work claim";
|
|
189
|
+
}
|
|
183
190
|
if (r.after_task) {
|
|
184
191
|
const s = sessionOf(r.after_task);
|
|
185
192
|
const q = s ? null : row(r.after_task);
|
package/lib/tasks/queue-start.ts
CHANGED
|
@@ -138,6 +138,7 @@ export async function startRow(
|
|
|
138
138
|
startedVia: (r.started_via ??
|
|
139
139
|
(r.started_by ? "session" : "person")) as StartVia,
|
|
140
140
|
view: r.view ?? undefined,
|
|
141
|
+
groupId: r.session_group_id ?? undefined,
|
|
141
142
|
agent: (r.agent_type as AgentType | null) ?? undefined,
|
|
142
143
|
baseBranch: r.base_branch ?? undefined,
|
|
143
144
|
hostId: placed?.hostId ?? r.host_id ?? undefined,
|
package/lib/tasks/queue-types.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface QueueRow {
|
|
|
26
26
|
started_by_device: string | null;
|
|
27
27
|
started_via: string | null;
|
|
28
28
|
view: "chat" | "terminal" | null;
|
|
29
|
+
// The user's group it joins when it starts (lib/session-groups).
|
|
30
|
+
session_group_id: string | null;
|
|
29
31
|
agent_type: string | null;
|
|
30
32
|
base_branch: string | null;
|
|
31
33
|
host_id: string | null;
|
|
@@ -37,6 +39,9 @@ export interface QueueRow {
|
|
|
37
39
|
// The finish (a task id) this "after any" waiter took; one finish lets one
|
|
38
40
|
// waiter go (a unique index keeps it so).
|
|
39
41
|
after_any_used: string | null;
|
|
42
|
+
// A work claim (lib/claims) it waits on, by claim id, until the claim is
|
|
43
|
+
// released or expires.
|
|
44
|
+
after_claim: string | null;
|
|
40
45
|
priority: Priority;
|
|
41
46
|
// What it needs from a machine, as JSON, when the starter said.
|
|
42
47
|
machine_needs: string | null;
|
|
@@ -102,10 +107,13 @@ export interface QueueRequest {
|
|
|
102
107
|
// The waiting plan piece it builds, claimed for its queue id.
|
|
103
108
|
pieceId?: number;
|
|
104
109
|
view?: "chat" | "terminal";
|
|
110
|
+
// The user's group it joins when it starts: a live group of its workspace.
|
|
111
|
+
groupId?: string;
|
|
105
112
|
agent?: AgentType;
|
|
106
113
|
baseBranch?: string;
|
|
107
114
|
hostId?: string;
|
|
108
|
-
// A task's id or name,
|
|
115
|
+
// A task's id or name, "any" for whichever running task finishes first, or
|
|
116
|
+
// "claim:<id>" for a work claim on a linked machine.
|
|
109
117
|
after?: string;
|
|
110
118
|
// Set when the orchestrator starts it.
|
|
111
119
|
originWorkspaceId?: string;
|
package/lib/tasks/queue.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { randomUUID } from "crypto";
|
|
|
23
23
|
import { db } from "../db";
|
|
24
24
|
import { getProject } from "../projects";
|
|
25
25
|
import { isRemoteHost } from "../hosts";
|
|
26
|
+
import { liveClaim } from "../claims/store";
|
|
26
27
|
import { isPaused } from "../orchestrator/pause";
|
|
27
28
|
import { BrakeRefused, recordStart } from "../orchestrator/brakes";
|
|
28
29
|
import { discardQueuedHeld, queuedLabel } from "./queue-held";
|
|
@@ -81,6 +82,7 @@ export function queueIfNeeded(req: QueueRequest): QueuedTaskView | null {
|
|
|
81
82
|
const limit = limitOf(workspaceId);
|
|
82
83
|
let afterTask: string | null = null;
|
|
83
84
|
let afterAny: string[] | null = null;
|
|
85
|
+
let afterClaim: string | null = null;
|
|
84
86
|
const after = req.after?.trim();
|
|
85
87
|
// "any" with nothing working is nothing to wait for: it starts at once.
|
|
86
88
|
let waits = !!after;
|
|
@@ -92,6 +94,12 @@ export function queueIfNeeded(req: QueueRequest): QueuedTaskView | null {
|
|
|
92
94
|
afterAny = null;
|
|
93
95
|
waits = false;
|
|
94
96
|
}
|
|
97
|
+
} else if (after?.toLowerCase().startsWith("claim:")) {
|
|
98
|
+
afterClaim = after.slice("claim:".length).trim();
|
|
99
|
+
if (!liveClaim(afterClaim))
|
|
100
|
+
throw new Error(
|
|
101
|
+
`No live work claim "${afterClaim}": it ended, or it isn't known here`
|
|
102
|
+
);
|
|
95
103
|
} else if (after)
|
|
96
104
|
afterTask = resolveAfter(after, req.originWorkspaceId ?? workspaceId).id;
|
|
97
105
|
if (req.now && waits)
|
|
@@ -133,10 +141,10 @@ export function queueIfNeeded(req: QueueRequest): QueuedTaskView | null {
|
|
|
133
141
|
// The row and the piece claim stand or fall together.
|
|
134
142
|
db.transaction(() => {
|
|
135
143
|
db.prepare(
|
|
136
|
-
`INSERT INTO task_queue (id, project_id, prompt, name, model, model_overrides, plan, plan_reason, plan_only, started_by, parent_session_id, started_by_device, started_via, view, agent_type, base_branch,
|
|
137
|
-
host_id, after_task, after_any, origin_workspace_id, priority, machine_needs, position)
|
|
138
|
-
VALUES (@id, @project_id, @prompt, @name, @model, @model_overrides, @plan, @plan_reason, @plan_only, @started_by, @parent_session_id, @started_by_device, @started_via, @view, @agent_type, @base_branch,
|
|
139
|
-
@host_id, @after_task, @after_any, @origin, @priority, @machine_needs,
|
|
144
|
+
`INSERT INTO task_queue (id, project_id, prompt, name, model, model_overrides, plan, plan_reason, plan_only, started_by, parent_session_id, started_by_device, started_via, view, session_group_id, agent_type, base_branch,
|
|
145
|
+
host_id, after_task, after_any, after_claim, origin_workspace_id, priority, machine_needs, position)
|
|
146
|
+
VALUES (@id, @project_id, @prompt, @name, @model, @model_overrides, @plan, @plan_reason, @plan_only, @started_by, @parent_session_id, @started_by_device, @started_via, @view, @session_group_id, @agent_type, @base_branch,
|
|
147
|
+
@host_id, @after_task, @after_any, @after_claim, @origin, @priority, @machine_needs,
|
|
140
148
|
(SELECT COALESCE(MAX(position), 0) + 1 FROM task_queue))`
|
|
141
149
|
).run({
|
|
142
150
|
id,
|
|
@@ -156,11 +164,13 @@ export function queueIfNeeded(req: QueueRequest): QueuedTaskView | null {
|
|
|
156
164
|
started_by_device: req.startedByDevice ?? null,
|
|
157
165
|
started_via: req.startedVia,
|
|
158
166
|
view: req.view ?? null,
|
|
167
|
+
session_group_id: req.groupId ?? null,
|
|
159
168
|
agent_type: req.agent ?? null,
|
|
160
169
|
base_branch: req.baseBranch || null,
|
|
161
170
|
host_id: req.hostId || null,
|
|
162
171
|
after_task: afterTask,
|
|
163
172
|
after_any: afterAny ? JSON.stringify(afterAny) : null,
|
|
173
|
+
after_claim: afterClaim,
|
|
164
174
|
origin: req.originWorkspaceId ?? null,
|
|
165
175
|
priority: req.priority ?? DEFAULT_PRIORITY,
|
|
166
176
|
machine_needs: setting ? JSON.stringify(setting) : null,
|
|
@@ -217,7 +227,7 @@ export async function startQueuedForOrchestrator(
|
|
|
217
227
|
wasFull: slots.full,
|
|
218
228
|
running: slots.running,
|
|
219
229
|
limit: slots.limit,
|
|
220
|
-
skippedAfter: !!(r.after_task || r.after_any),
|
|
230
|
+
skippedAfter: !!(r.after_task || r.after_any || r.after_claim),
|
|
221
231
|
};
|
|
222
232
|
const held = (text: string): ForcedStart => ({
|
|
223
233
|
...base,
|
package/lib/test-temp.ts
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, sep } from "node:path";
|
|
4
|
+
import { RUN_PREFIX } from "@/lib/worktree-sweep/testTemp";
|
|
4
5
|
|
|
5
6
|
// Folders made by tempDir, removed when the test file ends (vitest.setup.ts
|
|
6
7
|
// calls removeTempDirs). Kept on globalThis: vi.resetModules() would
|
|
7
8
|
// otherwise lose the list.
|
|
8
9
|
const KEY = Symbol.for("agentos.testTempDirs");
|
|
10
|
+
const ROOT_KEY = Symbol.for("agentos.testTempRoot");
|
|
9
11
|
const made = (): Set<string> => {
|
|
10
12
|
const g = globalThis as unknown as Record<symbol, Set<string> | undefined>;
|
|
11
13
|
return (g[KEY] ??= new Set<string>());
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
// The sweep recognises a test's leftovers by the RUN_PREFIX folder above
|
|
17
|
+
// them. The suite's TMPDIR is already inside one; outside it (a script
|
|
18
|
+
// importing this) the folder is made here, so every tempDir is under one.
|
|
19
|
+
function base(): string {
|
|
20
|
+
const root = tmpdir();
|
|
21
|
+
if (root.split(sep).some((s) => s.startsWith(RUN_PREFIX))) return root;
|
|
22
|
+
const g = globalThis as unknown as Record<symbol, string | undefined>;
|
|
23
|
+
if (!g[ROOT_KEY] || !existsSync(g[ROOT_KEY])) {
|
|
24
|
+
g[ROOT_KEY] = mkdtempSync(join(root, RUN_PREFIX));
|
|
25
|
+
made().add(g[ROOT_KEY]);
|
|
26
|
+
}
|
|
27
|
+
return g[ROOT_KEY];
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/** A fresh temp folder that is removed when the test file ends. */
|
|
15
31
|
export function tempDir(prefix: string): string {
|
|
16
|
-
const dir = mkdtempSync(join(
|
|
32
|
+
const dir = mkdtempSync(join(base(), prefix));
|
|
17
33
|
made().add(dir);
|
|
18
34
|
return dir;
|
|
19
35
|
}
|
package/lib/workspaces.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
7
|
import { db, type Workspace } from "./db";
|
|
8
|
+
import { clearForeignGroup } from "./session-groups/assignSessions";
|
|
8
9
|
|
|
9
10
|
type WorkspaceRow = Omit<
|
|
10
11
|
Workspace,
|
|
@@ -141,7 +142,14 @@ export function setProjectWorkspace(
|
|
|
141
142
|
if (workspaceId && !getWorkspace(workspaceId)) {
|
|
142
143
|
throw new Error("Unknown workspace");
|
|
143
144
|
}
|
|
144
|
-
db.
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
db.transaction(() => {
|
|
146
|
+
db.prepare(
|
|
147
|
+
`UPDATE projects SET workspace_id = ? WHERE id = ? AND is_uncategorized = 0`
|
|
148
|
+
).run(workspaceId, projectId);
|
|
149
|
+
// Its sessions changed workspace, so they leave the old one's groups.
|
|
150
|
+
const ids = db
|
|
151
|
+
.prepare(`SELECT id FROM sessions WHERE project_id = ?`)
|
|
152
|
+
.all(projectId) as { id: string }[];
|
|
153
|
+
for (const { id } of ids) clearForeignGroup(id);
|
|
154
|
+
})();
|
|
147
155
|
}
|
|
@@ -1,116 +1,33 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// What the sweep may take from the OS temp folder as a test run's leftovers.
|
|
2
|
+
// Every folder a test makes with tempDir() lives inside a run root named
|
|
3
|
+
// RUN_PREFIX…, so the sweep matches that one prefix and a new test needs no
|
|
4
|
+
// list edit. Names AgentOS makes itself (aos-snap-, aos-review-…) never
|
|
5
|
+
// start with it.
|
|
6
|
+
|
|
7
|
+
/** Short: a Unix socket path is capped at 104 bytes on macOS. */
|
|
8
|
+
export const RUN_PREFIX = "aos-t-";
|
|
9
|
+
|
|
10
|
+
// Names only the test suite created, left directly in the OS temp folder by
|
|
11
|
+
// runs from before every test got its own root. Frozen: nothing new belongs
|
|
12
|
+
// here. Names server code also creates (agentos-db-, agentos-img-…) are not
|
|
13
|
+
// listed, since a live server could own them.
|
|
14
|
+
export const LEGACY_TEST_TEMP_PREFIXES: readonly string[] = [
|
|
15
|
+
"agent-os-test-",
|
|
7
16
|
"agent-os-home-",
|
|
8
|
-
"agent-os-kept-",
|
|
9
17
|
"agent-os-limits-",
|
|
18
|
+
"agent-os-git-",
|
|
10
19
|
"agent-os-session-logs-",
|
|
11
|
-
"agent-os-
|
|
12
|
-
"aos-acc-",
|
|
13
|
-
"aos-add-",
|
|
14
|
-
"aos-armed-repo-",
|
|
15
|
-
"aos-art-",
|
|
16
|
-
"aos-autodeploy-",
|
|
17
|
-
"aos-caches-",
|
|
18
|
-
"aos-channel-",
|
|
19
|
-
"aos-clone-",
|
|
20
|
-
"aos-config-",
|
|
21
|
-
"aos-crypt-",
|
|
22
|
-
"aos-db-",
|
|
23
|
-
"aos-db-drops-",
|
|
24
|
-
"aos-db-wt-",
|
|
25
|
-
"aos-decl-",
|
|
26
|
-
"aos-device-keys-",
|
|
27
|
-
"aos-done-home-",
|
|
20
|
+
"agent-os-kept-",
|
|
28
21
|
"aos-done-repo-",
|
|
29
|
-
"aos-
|
|
30
|
-
"aos-
|
|
31
|
-
"aos-files-",
|
|
32
|
-
"aos-git-",
|
|
33
|
-
"aos-gr-",
|
|
34
|
-
"aos-heavy-",
|
|
35
|
-
"aos-init-route-",
|
|
36
|
-
"aos-install-",
|
|
37
|
-
"aos-kind-",
|
|
38
|
-
"aos-launch-plain-",
|
|
39
|
-
"aos-launch-repo-",
|
|
40
|
-
"aos-machine-",
|
|
41
|
-
"aos-limits-",
|
|
42
|
-
"aos-mcp-",
|
|
43
|
-
"aos-merge-policy-",
|
|
44
|
-
"aos-mig-",
|
|
45
|
-
"aos-move-",
|
|
46
|
-
"aos-no-approvals-",
|
|
47
|
-
"aos-nogit-",
|
|
48
|
-
"aos-notify-",
|
|
49
|
-
"aos-orch-act-",
|
|
50
|
-
"aos-orch-add-",
|
|
51
|
-
"aos-orch-approve-",
|
|
52
|
-
"aos-orch-armed-",
|
|
53
|
-
"aos-orch-asks-",
|
|
54
|
-
"aos-orch-brakes-",
|
|
22
|
+
"aos-orch-repo-",
|
|
23
|
+
"aos-armed-repo-",
|
|
55
24
|
"aos-orch-ext-repo-",
|
|
56
25
|
"aos-orch-external-",
|
|
57
|
-
"aos-orch-home-",
|
|
58
|
-
"aos-orch-move-",
|
|
59
|
-
"aos-orch-plain-",
|
|
60
|
-
"aos-orch-questions-",
|
|
61
|
-
"aos-orch-recipes-",
|
|
62
|
-
"aos-orch-remind-",
|
|
63
|
-
"aos-orch-repo-",
|
|
64
|
-
"aos-orch-rules-",
|
|
65
|
-
"aos-orch-settle-",
|
|
66
|
-
"aos-orch-signoff-",
|
|
67
|
-
"aos-orch-sweep-",
|
|
68
|
-
"aos-origin-",
|
|
69
|
-
"aos-path-",
|
|
70
|
-
"aos-peer-connect-",
|
|
71
|
-
"aos-peer-keys-",
|
|
72
|
-
"aos-plain-",
|
|
73
|
-
"aos-plan-",
|
|
74
|
-
"aos-ports-",
|
|
75
|
-
"aos-pr-",
|
|
76
|
-
"aos-pr-async-",
|
|
77
|
-
"aos-pr-create-",
|
|
78
|
-
"aos-pr-gen-",
|
|
79
|
-
"aos-pr-route-",
|
|
80
|
-
"aos-prompts-",
|
|
81
|
-
"aos-quick-",
|
|
82
26
|
"aos-quick-repo-",
|
|
83
|
-
"aos-
|
|
84
|
-
"
|
|
85
|
-
"aos-report-",
|
|
86
|
-
"aos-resurrect-claude-",
|
|
87
|
-
"aos-resurrect-home-",
|
|
88
|
-
"aos-run-",
|
|
89
|
-
"aos-runner-",
|
|
90
|
-
"aos-sec-",
|
|
91
|
-
"aos-shot-",
|
|
92
|
-
"aos-sim-",
|
|
93
|
-
"aos-spare-",
|
|
94
|
-
"aos-stack-",
|
|
95
|
-
"aos-start-opts-",
|
|
96
|
-
"aos-start-plain-",
|
|
97
|
-
"aos-sweep-own-",
|
|
98
|
-
"aos-t-",
|
|
99
|
-
"aos-task-diff-",
|
|
100
|
-
"aos-temp-helper-",
|
|
101
|
-
"aos-tmux-",
|
|
102
|
-
"aos-transcripts-",
|
|
103
|
-
"aos-ui-",
|
|
104
|
-
"aos-upd-",
|
|
105
|
-
"aos-usage-",
|
|
106
|
-
"aos-wg-",
|
|
107
|
-
"aos-wg-path-",
|
|
108
|
-
"aos-wl-",
|
|
109
|
-
"aos-worker-env-",
|
|
110
|
-
"aos-worker-tmux-",
|
|
111
|
-
"aos-wsl-",
|
|
112
|
-
"aos-wt-",
|
|
27
|
+
"aos-config-",
|
|
28
|
+
"agentos-link-",
|
|
113
29
|
];
|
|
114
30
|
|
|
115
31
|
export const isTestTempName = (name: string) =>
|
|
116
|
-
|
|
32
|
+
name.startsWith(RUN_PREFIX) ||
|
|
33
|
+
LEGACY_TEST_TEMP_PREFIXES.some((p) => name.startsWith(p));
|
package/package.json
CHANGED
package/scripts/agent-os
CHANGED
|
@@ -88,7 +88,7 @@ case "${1:-}" in
|
|
|
88
88
|
passkeys) shift; (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/security/passkeys-cli.ts "$@") ;;
|
|
89
89
|
logs) cmd_logs ;;
|
|
90
90
|
update) cmd_update ;;
|
|
91
|
-
machine) cmd_machine ;;
|
|
91
|
+
machine) shift; cmd_machine "$@" ;;
|
|
92
92
|
enable) cmd_enable ;;
|
|
93
93
|
disable) cmd_disable ;;
|
|
94
94
|
uninstall) cmd_uninstall ;;
|
package/scripts/autodeploy
CHANGED
|
@@ -100,6 +100,8 @@ if [ "$head" != "$deployed" ]; then
|
|
|
100
100
|
# The pull takes what landed while it waited, too.
|
|
101
101
|
git rev-parse HEAD > "$state/deployed"
|
|
102
102
|
date +%s > "$state/restarted"
|
|
103
|
+
# A link into an older clone would run an old aos on this machine.
|
|
104
|
+
scripts/sync-cli-links || log "CLI link sync failed"
|
|
103
105
|
fi
|
|
104
106
|
|
|
105
107
|
# Publishing waits minutes for npm to serve a version, so it runs on its own
|