@saadnvd1/agent-os 0.3.631 → 0.3.639
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/app/api/sessions/[id]/pr/route.ts +3 -3
- package/app/api/sessions/[id]/setup/route.ts +3 -3
- package/app/api/tasks/[id]/approve-plan/route.ts +5 -1
- package/app/api/tasks/[id]/arm/route.ts +6 -5
- package/app/api/tasks/[id]/chat-facts/route.ts +2 -2
- package/app/api/tasks/[id]/chat-tail/route.ts +2 -2
- package/app/api/tasks/[id]/checks/route.ts +1 -1
- package/app/api/tasks/[id]/diff/route.ts +1 -1
- package/app/api/tasks/[id]/done/route.ts +2 -2
- package/app/api/tasks/[id]/drop/route.ts +6 -2
- package/app/api/tasks/[id]/export/route.ts +3 -4
- package/app/api/tasks/[id]/merge/route.ts +3 -3
- package/app/api/tasks/[id]/message/route.ts +2 -2
- package/app/api/tasks/[id]/model/route.ts +5 -1
- package/app/api/tasks/[id]/move/route.ts +5 -1
- package/app/api/tasks/[id]/moved/route.ts +6 -8
- package/app/api/tasks/[id]/plan/route.ts +35 -0
- package/app/api/tasks/[id]/plan-edits/route.ts +6 -5
- package/app/api/tasks/[id]/priority/route.ts +4 -1
- package/app/api/tasks/[id]/resume/route.ts +3 -4
- package/app/api/tasks/[id]/stop/route.ts +2 -2
- package/app/api/tasks/route.ts +5 -2
- package/app/globals.css +28 -0
- package/components/Chat/ChatPanel.tsx +2 -11
- package/components/Chat/EventTurns.tsx +1 -7
- package/components/Chat/Markdown.tsx +1 -0
- package/components/Chat/Messages.tsx +1 -1
- package/lib/approvals/enable.ts +9 -2
- package/lib/away/report.ts +4 -4
- package/lib/away/run.ts +2 -2
- package/lib/bookmarks/store.ts +3 -3
- package/lib/bus/held-reply.ts +2 -2
- package/lib/bus/index.ts +3 -3
- package/lib/chat/compact-context.ts +1 -1
- package/lib/chat/events.ts +4 -0
- package/lib/chat/group.ts +3 -1
- package/lib/chat/hold.ts +3 -3
- package/lib/chat/reading-order.ts +50 -0
- package/lib/chat/refresh.ts +1 -1
- package/lib/chat/registry.ts +3 -3
- package/lib/chat/runner.ts +1 -1
- package/lib/chat/turns.ts +22 -3
- package/lib/chat/worker/host.ts +3 -0
- package/lib/chat/worker/main.ts +1 -1
- package/lib/db/index.ts +5 -1
- package/lib/db/lumifyhub.ts +8 -9
- package/lib/db/machine-scope.ts +12 -0
- package/lib/db/migrations/20261015T1600_plan_waits_for_caller.ts +19 -0
- package/lib/db/migrations/20261015T1800_split_sessions_tables.ts +172 -0
- package/lib/db/migrations/README.md +7 -0
- package/lib/db/queries.ts +16 -31
- package/lib/db/session-parts/columns.ts +186 -0
- package/lib/db/session-parts/fill.ts +33 -0
- package/lib/db/session-parts/index.ts +9 -0
- package/lib/db/session-parts/pauses.ts +11 -0
- package/lib/db/session-parts/placement.ts +11 -0
- package/lib/db/session-parts/plans.ts +31 -0
- package/lib/db/session-parts/prs.ts +31 -0
- package/lib/db/session-parts/tasks.ts +27 -0
- package/lib/db/session-parts/view.ts +43 -0
- package/lib/db/session-parts/write.ts +69 -0
- package/lib/db/types.ts +6 -1
- package/lib/done/archive.ts +2 -2
- package/lib/done/auto-archive.ts +1 -1
- package/lib/done/bulk.ts +1 -1
- package/lib/done/index.ts +8 -14
- package/lib/env-setup.ts +17 -4
- package/lib/health/evidence.ts +1 -1
- package/lib/health/facts.ts +1 -1
- package/lib/health/pieces.ts +1 -1
- package/lib/hosts/peer-actions.ts +2 -2
- package/lib/hosts/peer-mirror.ts +2 -2
- package/lib/hosts/peer-sync.ts +69 -32
- package/lib/learning-cards/input.ts +1 -1
- package/lib/learning-cards/store.ts +1 -1
- package/lib/models/plan-step.ts +83 -46
- package/lib/models/resume.ts +5 -7
- package/lib/notify/index.ts +1 -1
- package/lib/notify/push/events.ts +1 -1
- package/lib/notify/push/index.ts +1 -1
- package/lib/orchestrator/act.ts +1 -1
- package/lib/orchestrator/armed.ts +17 -15
- package/lib/orchestrator/ask-approvals.ts +1 -1
- package/lib/orchestrator/ask-heads.ts +1 -1
- package/lib/orchestrator/ask-settle.ts +1 -1
- package/lib/orchestrator/brakes.ts +1 -1
- package/lib/orchestrator/digest.ts +2 -2
- package/lib/orchestrator/external-pr.ts +2 -2
- package/lib/orchestrator/facts.ts +4 -28
- package/lib/orchestrator/held-asks.ts +3 -3
- package/lib/orchestrator/home.ts +2 -2
- package/lib/orchestrator/overview.ts +1 -1
- package/lib/orchestrator/pr-wake.ts +3 -5
- package/lib/orchestrator/questions.ts +1 -1
- package/lib/orchestrator/quick-store.ts +1 -1
- package/lib/orchestrator/quick.ts +10 -7
- package/lib/orchestrator/read.ts +1 -1
- package/lib/orchestrator/rebase.ts +8 -11
- package/lib/orchestrator/reconcile.ts +2 -2
- package/lib/orchestrator/signoff.ts +3 -1
- package/lib/orchestrator/targets.ts +1 -1
- package/lib/orchestrator/task-state.ts +4 -2
- package/lib/plans/features.ts +1 -1
- package/lib/plans/release.ts +1 -1
- package/lib/ports.ts +1 -1
- package/lib/resurrect/find.ts +1 -1
- package/lib/resurrect/index.ts +24 -18
- package/lib/schedules/store.ts +1 -1
- package/lib/session-audit/sources.ts +3 -3
- package/lib/session-logs/index.ts +1 -1
- package/lib/session-titles.ts +1 -1
- package/lib/sessions/setup-progress.ts +1 -1
- package/lib/sessions/stop.ts +1 -1
- package/lib/sessions/worktree-setup.ts +2 -1
- package/lib/simulator/index.ts +1 -1
- package/lib/simulator/task-devices.ts +1 -1
- package/lib/stacks/board.ts +1 -1
- package/lib/stacks/land.ts +1 -1
- package/lib/stacks/restack.ts +1 -1
- package/lib/stacks/start.ts +2 -2
- package/lib/stacks/tick.ts +1 -1
- package/lib/stacks/view.ts +1 -1
- package/lib/status/incremental.ts +1 -1
- package/lib/tasks/contract/actions.ts +35 -0
- package/lib/tasks/contract/authorize.ts +82 -0
- package/lib/tasks/contract/client.ts +296 -0
- package/lib/tasks/{held-remote.ts → contract/held-calls.ts} +35 -66
- package/lib/tasks/contract/index.ts +28 -0
- package/lib/tasks/contract/plan-path.ts +7 -0
- package/lib/tasks/contract/plan.ts +274 -0
- package/lib/tasks/contract/routes.ts +26 -0
- package/lib/tasks/contract/start.ts +122 -0
- package/lib/tasks/contract/sync.ts +126 -0
- package/lib/tasks/contract/types.ts +41 -0
- package/lib/tasks/{remote-views.ts → contract/views.ts} +11 -18
- package/lib/tasks/finish.ts +10 -9
- package/lib/tasks/held-replies.ts +7 -13
- package/lib/tasks/held.ts +36 -17
- package/lib/tasks/hold-checks.ts +134 -0
- package/lib/tasks/holds.ts +281 -0
- package/lib/tasks/import.ts +26 -14
- package/lib/tasks/index.ts +56 -22
- package/lib/tasks/launch-gate.ts +1 -1
- package/lib/tasks/move-bundle.ts +17 -2
- package/lib/tasks/move-carry.ts +15 -7
- package/lib/tasks/move-flow.ts +29 -36
- package/lib/tasks/move-guard.ts +10 -2
- package/lib/tasks/move-recover.ts +6 -25
- package/lib/tasks/move.ts +28 -27
- package/lib/tasks/moved-away.ts +1 -1
- package/lib/tasks/pause-hold.ts +13 -5
- package/lib/tasks/pause.ts +21 -54
- package/lib/tasks/placement.ts +16 -16
- package/lib/tasks/plan-view.ts +11 -3
- package/lib/tasks/queue-candidates.ts +151 -0
- package/lib/tasks/queue-placement.ts +155 -0
- package/lib/tasks/queue-rows.ts +236 -0
- package/lib/tasks/queue-start.ts +213 -0
- package/lib/tasks/queue-tick.ts +148 -0
- package/lib/tasks/queue-types.ts +141 -0
- package/lib/tasks/queue-view.ts +112 -0
- package/lib/tasks/queue.ts +67 -1102
- package/lib/tasks/schedule.ts +44 -147
- package/lib/tasks/session.ts +7 -4
- package/lib/tasks/start.ts +4 -4
- package/lib/tasks/starter.ts +8 -11
- package/lib/usage/calls.ts +1 -1
- package/lib/usage/compare.ts +1 -1
- package/lib/usage/quality.ts +1 -1
- package/lib/usage/turns.ts +1 -1
- package/lib/voice/vocab.ts +1 -1
- package/package.json +1 -1
- package/scripts/backfill-session-logs.ts +1 -1
- package/scripts/check +55 -7
- package/lib/tasks/auto-hold.ts +0 -17
- package/lib/tasks/held-routes.ts +0 -33
- package/lib/tasks/remote.ts +0 -238
package/lib/tasks/schedule.ts
CHANGED
|
@@ -13,59 +13,33 @@ import {
|
|
|
13
13
|
PRIORITY_LABEL,
|
|
14
14
|
type Priority,
|
|
15
15
|
type QueueExplainView,
|
|
16
|
-
type WaitCode,
|
|
17
16
|
} from "./schedule-types";
|
|
17
|
+
import {
|
|
18
|
+
PRIORITY_POINTS,
|
|
19
|
+
holdsFor,
|
|
20
|
+
type Hold,
|
|
21
|
+
type MachineView,
|
|
22
|
+
} from "./holds";
|
|
18
23
|
|
|
19
24
|
export { PRIORITIES, DEFAULT_PRIORITY } from "./schedule-types";
|
|
20
25
|
export type { Priority, WaitCode } from "./schedule-types";
|
|
26
|
+
export {
|
|
27
|
+
AMBER_MIN_PRIORITY,
|
|
28
|
+
HEAVY_START_GAP_MS,
|
|
29
|
+
LOAD_SETTLE_MS,
|
|
30
|
+
PRIORITY_POINTS,
|
|
31
|
+
START_GAP_MS,
|
|
32
|
+
machineHold,
|
|
33
|
+
slotText,
|
|
34
|
+
type Hold,
|
|
35
|
+
type MachineLevel,
|
|
36
|
+
type MachineView,
|
|
37
|
+
} from "./holds";
|
|
21
38
|
|
|
22
|
-
export const PRIORITY_POINTS: Record<Priority, number> = {
|
|
23
|
-
urgent: 400,
|
|
24
|
-
blocking: 300,
|
|
25
|
-
normal: 200,
|
|
26
|
-
background: 100,
|
|
27
|
-
};
|
|
28
39
|
export const AGE_POINTS_PER_MIN = 0.5;
|
|
29
40
|
// Reached after five hours of waiting.
|
|
30
41
|
export const AGE_CAP = 150;
|
|
31
42
|
export const RESUME_BONUS = 150;
|
|
32
|
-
// Out of red this long before automatic starts resume.
|
|
33
|
-
export const LOAD_SETTLE_MS = 120_000;
|
|
34
|
-
// On amber, only this priority and above start.
|
|
35
|
-
export const AMBER_MIN_PRIORITY: Priority = "blocking";
|
|
36
|
-
// Between automatic starts on one machine.
|
|
37
|
-
export const START_GAP_MS = 60_000;
|
|
38
|
-
// Between starts of tasks that boot a simulator.
|
|
39
|
-
export const HEAVY_START_GAP_MS = 5 * 60_000;
|
|
40
|
-
|
|
41
|
-
export type MachineLevel = "green" | "amber" | "red";
|
|
42
|
-
|
|
43
|
-
// What the machine a candidate would start on looks like right now.
|
|
44
|
-
export interface MachineView {
|
|
45
|
-
// Shown in hold texts, e.g. "this machine".
|
|
46
|
-
label: string;
|
|
47
|
-
// False: no load holds at all (AGENTOS_LOAD=off).
|
|
48
|
-
enabled: boolean;
|
|
49
|
-
// No sample read yet since AgentOS started.
|
|
50
|
-
reading: boolean;
|
|
51
|
-
level: MachineLevel;
|
|
52
|
-
// Not red, and calm for LOAD_SETTLE_MS since it last was.
|
|
53
|
-
settled: boolean;
|
|
54
|
-
// Seconds of calm still needed, when not settled and not red.
|
|
55
|
-
settleLeftMs: number;
|
|
56
|
-
booted: number;
|
|
57
|
-
maxBooted: number;
|
|
58
|
-
// Heavy tasks started within HEAVY_START_GAP_MS that hold no lease yet.
|
|
59
|
-
heavyStarting: number;
|
|
60
|
-
lastAutoStartAt: number | null;
|
|
61
|
-
lastHeavyStartAt: number | null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface Hold {
|
|
65
|
-
code: WaitCode;
|
|
66
|
-
text: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
43
|
export interface Candidate {
|
|
70
44
|
id: string;
|
|
71
45
|
kind: "start" | "resume";
|
|
@@ -88,7 +62,7 @@ export interface Candidate {
|
|
|
88
62
|
// `after X` hold, from the caller (it needs the database).
|
|
89
63
|
before: Hold | null;
|
|
90
64
|
// Orchestrator Pause and the usage window, which hold after `after`.
|
|
91
|
-
auto: Hold | null;
|
|
65
|
+
auto: Hold | readonly Hold[] | null;
|
|
92
66
|
// "After any": its set, the ones that have finished, the ones other waiters
|
|
93
67
|
// already used, and whether it was already released (a retry).
|
|
94
68
|
any: {
|
|
@@ -110,6 +84,8 @@ export interface Plan {
|
|
|
110
84
|
// The finish an "after any" waiter takes, by candidate id (null: none left).
|
|
111
85
|
consume: Map<string, string | null>;
|
|
112
86
|
explain: Map<string, QueueExplainView>;
|
|
87
|
+
// Every hold on each candidate, in order (empty: it starts).
|
|
88
|
+
holds: Map<string, Hold[]>;
|
|
113
89
|
}
|
|
114
90
|
|
|
115
91
|
export function scoreParts(
|
|
@@ -124,75 +100,6 @@ export function scoreParts(
|
|
|
124
100
|
return { priority, age, resume, score: priority + age + resume };
|
|
125
101
|
}
|
|
126
102
|
|
|
127
|
-
const priorityRank = (p: Priority) => PRIORITY_POINTS[p] ?? 0;
|
|
128
|
-
|
|
129
|
-
const seconds = (ms: number) => Math.max(1, Math.ceil(ms / 1000));
|
|
130
|
-
|
|
131
|
-
// The machine holds, in order. Null: it may start here now.
|
|
132
|
-
export function machineHold(
|
|
133
|
-
c: Pick<Candidate, "heavy" | "needsUnmet" | "remote" | "priority"> &
|
|
134
|
-
Partial<Pick<Candidate, "planOnly">>,
|
|
135
|
-
m: MachineView,
|
|
136
|
-
now: number
|
|
137
|
-
): Hold | null {
|
|
138
|
-
if (c.remote || c.planOnly) return null;
|
|
139
|
-
if (c.needsUnmet) return { code: "needs", text: `Waiting: needs a Mac` };
|
|
140
|
-
if (!m.enabled) return null;
|
|
141
|
-
if (m.reading)
|
|
142
|
-
return { code: "load_red", text: "Waiting: reading machine load" };
|
|
143
|
-
if (m.level === "red")
|
|
144
|
-
return { code: "load_red", text: `Waiting: ${m.label} load red` };
|
|
145
|
-
if (!m.settled)
|
|
146
|
-
return {
|
|
147
|
-
code: "load_red",
|
|
148
|
-
text: `Waiting: ${m.label} load was red; calm for ${seconds(m.settleLeftMs)}s more`,
|
|
149
|
-
};
|
|
150
|
-
if (
|
|
151
|
-
m.level === "amber" &&
|
|
152
|
-
priorityRank(c.priority) < priorityRank(AMBER_MIN_PRIORITY)
|
|
153
|
-
)
|
|
154
|
-
return {
|
|
155
|
-
code: "load_amber",
|
|
156
|
-
text: `Waiting: ${m.label} load amber; ${c.priority} work starts on green`,
|
|
157
|
-
};
|
|
158
|
-
if (c.heavy) {
|
|
159
|
-
if (m.level !== "green")
|
|
160
|
-
return {
|
|
161
|
-
code: "heavy",
|
|
162
|
-
text: `Waiting: ${m.label} load amber; simulator work starts on green`,
|
|
163
|
-
};
|
|
164
|
-
if (m.heavyStarting > 0)
|
|
165
|
-
return {
|
|
166
|
-
code: "heavy",
|
|
167
|
-
text: `Waiting: a simulator is booting on ${m.label}`,
|
|
168
|
-
};
|
|
169
|
-
if (m.booted >= m.maxBooted)
|
|
170
|
-
return {
|
|
171
|
-
code: "heavy",
|
|
172
|
-
text: `Waiting: ${m.booted} of ${m.maxBooted} simulators in use`,
|
|
173
|
-
};
|
|
174
|
-
if (
|
|
175
|
-
m.lastHeavyStartAt !== null &&
|
|
176
|
-
now - m.lastHeavyStartAt < HEAVY_START_GAP_MS
|
|
177
|
-
)
|
|
178
|
-
return {
|
|
179
|
-
code: "heavy",
|
|
180
|
-
text: `Waiting: a simulator task just started on ${m.label} (next in ${seconds(HEAVY_START_GAP_MS - (now - m.lastHeavyStartAt))}s)`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (m.lastAutoStartAt !== null && now - m.lastAutoStartAt < START_GAP_MS)
|
|
184
|
-
return {
|
|
185
|
-
code: "spacing",
|
|
186
|
-
text: `Waiting: spacing starts (next in ${seconds(START_GAP_MS - (now - m.lastAutoStartAt))}s)`,
|
|
187
|
-
};
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function slotText(rank: number, of: number, priority: Priority): string {
|
|
192
|
-
const word = priority === DEFAULT_PRIORITY ? "" : ` · ${priority}`;
|
|
193
|
-
return `#${rank} of ${of}${word}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
103
|
export function priorityWord(priority: Priority): string {
|
|
197
104
|
return PRIORITY_LABEL[priority];
|
|
198
105
|
}
|
|
@@ -204,7 +111,12 @@ export function planStarts(
|
|
|
204
111
|
machine: MachineView,
|
|
205
112
|
now: number
|
|
206
113
|
): Plan {
|
|
207
|
-
const plan: Plan = {
|
|
114
|
+
const plan: Plan = {
|
|
115
|
+
start: [],
|
|
116
|
+
consume: new Map(),
|
|
117
|
+
explain: new Map(),
|
|
118
|
+
holds: new Map(),
|
|
119
|
+
};
|
|
208
120
|
const m: MachineView = { ...machine };
|
|
209
121
|
const byGroup = new Map<string, Candidate[]>();
|
|
210
122
|
for (const c of candidates)
|
|
@@ -218,26 +130,27 @@ export function planStarts(
|
|
|
218
130
|
const scored = list
|
|
219
131
|
.map((c) => ({ c, ...scoreParts(c, now) }))
|
|
220
132
|
.sort((a, b) => b.score - a.score || a.c.position - b.c.position);
|
|
221
|
-
scored.forEach(({ c, score
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
133
|
+
scored.forEach(({ c, score }, index) => {
|
|
134
|
+
const holds = holdsFor(c, {
|
|
135
|
+
machine: m,
|
|
136
|
+
now,
|
|
137
|
+
used,
|
|
138
|
+
inFlight,
|
|
139
|
+
limit: group.limit,
|
|
140
|
+
rank: index + 1,
|
|
141
|
+
of: scored.length,
|
|
142
|
+
});
|
|
143
|
+
const hold = holds[0] ?? null;
|
|
144
|
+
plan.holds.set(c.id, holds);
|
|
145
|
+
plan.explain.set(c.id, {
|
|
146
|
+
code: hold?.code ?? null,
|
|
147
|
+
text: hold?.text ?? null,
|
|
227
148
|
score: Math.round(score * 10) / 10,
|
|
228
|
-
rank,
|
|
149
|
+
rank: index + 1,
|
|
229
150
|
of: scored.length,
|
|
230
151
|
priority: c.priority,
|
|
231
152
|
});
|
|
232
|
-
if (hold) return
|
|
233
|
-
if (!c.planOnly && inFlight >= group.limit)
|
|
234
|
-
return void plan.explain.set(
|
|
235
|
-
c.id,
|
|
236
|
-
view({
|
|
237
|
-
code: "slot",
|
|
238
|
-
text: slotText(rank, scored.length, c.priority),
|
|
239
|
-
})
|
|
240
|
-
);
|
|
153
|
+
if (hold) return;
|
|
241
154
|
// Passed every hold: it starts.
|
|
242
155
|
if (c.any && !c.any.released) {
|
|
243
156
|
const free = c.any.finished.find((id) => !used.has(id)) ?? null;
|
|
@@ -253,23 +166,7 @@ export function planStarts(
|
|
|
253
166
|
}
|
|
254
167
|
}
|
|
255
168
|
plan.start.push(c.id);
|
|
256
|
-
plan.explain.set(c.id, view(null));
|
|
257
169
|
});
|
|
258
170
|
}
|
|
259
171
|
return plan;
|
|
260
|
-
|
|
261
|
-
function holdOf(c: Candidate): Hold | null {
|
|
262
|
-
if (c.before) return c.before;
|
|
263
|
-
if (c.any && !c.any.released) {
|
|
264
|
-
const free = c.any.finished.some((id) => !used.has(id));
|
|
265
|
-
const spent =
|
|
266
|
-
c.any.ids.length > 0 && c.any.finished.length === c.any.ids.length;
|
|
267
|
-
if (!free && !spent)
|
|
268
|
-
return {
|
|
269
|
-
code: "after_any",
|
|
270
|
-
text: `Waiting: after any running task (${c.any.label})`,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return c.auto ?? machineHold(c, m, now);
|
|
274
|
-
}
|
|
275
172
|
}
|
package/lib/tasks/session.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { notifyStatusChanged } from "../status/hub";
|
|
27
27
|
import type { TaskPR } from "./state";
|
|
28
28
|
import { markStatusDirty } from "../status/dirty";
|
|
29
|
+
import { setPr } from "../db/session-parts";
|
|
29
30
|
|
|
30
31
|
export const expandHome = (p: string) => p.replace(/^~/, os.homedir());
|
|
31
32
|
|
|
@@ -33,7 +34,7 @@ export const expandHome = (p: string) => p.replace(/^~/, os.homedir());
|
|
|
33
34
|
export function taskSessions(includeArchived = true): Session[] {
|
|
34
35
|
return db
|
|
35
36
|
.prepare(
|
|
36
|
-
`SELECT ${sessionListColumns(db)} FROM
|
|
37
|
+
`SELECT ${sessionListColumns(db)} FROM session_rows
|
|
37
38
|
WHERE task_status IS NOT NULL AND task_status != 'moved'
|
|
38
39
|
${includeArchived ? "" : "AND archived_at IS NULL"}
|
|
39
40
|
ORDER BY created_at DESC`
|
|
@@ -205,9 +206,11 @@ export async function prFor(
|
|
|
205
206
|
if (pr) pushPrChange(session, pr);
|
|
206
207
|
}
|
|
207
208
|
if (pr) {
|
|
208
|
-
db.
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
setPr(db, session.id, {
|
|
210
|
+
pr_url: pr.url,
|
|
211
|
+
pr_number: pr.number,
|
|
212
|
+
pr_status: pr.state.toLowerCase() as "open" | "merged" | "closed",
|
|
213
|
+
});
|
|
211
214
|
// Merged on GitHub rather than through AgentOS: its log is due too.
|
|
212
215
|
if (pr.state === "MERGED") {
|
|
213
216
|
if (session.pr_status !== "merged") pushMerged(session.id);
|
package/lib/tasks/start.ts
CHANGED
|
@@ -107,7 +107,7 @@ export async function finishTaskStart(
|
|
|
107
107
|
earlier?: TaskSetup
|
|
108
108
|
): Promise<TaskSetup> {
|
|
109
109
|
const session = db
|
|
110
|
-
.prepare(`SELECT * FROM
|
|
110
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
111
111
|
.get(sessionId) as Session | undefined;
|
|
112
112
|
if (!session?.worktree_path) throw new Error(`No task ${sessionId}`);
|
|
113
113
|
const project = session.project_id ? getProject(session.project_id) : null;
|
|
@@ -133,7 +133,7 @@ export async function finishTaskStart(
|
|
|
133
133
|
// The setup's own error is kept, for the agent's prompt when it launches.
|
|
134
134
|
if (held) return done({ ...setup, status: "held" });
|
|
135
135
|
const now = db
|
|
136
|
-
.prepare(`SELECT task_status, archived_at FROM
|
|
136
|
+
.prepare(`SELECT task_status, archived_at FROM session_rows WHERE id = ?`)
|
|
137
137
|
.get(sessionId) as
|
|
138
138
|
| { task_status: string | null; archived_at: string | null }
|
|
139
139
|
| undefined;
|
|
@@ -226,7 +226,7 @@ export async function resumeTaskStarts(
|
|
|
226
226
|
): Promise<string[]> {
|
|
227
227
|
const rows = db
|
|
228
228
|
.prepare(
|
|
229
|
-
`SELECT id, tmux_name, view, setup_ms, setup_error FROM
|
|
229
|
+
`SELECT id, tmux_name, view, setup_ms, setup_error FROM session_rows
|
|
230
230
|
WHERE setup_status = 'running' AND task_status = 'running'
|
|
231
231
|
AND archived_at IS NULL`
|
|
232
232
|
)
|
|
@@ -269,7 +269,7 @@ export async function resumeTaskStarts(
|
|
|
269
269
|
export function resumeHeldStarts(): string[] {
|
|
270
270
|
const rows = db
|
|
271
271
|
.prepare(
|
|
272
|
-
`SELECT id, setup_ms, setup_error FROM
|
|
272
|
+
`SELECT id, setup_ms, setup_error FROM session_rows WHERE setup_status = 'held'
|
|
273
273
|
AND task_status = 'running' AND archived_at IS NULL`
|
|
274
274
|
)
|
|
275
275
|
.all() as ({ id: string } & SetupColumns)[];
|
package/lib/tasks/starter.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { db } from "../db";
|
|
7
|
+
import { setPlacement } from "../db/session-parts";
|
|
7
8
|
|
|
8
9
|
export type StartVia =
|
|
9
10
|
| "session"
|
|
@@ -59,7 +60,7 @@ export function callerSession(input: {
|
|
|
59
60
|
const cwd = path.resolve(input.cwd);
|
|
60
61
|
const rows = db
|
|
61
62
|
.prepare(
|
|
62
|
-
`SELECT id, role, worktree_path FROM
|
|
63
|
+
`SELECT id, role, worktree_path FROM session_rows
|
|
63
64
|
WHERE worktree_path IS NOT NULL AND archived_at IS NULL
|
|
64
65
|
AND task_status IS NOT NULL
|
|
65
66
|
AND (host_id IS NULL OR host_id = 'local')`
|
|
@@ -111,14 +112,10 @@ export function orchestratorOf(startedBy: string | null | undefined) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
export function recordStart(id: string, s: Starter): void {
|
|
114
|
-
db.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
s.
|
|
119
|
-
|
|
120
|
-
s.startedVia,
|
|
121
|
-
s.startedByDevice ?? null,
|
|
122
|
-
id
|
|
123
|
-
);
|
|
115
|
+
db.transaction(() => {
|
|
116
|
+
db.prepare(
|
|
117
|
+
`UPDATE sessions SET started_by = ?, parent_session_id = (SELECT id FROM sessions WHERE id = ?), started_via = ? WHERE id = ?`
|
|
118
|
+
).run(s.startedBy, s.parentSessionId, s.startedVia, id);
|
|
119
|
+
setPlacement(db, id, { started_by_device: s.startedByDevice ?? undefined });
|
|
120
|
+
})();
|
|
124
121
|
}
|
package/lib/usage/calls.ts
CHANGED
|
@@ -97,7 +97,7 @@ export function sessionContext(sessionId: string): SessionContext {
|
|
|
97
97
|
const s = db
|
|
98
98
|
.prepare(
|
|
99
99
|
`SELECT name, workspace_id, project_id, model_provider, role, task_status,
|
|
100
|
-
plan_phase, started_by FROM
|
|
100
|
+
plan_phase, started_by FROM session_rows WHERE id = ?`
|
|
101
101
|
)
|
|
102
102
|
.get(sessionId) as
|
|
103
103
|
| {
|
package/lib/usage/compare.ts
CHANGED
|
@@ -225,7 +225,7 @@ export function taskOutcomes(ids: string[]): Map<string, TaskOutcome> {
|
|
|
225
225
|
const marks = ids.map(() => "?").join(",");
|
|
226
226
|
const tasks = db
|
|
227
227
|
.prepare(
|
|
228
|
-
`SELECT id, task_status, plan_decided FROM
|
|
228
|
+
`SELECT id, task_status, plan_decided FROM session_rows
|
|
229
229
|
WHERE id IN (${marks}) AND task_status IS NOT NULL`
|
|
230
230
|
)
|
|
231
231
|
.all(...ids) as {
|
package/lib/usage/quality.ts
CHANGED
|
@@ -82,7 +82,7 @@ export function mergedTasks(from: number, to: number): MergedTask[] {
|
|
|
82
82
|
db
|
|
83
83
|
.prepare(
|
|
84
84
|
`SELECT id, project_id AS projectId, pr_number AS prNumber, merged_at AS mergedAt
|
|
85
|
-
FROM
|
|
85
|
+
FROM session_rows WHERE task_status = 'merged' AND merged_at >= ? AND merged_at < ?`
|
|
86
86
|
)
|
|
87
87
|
.all(sqlTime(from), sqlTime(to)) as MergedTask[]
|
|
88
88
|
).filter((t) => t.mergedAt);
|
package/lib/usage/turns.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function recordTurn(
|
|
|
61
61
|
const current = db
|
|
62
62
|
.prepare(
|
|
63
63
|
`SELECT name, workspace_id, model, model_provider, role, task_status,
|
|
64
|
-
plan_phase, started_by FROM
|
|
64
|
+
plan_phase, started_by FROM session_rows WHERE id = ?`
|
|
65
65
|
)
|
|
66
66
|
.get(session.id) as
|
|
67
67
|
| Pick<
|
package/lib/voice/vocab.ts
CHANGED
|
@@ -44,7 +44,7 @@ export function vocabFor(workspaceId: string): string[] {
|
|
|
44
44
|
workspaceId
|
|
45
45
|
);
|
|
46
46
|
const sessions = names(
|
|
47
|
-
`SELECT s.name FROM
|
|
47
|
+
`SELECT s.name FROM session_rows s
|
|
48
48
|
JOIN projects p ON p.id = s.project_id
|
|
49
49
|
WHERE p.workspace_id = ?
|
|
50
50
|
AND (s.task_status IS NULL OR s.task_status NOT IN ('merged','dropped','done','moved'))
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@ async function main() {
|
|
|
47
47
|
const { commitLogs } = await import("../lib/session-logs/hq");
|
|
48
48
|
const rows = db
|
|
49
49
|
.prepare(
|
|
50
|
-
`SELECT id, name FROM
|
|
50
|
+
`SELECT id, name FROM session_rows
|
|
51
51
|
WHERE task_prompt IS NOT NULL AND peer_mirror = 0
|
|
52
52
|
AND (task_status = 'merged' OR (pr_status = 'merged' AND task_status != 'dropped'))
|
|
53
53
|
ORDER BY COALESCE(merged_at, updated_at)`
|
package/scripts/check
CHANGED
|
@@ -2,18 +2,66 @@
|
|
|
2
2
|
# The full guard, the same run locally and in CI:
|
|
3
3
|
# scripts/check banned terms, typecheck, lint, format, tests
|
|
4
4
|
# scripts/check --build ...and a production build, held to its JS budget
|
|
5
|
+
# CI runs the same groups as parallel jobs:
|
|
6
|
+
# --only static,test,build run just these groups (static = migrations,
|
|
7
|
+
# terms, typecheck, lint, format)
|
|
8
|
+
# --parallel run typecheck, lint and format at once
|
|
9
|
+
# -- <args> passed to `vitest run` (e.g. --shard=1/3)
|
|
5
10
|
set -euo pipefail
|
|
6
11
|
cd "$(dirname "$0")/.."
|
|
7
12
|
|
|
13
|
+
usage() { sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2; }
|
|
14
|
+
|
|
15
|
+
groups="static,test" parallel=0 vitest_args=()
|
|
16
|
+
while [ $# -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--build) groups="$groups,build" ;;
|
|
19
|
+
--only) [ $# -ge 2 ] || usage; groups="$2"; shift ;;
|
|
20
|
+
--parallel) parallel=1 ;;
|
|
21
|
+
--) shift; vitest_args=("$@"); break ;;
|
|
22
|
+
*) usage ;;
|
|
23
|
+
esac
|
|
24
|
+
shift
|
|
25
|
+
done
|
|
26
|
+
for g in ${groups//,/ }; do
|
|
27
|
+
case "$g" in static|test|build) ;; *) usage ;; esac
|
|
28
|
+
done
|
|
29
|
+
has() { [[ ",$groups," == *",$1,"* ]]; }
|
|
30
|
+
|
|
8
31
|
step() { printf '\n▸ %s\n' "$1"; }
|
|
9
32
|
|
|
10
|
-
|
|
11
|
-
step "
|
|
12
|
-
step "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
if has static; then
|
|
34
|
+
step "migrations"; node scripts/gen-migrations-index.mjs && node scripts/gen-migrations-index.mjs --check
|
|
35
|
+
step "terms"; npx tsx scripts/hooks/banned-terms.ts --tree
|
|
36
|
+
if [ "$parallel" = 1 ]; then
|
|
37
|
+
out=$(mktemp -d); trap 'rm -rf "$out"' EXIT
|
|
38
|
+
pids=()
|
|
39
|
+
npx tsc --noEmit >"$out/typecheck" 2>&1 & pids+=($!)
|
|
40
|
+
npx eslint . --max-warnings=40 --concurrency=auto >"$out/lint" 2>&1 & pids+=($!)
|
|
41
|
+
npx prettier --check . --log-level warn >"$out/format" 2>&1 & pids+=($!)
|
|
42
|
+
failed=()
|
|
43
|
+
names=(typecheck lint format)
|
|
44
|
+
for i in 0 1 2; do
|
|
45
|
+
wait "${pids[$i]}" || failed+=("${names[$i]}")
|
|
46
|
+
step "${names[$i]}"; cat "$out/${names[$i]}"
|
|
47
|
+
done
|
|
48
|
+
if [ ${#failed[@]} -gt 0 ]; then
|
|
49
|
+
printf '\n✗ failed: %s\n' "${failed[*]}" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
else
|
|
53
|
+
step "typecheck"; npx tsc --noEmit
|
|
54
|
+
step "lint"; npx eslint . --max-warnings=40 --concurrency=auto
|
|
55
|
+
step "format"; npx prettier --check . --log-level warn
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
if has test; then
|
|
59
|
+
step "test"; npx vitest run ${vitest_args[@]+"${vitest_args[@]}"}
|
|
60
|
+
fi
|
|
61
|
+
if has build; then
|
|
62
|
+
# The build imports the generated migrations index, so it needs it even
|
|
63
|
+
# when the static group (which checks it) did not run.
|
|
64
|
+
step "migrations"; node scripts/gen-migrations-index.mjs
|
|
17
65
|
step "build"; npx next build
|
|
18
66
|
step "bundle"; node scripts/check-bundle.mjs
|
|
19
67
|
fi
|
package/lib/tasks/auto-hold.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { brakesEnabled } from "../orchestrator/brakes";
|
|
2
|
-
import { isPaused } from "../orchestrator/pause";
|
|
3
|
-
import { readUsage, windowRefusal } from "../orchestrator/usage";
|
|
4
|
-
import type { Hold } from "./schedule";
|
|
5
|
-
|
|
6
|
-
// Why an automatic start has to wait, or null. A start now by a person
|
|
7
|
-
// isn't held.
|
|
8
|
-
export function autoHold(
|
|
9
|
-
workspaceId: string | null,
|
|
10
|
-
planOnly = false
|
|
11
|
-
): Hold | null {
|
|
12
|
-
if (workspaceId && isPaused(workspaceId))
|
|
13
|
-
return { code: "orch_paused", text: "Waiting: the orchestrator is paused" };
|
|
14
|
-
if (planOnly) return null;
|
|
15
|
-
const window = brakesEnabled() ? windowRefusal(readUsage()) : null;
|
|
16
|
-
return window ? { code: "window", text: `Waiting: ${window}` } : null;
|
|
17
|
-
}
|
package/lib/tasks/held-routes.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The routes only the device that moved a task here may call on it
|
|
3
|
-
* (held.ts): message, stop, done and read. Each is refused for any other
|
|
4
|
-
* device, and for a task that isn't held.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
8
|
-
import { db, type Session } from "../db";
|
|
9
|
-
import { callingDevice } from "../security/device-me";
|
|
10
|
-
import { holderRefusal } from "./held";
|
|
11
|
-
|
|
12
|
-
type Ctx = { params: Promise<{ id: string }> };
|
|
13
|
-
|
|
14
|
-
export async function heldRoute(
|
|
15
|
-
request: NextRequest,
|
|
16
|
-
{ params }: Ctx,
|
|
17
|
-
run: (task: Session) => Promise<object>
|
|
18
|
-
): Promise<NextResponse> {
|
|
19
|
-
const id = (await params).id;
|
|
20
|
-
const task = db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id) as
|
|
21
|
-
| Session
|
|
22
|
-
| undefined;
|
|
23
|
-
if (!task?.held_by_device)
|
|
24
|
-
return NextResponse.json({ error: "No such held task" }, { status: 404 });
|
|
25
|
-
const refusal = holderRefusal(task, callingDevice(request));
|
|
26
|
-
if (refusal) return NextResponse.json({ error: refusal }, { status: 403 });
|
|
27
|
-
try {
|
|
28
|
-
return NextResponse.json(await run(task));
|
|
29
|
-
} catch (error) {
|
|
30
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
-
return NextResponse.json({ error: message }, { status: 409 });
|
|
32
|
-
}
|
|
33
|
-
}
|