@saadnvd1/agent-os 0.3.637 → 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 +3 -4
- 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 -5
- 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 +4 -6
- 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 +1 -1
- package/lib/approvals/enable.ts +1 -1
- 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/hold.ts +3 -3
- package/lib/chat/refresh.ts +1 -1
- package/lib/chat/registry.ts +3 -3
- package/lib/chat/runner.ts +1 -1
- 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/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/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/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 +10 -24
- 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/{remote-plan-path.ts → contract/plan-path.ts} +2 -2
- package/lib/tasks/{remote-plan.ts → contract/plan.ts} +76 -110
- 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 +14 -18
- package/lib/tasks/import.ts +19 -15
- package/lib/tasks/index.ts +34 -22
- package/lib/tasks/launch-gate.ts +1 -1
- package/lib/tasks/move-carry.ts +15 -7
- package/lib/tasks/move-flow.ts +29 -36
- package/lib/tasks/move-guard.ts +1 -1
- package/lib/tasks/move-recover.ts +6 -25
- package/lib/tasks/move.ts +23 -23
- package/lib/tasks/moved-away.ts +1 -1
- package/lib/tasks/pause-hold.ts +13 -5
- package/lib/tasks/pause.ts +19 -23
- package/lib/tasks/plan-view.ts +1 -1
- package/lib/tasks/queue-placement.ts +1 -1
- package/lib/tasks/queue-rows.ts +4 -4
- package/lib/tasks/queue-start.ts +5 -3
- package/lib/tasks/queue-tick.ts +2 -2
- package/lib/tasks/queue.ts +14 -6
- 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/lib/tasks/held-routes.ts +0 -33
- package/lib/tasks/remote.ts +0 -255
package/lib/notify/push/index.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface PushOptions {
|
|
|
57
57
|
|
|
58
58
|
function isMirrored(sessionId: string): boolean {
|
|
59
59
|
const s = db
|
|
60
|
-
.prepare(`SELECT host_id, peer_mirror FROM
|
|
60
|
+
.prepare(`SELECT host_id, peer_mirror FROM session_rows WHERE id = ?`)
|
|
61
61
|
.get(sessionId) as
|
|
62
62
|
| { host_id: string | null; peer_mirror: number | null }
|
|
63
63
|
| undefined;
|
package/lib/orchestrator/act.ts
CHANGED
|
@@ -439,7 +439,7 @@ export async function resurrect(
|
|
|
439
439
|
if (!reason.trim()) throw new Error("Say why it's restored");
|
|
440
440
|
const id = resolveEnded(ref, { workspaceId });
|
|
441
441
|
const row = db
|
|
442
|
-
.prepare(`SELECT task_status, archived_at FROM
|
|
442
|
+
.prepare(`SELECT task_status, archived_at FROM session_rows WHERE id = ?`)
|
|
443
443
|
.get(id) as { task_status: string | null; archived_at: string | null };
|
|
444
444
|
const task =
|
|
445
445
|
row.task_status === "dropped" ||
|
|
@@ -28,6 +28,7 @@ import { review } from "./review";
|
|
|
28
28
|
import { judge, mergeJudged } from "./signoff";
|
|
29
29
|
import { workspaceTask } from "./targets";
|
|
30
30
|
import { markStatusDirty } from "../status/dirty";
|
|
31
|
+
import { setPr } from "../db/session-parts";
|
|
31
32
|
|
|
32
33
|
// Judged again at least this often, for what the fingerprint can't see.
|
|
33
34
|
export const ARMED_RECHECK_MS = 5 * 60 * 1000;
|
|
@@ -35,10 +36,18 @@ export const ARMED_RECHECK_MS = 5 * 60 * 1000;
|
|
|
35
36
|
const short = (sha: string) => sha.slice(0, 7);
|
|
36
37
|
|
|
37
38
|
export function setArmed(sessionId: string, armed: boolean): void {
|
|
38
|
-
db.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
db.transaction(() => {
|
|
40
|
+
const row = db
|
|
41
|
+
.prepare(`SELECT merge_armed_at FROM session_rows WHERE id = ?`)
|
|
42
|
+
.get(sessionId) as { merge_armed_at: string | null } | undefined;
|
|
43
|
+
setPr(db, sessionId, {
|
|
44
|
+
merge_armed_at: !armed
|
|
45
|
+
? null
|
|
46
|
+
: (row?.merge_armed_at ??
|
|
47
|
+
new Date().toISOString().slice(0, 19).replace("T", " ")),
|
|
48
|
+
merge_armed_seen: null,
|
|
49
|
+
});
|
|
50
|
+
})();
|
|
42
51
|
looked.delete(sessionId);
|
|
43
52
|
markStatusDirty(sessionId);
|
|
44
53
|
notifyStatusChanged();
|
|
@@ -57,7 +66,7 @@ export function armingWorkspace(task: Session): string | null {
|
|
|
57
66
|
function armedTasks(workspaceId: string): Session[] {
|
|
58
67
|
return db
|
|
59
68
|
.prepare(
|
|
60
|
-
`SELECT s.* FROM
|
|
69
|
+
`SELECT s.* FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
61
70
|
WHERE p.workspace_id = ? AND s.merge_armed_at IS NOT NULL
|
|
62
71
|
AND s.task_status = 'running' AND s.peer_mirror = 0
|
|
63
72
|
AND s.held_by_device IS NULL`
|
|
@@ -70,7 +79,7 @@ function armedTasks(workspaceId: string): Session[] {
|
|
|
70
79
|
function stillArmed(workspaceId: string, taskId: string): boolean {
|
|
71
80
|
if (isPaused(workspaceId)) return false;
|
|
72
81
|
const row = db
|
|
73
|
-
.prepare(`SELECT merge_armed_at FROM
|
|
82
|
+
.prepare(`SELECT merge_armed_at FROM session_rows WHERE id = ?`)
|
|
74
83
|
.get(taskId) as { merge_armed_at: string | null } | undefined;
|
|
75
84
|
return !!row?.merge_armed_at;
|
|
76
85
|
}
|
|
@@ -255,11 +264,7 @@ async function mergeOne(
|
|
|
255
264
|
if (counted.ok) return merge(workspaceId, task, counted, deps);
|
|
256
265
|
const toSaad = holdingEscalations(task.id).length > 0;
|
|
257
266
|
if (toSaad) setArmed(task.id, false);
|
|
258
|
-
else
|
|
259
|
-
db.prepare(`UPDATE sessions SET merge_armed_seen = ? WHERE id = ?`).run(
|
|
260
|
-
seen,
|
|
261
|
-
task.id
|
|
262
|
-
);
|
|
267
|
+
else setPr(db, task.id, { merge_armed_seen: seen });
|
|
263
268
|
event(
|
|
264
269
|
workspaceId,
|
|
265
270
|
task,
|
|
@@ -283,10 +288,7 @@ async function merge(
|
|
|
283
288
|
const why = error instanceof Error ? error.message : String(error);
|
|
284
289
|
const seen = `${verdict.sha}:merge:${why}`;
|
|
285
290
|
if (task.merge_armed_seen === seen) return;
|
|
286
|
-
db.
|
|
287
|
-
seen,
|
|
288
|
-
task.id
|
|
289
|
-
);
|
|
291
|
+
setPr(db, task.id, { merge_armed_seen: seen });
|
|
290
292
|
event(
|
|
291
293
|
workspaceId,
|
|
292
294
|
task,
|
|
@@ -76,7 +76,7 @@ export function releaseInterruptedClaims(): number {
|
|
|
76
76
|
`UPDATE orchestrator_asks SET used_at = NULL
|
|
77
77
|
WHERE status = 'approved' AND used_at IS NOT NULL
|
|
78
78
|
AND COALESCE(answer, '') != 'approve (void)'
|
|
79
|
-
AND (subject IN (SELECT 'task:' || id FROM
|
|
79
|
+
AND (subject IN (SELECT 'task:' || id FROM session_rows WHERE task_status = 'running')
|
|
80
80
|
-- A PR no task owns: a merge that happened can't happen again.
|
|
81
81
|
OR subject LIKE 'pr:%')`
|
|
82
82
|
)
|
|
@@ -34,7 +34,7 @@ export async function followAskHeads(
|
|
|
34
34
|
)
|
|
35
35
|
continue;
|
|
36
36
|
const task = db
|
|
37
|
-
.prepare(`SELECT * FROM
|
|
37
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
38
38
|
.get(f!.id) as Session | undefined;
|
|
39
39
|
if (!task || hostLink(task.host_id)) continue;
|
|
40
40
|
try {
|
|
@@ -44,7 +44,7 @@ function workspaceTasks(workspaceId: string): TaskRow[] {
|
|
|
44
44
|
return db
|
|
45
45
|
.prepare(
|
|
46
46
|
`SELECT s.id, s.task_status, s.pr_status, s.pr_url, s.pr_number
|
|
47
|
-
FROM
|
|
47
|
+
FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
48
48
|
WHERE p.workspace_id = ? AND s.task_status IS NOT NULL`
|
|
49
49
|
)
|
|
50
50
|
.all(workspaceId) as TaskRow[];
|
|
@@ -81,7 +81,7 @@ export async function runningCount(
|
|
|
81
81
|
// minutes, here or there: it holds its slot.
|
|
82
82
|
const moving = db
|
|
83
83
|
.prepare(
|
|
84
|
-
`SELECT s.id FROM
|
|
84
|
+
`SELECT s.id FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
85
85
|
WHERE p.workspace_id = ? AND s.task_status = 'moving' AND s.peer_mirror = 0`
|
|
86
86
|
)
|
|
87
87
|
.all(workspaceId) as { id: string }[];
|
|
@@ -167,7 +167,7 @@ function tasksSection(
|
|
|
167
167
|
.prepare(
|
|
168
168
|
`SELECT s.id, s.name, p.name AS project, s.pr_number, s.pr_status, s.view,
|
|
169
169
|
s.merge_armed_at AS armed_at, s.pause_state, s.paused_until, s.pause_note, s.placed_note
|
|
170
|
-
FROM
|
|
170
|
+
FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
171
171
|
WHERE p.workspace_id = ? AND s.role IS NULL AND s.archived_at IS NULL
|
|
172
172
|
AND s.task_status = 'running'
|
|
173
173
|
ORDER BY s.created_at, s.id`
|
|
@@ -223,7 +223,7 @@ function queueSection(workspaceId: string): Section {
|
|
|
223
223
|
AND b.held_queued = 1 AND b.delivered_at IS NULL AND b.read_at IS NULL) AS held,
|
|
224
224
|
p.name AS project, a.name AS after_name
|
|
225
225
|
FROM task_queue q JOIN projects p ON p.id = q.project_id
|
|
226
|
-
LEFT JOIN
|
|
226
|
+
LEFT JOIN session_rows a ON a.id = q.after_task
|
|
227
227
|
WHERE p.workspace_id = ? AND q.status IN ('queued', 'starting', 'failed')
|
|
228
228
|
ORDER BY q.position`
|
|
229
229
|
)
|
|
@@ -94,7 +94,7 @@ const unique = (repos: WorkspaceRepo[]) => [
|
|
|
94
94
|
function tasksWithPR(workspaceId: string, ref: PRRef): WorkspaceTask[] {
|
|
95
95
|
const rows = db
|
|
96
96
|
.prepare(
|
|
97
|
-
`SELECT s.*, p.name AS project_name FROM
|
|
97
|
+
`SELECT s.*, p.name AS project_name FROM session_rows s
|
|
98
98
|
JOIN projects p ON p.id = s.project_id
|
|
99
99
|
WHERE p.workspace_id = ? AND s.pr_number = ? AND s.task_status IS NOT NULL
|
|
100
100
|
ORDER BY s.created_at DESC`
|
|
@@ -125,7 +125,7 @@ async function ownerOf(
|
|
|
125
125
|
.prepare(
|
|
126
126
|
`SELECT s.*, p.name AS project_name, p.workspace_id AS owner_workspace,
|
|
127
127
|
p.working_directory AS project_dir, p.host_id AS project_host
|
|
128
|
-
FROM
|
|
128
|
+
FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
129
129
|
WHERE s.task_status IS NOT NULL
|
|
130
130
|
AND (s.pr_number = ? OR (s.branch_name = ? AND s.task_status IN ('running', 'moving', 'moved')))
|
|
131
131
|
ORDER BY s.created_at DESC`
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { db, type Session, type StackItemStatus } from "../db";
|
|
9
9
|
import { statusDetector } from "../status-detector";
|
|
10
|
-
import {
|
|
10
|
+
import { hostLink } from "../hosts/remote-api";
|
|
11
11
|
import { peerStatus } from "../hosts/peer-sessions";
|
|
12
12
|
import { chatCompacting, chatState } from "../chat/runner";
|
|
13
13
|
import { chatActivityLine, chatStall, type Stall } from "../chat/activity";
|
|
@@ -18,14 +18,13 @@ import { sessionRowInfo } from "../session-meta";
|
|
|
18
18
|
import { taskView, type TaskPR, type TaskState } from "../tasks";
|
|
19
19
|
import { prFor, storedPR } from "../tasks/session";
|
|
20
20
|
import { lastActiveOf } from "../tasks/last-active";
|
|
21
|
-
import
|
|
21
|
+
import * as taskClient from "../tasks/contract/client";
|
|
22
22
|
import { deriveTaskState } from "../tasks";
|
|
23
23
|
import { waitingState } from "./task-state";
|
|
24
24
|
import { commitTime, repoOf } from "./repo";
|
|
25
25
|
import { CHECKLESS_MS, checklessFor, ciSettleIn } from "./task-state";
|
|
26
26
|
import { repoHasCI } from "./repo-ci";
|
|
27
27
|
import { markStatusDirty } from "../status/dirty";
|
|
28
|
-
import { queueEvent } from "./events";
|
|
29
28
|
|
|
30
29
|
export type FactStatus = "running" | "waiting" | "idle" | "dead";
|
|
31
30
|
|
|
@@ -85,7 +84,7 @@ export function workspaceSessions(
|
|
|
85
84
|
): (Session & { project_name: string })[] {
|
|
86
85
|
return db
|
|
87
86
|
.prepare(
|
|
88
|
-
`SELECT s.*, p.name AS project_name FROM
|
|
87
|
+
`SELECT s.*, p.name AS project_name FROM session_rows s
|
|
89
88
|
JOIN projects p ON p.id = s.project_id
|
|
90
89
|
WHERE p.workspace_id = ? AND s.role IS NULL AND s.archived_at IS NULL
|
|
91
90
|
-- A linked machine's own sessions are its orchestrator's to run.
|
|
@@ -271,7 +270,6 @@ async function remoteTaskFacts(
|
|
|
271
270
|
prFor(s).catch(() => null),
|
|
272
271
|
waitingState(s),
|
|
273
272
|
]);
|
|
274
|
-
await announceRemotePlan(workspaceId, s);
|
|
275
273
|
const status = peerStatus(s.host_id, s.id) ?? "running";
|
|
276
274
|
const state = deriveTaskState({
|
|
277
275
|
taskStatus: "running",
|
|
@@ -288,34 +286,12 @@ async function remoteTaskFacts(
|
|
|
288
286
|
};
|
|
289
287
|
}
|
|
290
288
|
|
|
291
|
-
// The plan-written line a held task stored on its machine (plan-step.ts),
|
|
292
|
-
// once per distinct plan: the mirror keeps the last one it told about.
|
|
293
|
-
async function announceRemotePlan(
|
|
294
|
-
workspaceId: string,
|
|
295
|
-
s: Session
|
|
296
|
-
): Promise<void> {
|
|
297
|
-
const link = hostLink(s.host_id);
|
|
298
|
-
if (!link) return;
|
|
299
|
-
const view = await hostTasks(link)
|
|
300
|
-
.then(({ tasks }) => tasks.find((t) => t?.id === s.id))
|
|
301
|
-
.catch(() => undefined);
|
|
302
|
-
const note =
|
|
303
|
-
typeof view?.planNote === "string"
|
|
304
|
-
? cleanRemoteText(view.planNote).slice(0, 2000)
|
|
305
|
-
: "";
|
|
306
|
-
if (!note || note === s.plan_note) return;
|
|
307
|
-
db.prepare(`UPDATE sessions SET plan_note = ? WHERE id = ?`).run(note, s.id);
|
|
308
|
-
queueEvent(workspaceId, `plan-done:${s.id}`, s.id, note);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
289
|
// A task on another machine is as active as that machine says; one it can't
|
|
312
290
|
// reach is taken as active just now, so nothing reads it as abandoned.
|
|
313
291
|
async function lastActiveFor(s: Session): Promise<number> {
|
|
314
292
|
const link = hostLink(s.host_id);
|
|
315
293
|
if (!link) return lastActiveOf(s);
|
|
316
|
-
const view = await
|
|
317
|
-
.then(({ tasks }) => tasks.find((t) => t?.id === s.id))
|
|
318
|
-
.catch(() => undefined);
|
|
294
|
+
const view = await taskClient.view(link, s.id).catch(() => undefined);
|
|
319
295
|
return typeof view?.lastActiveAt === "number"
|
|
320
296
|
? view.lastActiveAt
|
|
321
297
|
: Date.now();
|
|
@@ -58,9 +58,9 @@ async function raiseFor(
|
|
|
58
58
|
now: number
|
|
59
59
|
): Promise<void> {
|
|
60
60
|
const f = facts.find((x) => x.id === taskId);
|
|
61
|
-
const task = db
|
|
62
|
-
|
|
63
|
-
| undefined;
|
|
61
|
+
const task = db
|
|
62
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
63
|
+
.get(taskId) as Session | undefined;
|
|
64
64
|
// Gone, merged or dropped: nothing left to ask. An open ask or an
|
|
65
65
|
// unspent approval already covers it (followAskHeads, heldVerdict).
|
|
66
66
|
if (
|
package/lib/orchestrator/home.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function getOrchestrator(workspaceId: string): Session | null {
|
|
|
24
24
|
return (
|
|
25
25
|
(db
|
|
26
26
|
.prepare(
|
|
27
|
-
`SELECT * FROM
|
|
27
|
+
`SELECT * FROM session_rows WHERE role = 'orchestrator' AND workspace_id = ?`
|
|
28
28
|
)
|
|
29
29
|
.get(workspaceId) as Session | undefined) ?? null
|
|
30
30
|
);
|
|
@@ -32,7 +32,7 @@ export function getOrchestrator(workspaceId: string): Session | null {
|
|
|
32
32
|
|
|
33
33
|
export function listOrchestrators(): Session[] {
|
|
34
34
|
return db
|
|
35
|
-
.prepare(`SELECT * FROM
|
|
35
|
+
.prepare(`SELECT * FROM session_rows WHERE role = 'orchestrator'`)
|
|
36
36
|
.all() as Session[];
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -25,7 +25,7 @@ function inReview(workspaceId: string): number {
|
|
|
25
25
|
return (
|
|
26
26
|
db
|
|
27
27
|
.prepare(
|
|
28
|
-
`SELECT COUNT(*) AS n FROM
|
|
28
|
+
`SELECT COUNT(*) AS n FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
29
29
|
WHERE p.workspace_id = ? AND s.role IS NULL
|
|
30
30
|
AND s.task_status = 'running' AND s.pr_status = 'open'`
|
|
31
31
|
)
|
|
@@ -41,6 +41,7 @@ import type { SessionFacts } from "./facts";
|
|
|
41
41
|
import { isPaused } from "./pause";
|
|
42
42
|
import { repoOf } from "./repo";
|
|
43
43
|
import { untrusted, UNTRUSTED_RULE } from "./untrusted";
|
|
44
|
+
import { setPr } from "../db/session-parts";
|
|
44
45
|
|
|
45
46
|
export const COMMENT_WAKE_CAP = 10;
|
|
46
47
|
// A first look at a PR this young takes its whole history as new.
|
|
@@ -93,7 +94,7 @@ const polled = new Map<string, number>();
|
|
|
93
94
|
const failures = new Map<string, number>();
|
|
94
95
|
|
|
95
96
|
const sessionRow = (id: string) =>
|
|
96
|
-
db.prepare(`SELECT * FROM
|
|
97
|
+
db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
|
|
97
98
|
| Session
|
|
98
99
|
| undefined;
|
|
99
100
|
|
|
@@ -350,10 +351,7 @@ function watchSince(task: Session, print: Fingerprint, now: number): string {
|
|
|
350
351
|
Number.isFinite(created) && now - created < YOUNG_PR_MS
|
|
351
352
|
? new Date(created).toISOString()
|
|
352
353
|
: new Date(now).toISOString();
|
|
353
|
-
db.
|
|
354
|
-
since,
|
|
355
|
-
task.id
|
|
356
|
-
);
|
|
354
|
+
setPr(db, task.id, { pr_wake_since: since });
|
|
357
355
|
task.pr_wake_since = since;
|
|
358
356
|
return since;
|
|
359
357
|
}
|
|
@@ -46,7 +46,7 @@ function settleQuestions(workspaceId: string): void {
|
|
|
46
46
|
`UPDATE orchestrator_questions
|
|
47
47
|
SET status = 'cleared', resolved_by = 'orchestrator', resolved_at = datetime('now')
|
|
48
48
|
WHERE workspace_id = ? AND status = 'open' AND task IN (
|
|
49
|
-
SELECT id FROM
|
|
49
|
+
SELECT id FROM session_rows WHERE task_status IN ('merged', 'dropped', 'done'))`
|
|
50
50
|
).run(workspaceId);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -9,7 +9,7 @@ export function quickWorktrees(orchestratorId: string): string[] {
|
|
|
9
9
|
return (
|
|
10
10
|
db
|
|
11
11
|
.prepare(
|
|
12
|
-
`SELECT worktree_path AS dir FROM
|
|
12
|
+
`SELECT worktree_path AS dir FROM session_rows
|
|
13
13
|
WHERE quick_of = ? AND task_status = 'running'
|
|
14
14
|
AND worktree_path IS NOT NULL AND worktree_path != ''`
|
|
15
15
|
)
|
|
@@ -29,6 +29,7 @@ import { addNote } from "./notes";
|
|
|
29
29
|
import { quickWorktrees } from "./quick-store";
|
|
30
30
|
import { findWorkspaceSession } from "./read";
|
|
31
31
|
import { workspaceProject } from "./targets";
|
|
32
|
+
import { recordPr, setTask } from "../db/session-parts";
|
|
32
33
|
|
|
33
34
|
// Open quick changes one orchestrator may hold at once.
|
|
34
35
|
export const MAX_OPEN_QUICK = 5;
|
|
@@ -105,8 +106,13 @@ export async function startQuickChange(
|
|
|
105
106
|
.updateSessionWorktree(db)
|
|
106
107
|
.run(wt.worktreePath, wt.branchName, baseBranch, null, id);
|
|
107
108
|
db.prepare(
|
|
108
|
-
`UPDATE sessions SET
|
|
109
|
-
).run(
|
|
109
|
+
`UPDATE sessions SET name_source = 'user', view = 'chat' WHERE id = ?`
|
|
110
|
+
).run(id);
|
|
111
|
+
setTask(db, id, {
|
|
112
|
+
task_prompt: `Quick change: ${label}`,
|
|
113
|
+
task_status: "running",
|
|
114
|
+
quick_of: owner.id,
|
|
115
|
+
});
|
|
110
116
|
})();
|
|
111
117
|
} catch (e) {
|
|
112
118
|
await deleteWorktree(wt.worktreePath, projectPath, true).catch(() => {});
|
|
@@ -208,10 +214,7 @@ export async function quickPR(
|
|
|
208
214
|
);
|
|
209
215
|
const head = (await revParse(wt, "HEAD")).trim();
|
|
210
216
|
const goal = `${title.trim()}\n\n${body.trim()}`;
|
|
211
|
-
db.
|
|
212
|
-
goal,
|
|
213
|
-
s.id
|
|
214
|
-
);
|
|
217
|
+
setTask(db, s.id, { task_prompt: goal });
|
|
215
218
|
const existing = await prFor(s, true);
|
|
216
219
|
if (existing?.state === "OPEN") {
|
|
217
220
|
forgetPR(s.id);
|
|
@@ -238,7 +241,7 @@ export async function quickPR(
|
|
|
238
241
|
60_000
|
|
239
242
|
);
|
|
240
243
|
const m = /https:\/\/\S+\/pull\/(\d+)/.exec(out);
|
|
241
|
-
if (m)
|
|
244
|
+
if (m) recordPr(db, s.id, { url: m[0], number: Number(m[1]), state: "open" });
|
|
242
245
|
forgetPR(s.id);
|
|
243
246
|
addNote(
|
|
244
247
|
workspaceId,
|
package/lib/orchestrator/read.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { hostLink } from "../hosts/remote-api";
|
|
|
10
10
|
import { peerPane } from "../hosts/peer-sessions";
|
|
11
11
|
import { workspaceSessions } from "./facts";
|
|
12
12
|
import { untrusted } from "./untrusted";
|
|
13
|
-
import { heldChatTail, isHeldRemote } from "../tasks/held-
|
|
13
|
+
import { heldChatTail, isHeldRemote } from "../tasks/contract/held-calls";
|
|
14
14
|
import {
|
|
15
15
|
findExternalSession,
|
|
16
16
|
knownExternalSession,
|
|
@@ -39,6 +39,7 @@ import { statusOf, type SessionFacts } from "./facts";
|
|
|
39
39
|
import { isPaused } from "./pause";
|
|
40
40
|
import { repoOf } from "./repo";
|
|
41
41
|
import { reviewAt, taskReviewTarget } from "./review";
|
|
42
|
+
import { setPr } from "../db/session-parts";
|
|
42
43
|
|
|
43
44
|
const short = (sha: string) => sha.slice(0, 7);
|
|
44
45
|
|
|
@@ -72,7 +73,7 @@ const defaults: RebaseDeps = {
|
|
|
72
73
|
const tried = new Map<string, string>();
|
|
73
74
|
|
|
74
75
|
const sessionRow = (id: string) =>
|
|
75
|
-
db.prepare(`SELECT * FROM
|
|
76
|
+
db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
|
|
76
77
|
| Session
|
|
77
78
|
| undefined;
|
|
78
79
|
|
|
@@ -207,9 +208,10 @@ async function rebaseOne(
|
|
|
207
208
|
},
|
|
208
209
|
{ from: pr.head, to, unpushed: false }
|
|
209
210
|
);
|
|
210
|
-
db.
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
setPr(db, task.id, {
|
|
212
|
+
rebased_from: heads.restacked_from,
|
|
213
|
+
rebased_to: heads.restacked_to,
|
|
214
|
+
});
|
|
213
215
|
// Only the task's own branch, and only if origin still has the head
|
|
214
216
|
// that was judged. --no-verify: CI runs on the PR.
|
|
215
217
|
const pushed = await git(
|
|
@@ -225,9 +227,7 @@ async function rebaseOne(
|
|
|
225
227
|
);
|
|
226
228
|
if (pushed !== null) {
|
|
227
229
|
console.error(`Pushing ${task.name}'s rebase:`, pushed);
|
|
228
|
-
db.
|
|
229
|
-
`UPDATE sessions SET rebased_from = ?, rebased_to = ? WHERE id = ?`
|
|
230
|
-
).run(...before, task.id);
|
|
230
|
+
setPr(db, task.id, { rebased_from: before[0], rebased_to: before[1] });
|
|
231
231
|
return askToRebase(
|
|
232
232
|
workspaceId,
|
|
233
233
|
task,
|
|
@@ -362,10 +362,7 @@ async function askToRebase(
|
|
|
362
362
|
deps: RebaseDeps
|
|
363
363
|
): Promise<void> {
|
|
364
364
|
// Claimed first: a restart mid-send doesn't ask twice.
|
|
365
|
-
db.
|
|
366
|
-
pr.head,
|
|
367
|
-
task.id
|
|
368
|
-
);
|
|
365
|
+
setPr(db, task.id, { rebase_asked_head: pr.head });
|
|
369
366
|
// A quick change has no agent to ask: it's the orchestrator's to redo.
|
|
370
367
|
if (task.quick_of) {
|
|
371
368
|
queueEvent(
|
|
@@ -114,7 +114,7 @@ async function ownedBy(
|
|
|
114
114
|
): Promise<boolean> {
|
|
115
115
|
const rows = db
|
|
116
116
|
.prepare(
|
|
117
|
-
`SELECT s.pr_url, s.branch_name, p.working_directory FROM
|
|
117
|
+
`SELECT s.pr_url, s.branch_name, p.working_directory FROM session_rows s
|
|
118
118
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
119
119
|
WHERE s.task_status IS NOT NULL AND (LOWER(s.pr_url) = ? OR s.branch_name = ?)`
|
|
120
120
|
)
|
|
@@ -145,7 +145,7 @@ export async function classifyAgentPrs(
|
|
|
145
145
|
let raised = 0;
|
|
146
146
|
for (const pr of open) {
|
|
147
147
|
const task = db
|
|
148
|
-
.prepare(`SELECT * FROM
|
|
148
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
149
149
|
.get(pr.session_id) as Session | undefined;
|
|
150
150
|
const workspaceId = task ? sessionWorkspace(task) : null;
|
|
151
151
|
if (!task || !workspaceId) {
|
|
@@ -331,7 +331,9 @@ export async function refundIfRefused(
|
|
|
331
331
|
await merge();
|
|
332
332
|
} catch (e) {
|
|
333
333
|
const merged = db
|
|
334
|
-
.prepare(
|
|
334
|
+
.prepare(
|
|
335
|
+
`SELECT 1 FROM session_rows WHERE id = ? AND task_status = 'merged'`
|
|
336
|
+
)
|
|
335
337
|
.get(taskId);
|
|
336
338
|
if (approval && !merged) refundApproval(approval);
|
|
337
339
|
throw e;
|
|
@@ -56,7 +56,7 @@ export function workspaceTask(
|
|
|
56
56
|
if (pr !== null) {
|
|
57
57
|
const rows = db
|
|
58
58
|
.prepare(
|
|
59
|
-
`SELECT s.*, p.name AS project_name FROM
|
|
59
|
+
`SELECT s.*, p.name AS project_name FROM session_rows s
|
|
60
60
|
JOIN projects p ON p.id = s.project_id
|
|
61
61
|
WHERE p.workspace_id = ? AND s.pr_number = ? AND s.task_status IS NOT NULL
|
|
62
62
|
ORDER BY s.created_at DESC`
|
|
@@ -9,8 +9,8 @@ import { blockedReason } from "../tasks/state";
|
|
|
9
9
|
import { checkWaitingPatterns, statusDetector } from "../status-detector";
|
|
10
10
|
import { getCheck, putCheck } from "./checks";
|
|
11
11
|
import { cleanRemoteText, hostLink, type HostLink } from "../hosts/remote-api";
|
|
12
|
-
import { heldChatFacts, isHeldRemote } from "../tasks/held-
|
|
13
|
-
import { hostTasks } from "../tasks/
|
|
12
|
+
import { heldChatFacts, isHeldRemote } from "../tasks/contract/held-calls";
|
|
13
|
+
import { list as hostTasks } from "../tasks/contract/client";
|
|
14
14
|
import { peerPane } from "../hosts/peer-sessions";
|
|
15
15
|
import { untrusted } from "./untrusted";
|
|
16
16
|
|
|
@@ -104,6 +104,8 @@ async function peerChatWaiting(
|
|
|
104
104
|
: `its chat on ${link.hostName} couldn't say whether a question is open`,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
+
// compat: task-contract-1. A machine without held-chat-facts is asked
|
|
108
|
+
// through its task list; drop this once every linked machine has it.
|
|
107
109
|
const view = await hostTasks(link, true)
|
|
108
110
|
.then(({ tasks, capabilities }) =>
|
|
109
111
|
// One that can't say its chat's turn can't rule out an open question.
|
package/lib/plans/features.ts
CHANGED
|
@@ -160,7 +160,7 @@ export function pieceOfSession(sessionId: string): FeaturePiece | undefined {
|
|
|
160
160
|
export function prOf(p: FeaturePiece): number | null {
|
|
161
161
|
if (!p.session_id) return null;
|
|
162
162
|
const s = db
|
|
163
|
-
.prepare(`SELECT pr_number, project_id FROM
|
|
163
|
+
.prepare(`SELECT pr_number, project_id FROM session_rows WHERE id = ?`)
|
|
164
164
|
.get(p.session_id) as
|
|
165
165
|
| { pr_number: number | null; project_id: string | null }
|
|
166
166
|
| undefined;
|
package/lib/plans/release.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface ReleaseDeps {
|
|
|
38
38
|
|
|
39
39
|
const sessionOf = (id: string | null) =>
|
|
40
40
|
id
|
|
41
|
-
? (db.prepare(`SELECT * FROM
|
|
41
|
+
? (db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
|
|
42
42
|
| Session
|
|
43
43
|
| undefined)
|
|
44
44
|
: undefined;
|
package/lib/ports.ts
CHANGED
|
@@ -70,7 +70,7 @@ async function reclaimEnded(
|
|
|
70
70
|
): Promise<void> {
|
|
71
71
|
const rows = db
|
|
72
72
|
.prepare(
|
|
73
|
-
`SELECT id, tmux_name, task_status, host_id FROM
|
|
73
|
+
`SELECT id, tmux_name, task_status, host_id FROM session_rows
|
|
74
74
|
WHERE port_slot IS NOT NULL
|
|
75
75
|
AND (archived_at IS NOT NULL OR task_status IN ('done', 'merged'))`
|
|
76
76
|
)
|
package/lib/resurrect/find.ts
CHANGED
|
@@ -65,7 +65,7 @@ function rows(scope: EndedScope, limit: number, kind?: EndedKind): Row[] {
|
|
|
65
65
|
`SELECT s.id, s.name, s.tmux_name, s.project_id, p.name AS project_name,
|
|
66
66
|
s.task_status, s.archived_at, s.plan_phase,
|
|
67
67
|
COALESCE(s.task_ended_at, s.archived_at, s.settled_at, s.updated_at) AS ended_at
|
|
68
|
-
FROM
|
|
68
|
+
FROM session_rows s LEFT JOIN projects p ON p.id = s.project_id
|
|
69
69
|
WHERE ${where.join(" AND ")}
|
|
70
70
|
ORDER BY ended_at DESC LIMIT ?`
|
|
71
71
|
)
|