@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { execFile } from "child_process";
|
|
3
3
|
import { promisify } from "util";
|
|
4
|
-
import { getDb, queries, type Session } from "@/lib/db";
|
|
4
|
+
import { getDb, queries, recordPr, type Session } from "@/lib/db";
|
|
5
5
|
import { gitPush } from "@/lib/git-exec";
|
|
6
6
|
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
@@ -129,7 +129,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
|
|
129
129
|
|
|
130
130
|
// Update session with PR info if found
|
|
131
131
|
if (pr) {
|
|
132
|
-
|
|
132
|
+
recordPr(db, id, pr);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
return NextResponse.json({ pr });
|
|
@@ -197,7 +197,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
|
|
197
197
|
);
|
|
198
198
|
|
|
199
199
|
// Save PR info to session
|
|
200
|
-
|
|
200
|
+
recordPr(db, id, pr);
|
|
201
201
|
|
|
202
202
|
return NextResponse.json({ pr }, { status: 201 });
|
|
203
203
|
} catch (error) {
|
|
@@ -16,9 +16,9 @@ export async function GET(
|
|
|
16
16
|
const { id } = await params;
|
|
17
17
|
const live = getSetup(id);
|
|
18
18
|
if (live) return NextResponse.json({ setup: live });
|
|
19
|
-
const session = db
|
|
20
|
-
|
|
21
|
-
| undefined;
|
|
19
|
+
const session = db
|
|
20
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
21
|
+
.get(id) as Session | undefined;
|
|
22
22
|
if (!session)
|
|
23
23
|
return NextResponse.json({ error: "Session not found" }, { status: 404 });
|
|
24
24
|
const link = isRemoteHost(session.host_id) ? hostLink(session.host_id) : null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { approvePlanNow } from "@/lib/models/plan-step";
|
|
3
4
|
|
|
@@ -7,13 +8,16 @@ export async function POST(
|
|
|
7
8
|
request: NextRequest,
|
|
8
9
|
{ params }: { params: Promise<{ id: string }> }
|
|
9
10
|
) {
|
|
11
|
+
const id = (await params).id;
|
|
12
|
+
const refused = refuse(request, id, "plan");
|
|
13
|
+
if (refused) return refused;
|
|
10
14
|
try {
|
|
11
15
|
const body = await request.json().catch(() => ({}));
|
|
12
16
|
const message =
|
|
13
17
|
typeof body?.message === "string" && body.message.trim()
|
|
14
18
|
? body.message.trim().slice(0, 8000)
|
|
15
19
|
: undefined;
|
|
16
|
-
const out = await approvePlanNow(
|
|
20
|
+
const out = await approvePlanNow(id, message);
|
|
17
21
|
return NextResponse.json({ success: true, model: out.ref });
|
|
18
22
|
} catch (error) {
|
|
19
23
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { db, type Session } from "@/lib/db";
|
|
3
4
|
import { armTask, setArmed } from "@/lib/orchestrator/armed";
|
|
4
|
-
import { callingDevice } from "@/lib/security/device-me";
|
|
5
|
-
import { heldRefusal } from "@/lib/tasks/held";
|
|
6
5
|
|
|
7
6
|
// { armed: true } merges the task's PR by itself once every gate passes;
|
|
8
7
|
// false takes that back.
|
|
@@ -11,8 +10,8 @@ export async function POST(
|
|
|
11
10
|
{ params }: { params: Promise<{ id: string }> }
|
|
12
11
|
) {
|
|
13
12
|
const id = (await params).id;
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
13
|
+
const refused = refuse(request, id, "open");
|
|
14
|
+
if (refused) return refused;
|
|
16
15
|
const { armed } = await request.json().catch(() => ({}));
|
|
17
16
|
if (typeof armed !== "boolean")
|
|
18
17
|
return NextResponse.json(
|
|
@@ -20,7 +19,9 @@ export async function POST(
|
|
|
20
19
|
{ status: 400 }
|
|
21
20
|
);
|
|
22
21
|
const task = db
|
|
23
|
-
.prepare(
|
|
22
|
+
.prepare(
|
|
23
|
+
`SELECT * FROM session_rows WHERE id = ? AND task_status IS NOT NULL`
|
|
24
|
+
)
|
|
24
25
|
.get((await params).id) as Session | undefined;
|
|
25
26
|
if (!task)
|
|
26
27
|
return NextResponse.json({ error: "No such task" }, { status: 404 });
|
|
@@ -2,7 +2,7 @@ import { NextRequest } from "next/server";
|
|
|
2
2
|
import { blockedReason } from "@/lib/tasks/state";
|
|
3
3
|
import { saidSinceLastMessage } from "@/lib/chat/store";
|
|
4
4
|
import { chatStateNow } from "@/lib/chat/runner";
|
|
5
|
-
import {
|
|
5
|
+
import { taskRoute } from "@/lib/tasks/contract/authorize";
|
|
6
6
|
|
|
7
7
|
// GET /api/tasks/:id/chat-facts - what its chat says about being blocked
|
|
8
8
|
// and whether a question is open, for the orchestrator that moved it here.
|
|
@@ -11,7 +11,7 @@ export async function GET(
|
|
|
11
11
|
request: NextRequest,
|
|
12
12
|
ctx: { params: Promise<{ id: string }> }
|
|
13
13
|
) {
|
|
14
|
-
return
|
|
14
|
+
return taskRoute(request, ctx, "holder", async (task) => {
|
|
15
15
|
if (task.view !== "chat") return { blocked: null, turn: null };
|
|
16
16
|
return {
|
|
17
17
|
blocked: blockedReason(saidSinceLastMessage(task.id)),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest } from "next/server";
|
|
2
2
|
import { chatTailText } from "@/lib/orchestrator/read";
|
|
3
|
-
import {
|
|
3
|
+
import { taskRoute } from "@/lib/tasks/contract/authorize";
|
|
4
4
|
|
|
5
5
|
// GET /api/tasks/:id/chat-tail?lines= - the last lines of its conversation,
|
|
6
6
|
// for the orchestrator that moved it here to read.
|
|
@@ -9,7 +9,7 @@ export async function GET(
|
|
|
9
9
|
ctx: { params: Promise<{ id: string }> }
|
|
10
10
|
) {
|
|
11
11
|
const lines = Number(request.nextUrl.searchParams.get("lines")) || 60;
|
|
12
|
-
return
|
|
12
|
+
return taskRoute(request, ctx, "holder", async (task) => ({
|
|
13
13
|
text: task.view === "chat" ? chatTailText(task.id, lines) : "",
|
|
14
14
|
}));
|
|
15
15
|
}
|
|
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|
|
2
2
|
import { db, queries, type Session } from "@/lib/db";
|
|
3
3
|
import { taskChecks } from "@/lib/tasks/checks-view";
|
|
4
4
|
import { LoopError, localTask } from "@/lib/tasks/loop-error";
|
|
5
|
-
import { isMirror } from "@/lib/tasks/
|
|
5
|
+
import { isMirror } from "@/lib/tasks/contract/start";
|
|
6
6
|
|
|
7
7
|
// GET /api/tasks/:id/checks - the PR head's checks, the tail of a failing
|
|
8
8
|
// job's log, and the review gate's verdict on the head. ?log=0 leaves the
|
|
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|
|
2
2
|
import { db, queries, type Session } from "@/lib/db";
|
|
3
3
|
import { taskDiff } from "@/lib/tasks/diff";
|
|
4
4
|
import { LoopError, localTask } from "@/lib/tasks/loop-error";
|
|
5
|
-
import { isMirror } from "@/lib/tasks/
|
|
5
|
+
import { isMirror } from "@/lib/tasks/contract/start";
|
|
6
6
|
|
|
7
7
|
// GET /api/tasks/:id/diff - the task's whole change, base to head: a file
|
|
8
8
|
// list with status and line counts; with ?file=<path>, that file's patch.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest } from "next/server";
|
|
2
2
|
import { doneSession } from "@/lib/done";
|
|
3
|
-
import {
|
|
3
|
+
import { taskRoute } from "@/lib/tasks/contract/authorize";
|
|
4
4
|
|
|
5
5
|
// POST /api/tasks/:id/done - its orchestrator finishes it: the agent stops
|
|
6
6
|
// and the worktree goes. Never a merge (noMerge): an open PR is refused
|
|
@@ -9,7 +9,7 @@ export async function POST(
|
|
|
9
9
|
request: NextRequest,
|
|
10
10
|
ctx: { params: Promise<{ id: string }> }
|
|
11
11
|
) {
|
|
12
|
-
return
|
|
12
|
+
return taskRoute(request, ctx, "holder", async (task) => ({
|
|
13
13
|
outcome: await doneSession(task.id, { by: "direct", noMerge: true }),
|
|
14
14
|
}));
|
|
15
15
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
2
3
|
import { dropTask } from "@/lib/tasks";
|
|
3
4
|
|
|
4
5
|
export async function POST(
|
|
5
|
-
|
|
6
|
+
request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string }> }
|
|
7
8
|
) {
|
|
9
|
+
const id = (await params).id;
|
|
10
|
+
const refused = refuse(request, id, "open");
|
|
11
|
+
if (refused) return refused;
|
|
8
12
|
try {
|
|
9
|
-
await dropTask(
|
|
13
|
+
await dropTask(id);
|
|
10
14
|
return NextResponse.json({ success: true });
|
|
11
15
|
} catch (error) {
|
|
12
16
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { exportOrResume } from "@/lib/tasks/move";
|
|
3
|
-
import { callingDevice } from "@/lib/security/device-me";
|
|
4
|
-
import { heldRefusal } from "@/lib/tasks/held";
|
|
5
4
|
|
|
6
5
|
// POST /api/tasks/:id/export - stop the agent, push, hand over the task.
|
|
7
6
|
// The caller confirms with /moved, or restarts it here with /resume; if it
|
|
@@ -11,8 +10,8 @@ export async function POST(
|
|
|
11
10
|
{ params }: { params: Promise<{ id: string }> }
|
|
12
11
|
) {
|
|
13
12
|
const id = (await params).id;
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
13
|
+
const refused = refuse(request, id, "plan");
|
|
14
|
+
if (refused) return refused;
|
|
16
15
|
try {
|
|
17
16
|
const { to } = await request.json().catch(() => ({}));
|
|
18
17
|
const bundle = await exportOrResume(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { signOffTask } from "@/lib/tasks";
|
|
3
4
|
import { callingDevice } from "@/lib/security/device-me";
|
|
4
5
|
import { isBranchName } from "@/lib/git";
|
|
5
|
-
import { heldRefusal } from "@/lib/tasks/held";
|
|
6
6
|
|
|
7
7
|
// head: merge only if the PR is still at this commit; the answer names the
|
|
8
8
|
// commit it was pinned to, so another machine forwarding a merge can check.
|
|
@@ -11,8 +11,8 @@ export async function POST(
|
|
|
11
11
|
{ params }: { params: Promise<{ id: string }> }
|
|
12
12
|
) {
|
|
13
13
|
const id = (await params).id;
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
14
|
+
const refused = refuse(request, id, "open");
|
|
15
|
+
if (refused) return refused;
|
|
16
16
|
const { head, base } = await request.json().catch(() => ({}));
|
|
17
17
|
if (
|
|
18
18
|
head !== undefined &&
|
|
@@ -3,7 +3,7 @@ import { statusDetector } from "@/lib/status-detector";
|
|
|
3
3
|
import { deliver } from "@/lib/bus";
|
|
4
4
|
import { sendChatConfirmed } from "@/lib/chat/runner";
|
|
5
5
|
import { cleanRemoteText } from "@/lib/hosts/remote-api";
|
|
6
|
-
import {
|
|
6
|
+
import { taskRoute } from "@/lib/tasks/contract/authorize";
|
|
7
7
|
|
|
8
8
|
const MAX_LINE = 40_000;
|
|
9
9
|
|
|
@@ -15,7 +15,7 @@ export async function POST(
|
|
|
15
15
|
ctx: { params: Promise<{ id: string }> }
|
|
16
16
|
) {
|
|
17
17
|
const body = await request.json().catch(() => ({}));
|
|
18
|
-
return
|
|
18
|
+
return taskRoute(request, ctx, "holder", async (task) => {
|
|
19
19
|
const line = typeof body.line === "string" ? body.line.trim() : "";
|
|
20
20
|
if (!line || line.length > MAX_LINE) throw new Error("Bad message");
|
|
21
21
|
const id = typeof body.id === "string" ? body.id.slice(0, 100) : undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { resumeOnModel } from "@/lib/models/resume";
|
|
3
4
|
|
|
@@ -8,10 +9,13 @@ export async function POST(
|
|
|
8
9
|
request: NextRequest,
|
|
9
10
|
{ params }: { params: Promise<{ id: string }> }
|
|
10
11
|
) {
|
|
12
|
+
const id = (await params).id;
|
|
13
|
+
const refused = refuse(request, id, "open");
|
|
14
|
+
if (refused) return refused;
|
|
11
15
|
const { model, message } = await request.json().catch(() => ({}));
|
|
12
16
|
try {
|
|
13
17
|
const result = await resumeOnModel(
|
|
14
|
-
|
|
18
|
+
id,
|
|
15
19
|
model,
|
|
16
20
|
typeof message === "string" && message.trim() ? message : undefined
|
|
17
21
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
2
3
|
import { moveTask } from "@/lib/tasks";
|
|
3
4
|
import { getProgress } from "@/lib/tasks/move-progress";
|
|
4
5
|
|
|
@@ -7,9 +8,12 @@ export async function POST(
|
|
|
7
8
|
request: NextRequest,
|
|
8
9
|
{ params }: { params: Promise<{ id: string }> }
|
|
9
10
|
) {
|
|
11
|
+
const id = (await params).id;
|
|
12
|
+
const refused = refuse(request, id, "open");
|
|
13
|
+
if (refused) return refused;
|
|
10
14
|
try {
|
|
11
15
|
const { hostId } = await request.json();
|
|
12
|
-
const session = await moveTask(
|
|
16
|
+
const session = await moveTask(id, String(hostId || ""));
|
|
13
17
|
return NextResponse.json({ session });
|
|
14
18
|
} catch (error) {
|
|
15
19
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { markMoved } from "@/lib/tasks/move";
|
|
3
|
-
import {
|
|
4
|
-
import { heldRefusal } from "@/lib/tasks/held";
|
|
3
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
5
4
|
import { cleanUpMovedAway } from "@/lib/tasks/moved-away";
|
|
6
5
|
import { runInBackground } from "@/lib/async-operations";
|
|
7
6
|
|
|
@@ -11,14 +10,13 @@ export async function POST(
|
|
|
11
10
|
{ params }: { params: Promise<{ id: string }> }
|
|
12
11
|
) {
|
|
13
12
|
const id = (await params).id;
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
13
|
+
const refused = refuse(request, id, "open");
|
|
14
|
+
if (refused) return refused;
|
|
16
15
|
const { to } = await request.json().catch(() => ({}));
|
|
17
16
|
markMoved(id, String(to || "another machine").slice(0, 100));
|
|
18
17
|
// A task its orchestrator took back: its agent and worktree here go.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}, `moved-away-${id}`);
|
|
18
|
+
runInBackground(async () => {
|
|
19
|
+
await cleanUpMovedAway(id);
|
|
20
|
+
}, `moved-away-${id}`);
|
|
23
21
|
return NextResponse.json({ success: true });
|
|
24
22
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
5
|
+
import { db, type Session } from "@/lib/db";
|
|
6
|
+
import { MAX_PLAN_BYTES } from "@/lib/tasks/move-bundle";
|
|
7
|
+
import { PLAN_FILE } from "@/lib/models/plan-step";
|
|
8
|
+
import { expandHome } from "@/lib/tasks/session";
|
|
9
|
+
|
|
10
|
+
// GET /api/tasks/:id/plan - the plan file of a task whose plan is written, for
|
|
11
|
+
// the machine that placed the plan here and decides what follows it.
|
|
12
|
+
export async function GET(
|
|
13
|
+
request: NextRequest,
|
|
14
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
15
|
+
) {
|
|
16
|
+
const id = (await params).id;
|
|
17
|
+
const refused = refuse(request, id, "plan");
|
|
18
|
+
if (refused) return refused;
|
|
19
|
+
const s = db
|
|
20
|
+
.prepare(`SELECT plan_phase, worktree_path FROM session_rows WHERE id = ?`)
|
|
21
|
+
.get(id) as Pick<Session, "plan_phase" | "worktree_path"> | undefined;
|
|
22
|
+
if (!s?.worktree_path)
|
|
23
|
+
return NextResponse.json({ error: "No such task" }, { status: 404 });
|
|
24
|
+
let text: string | null = null;
|
|
25
|
+
try {
|
|
26
|
+
const read = await fs.readFile(
|
|
27
|
+
path.join(expandHome(s.worktree_path), PLAN_FILE),
|
|
28
|
+
"utf8"
|
|
29
|
+
);
|
|
30
|
+
text = read.length > MAX_PLAN_BYTES ? read.slice(0, MAX_PLAN_BYTES) : read;
|
|
31
|
+
} catch {
|
|
32
|
+
// No plan file was written.
|
|
33
|
+
}
|
|
34
|
+
return NextResponse.json({ text, phase: s.plan_phase ?? null });
|
|
35
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { db, type Session } from "@/lib/db";
|
|
3
4
|
import { notifyStatusChanged } from "@/lib/status/hub";
|
|
5
|
+
import { setPlan } from "@/lib/db/session-parts";
|
|
4
6
|
|
|
5
7
|
// Saad's edits to a waiting plan's pieces: which to fold into the one before
|
|
6
8
|
// and which to leave out. Kept on the task and applied when the plan is
|
|
@@ -10,6 +12,8 @@ export async function PUT(
|
|
|
10
12
|
{ params }: { params: Promise<{ id: string }> }
|
|
11
13
|
) {
|
|
12
14
|
const { id } = await params;
|
|
15
|
+
const refused = refuse(request, id, "open");
|
|
16
|
+
if (refused) return refused;
|
|
13
17
|
const body = await request.json().catch(() => ({}));
|
|
14
18
|
const ints = (v: unknown) =>
|
|
15
19
|
Array.isArray(v)
|
|
@@ -18,17 +22,14 @@ export async function PUT(
|
|
|
18
22
|
const merge = ints(body?.merge);
|
|
19
23
|
const drop = ints(body?.drop).filter((n) => !merge.includes(n));
|
|
20
24
|
const session = db
|
|
21
|
-
.prepare(`SELECT plan_phase FROM
|
|
25
|
+
.prepare(`SELECT plan_phase FROM session_rows WHERE id = ?`)
|
|
22
26
|
.get(id) as Pick<Session, "plan_phase"> | undefined;
|
|
23
27
|
if (session?.plan_phase !== "awaiting")
|
|
24
28
|
return NextResponse.json(
|
|
25
29
|
{ error: "That task isn't waiting for plan approval" },
|
|
26
30
|
{ status: 409 }
|
|
27
31
|
);
|
|
28
|
-
db
|
|
29
|
-
JSON.stringify({ merge, drop }),
|
|
30
|
-
id
|
|
31
|
-
);
|
|
32
|
+
setPlan(db, id, { plan_edits: JSON.stringify({ merge, drop }) });
|
|
32
33
|
notifyStatusChanged();
|
|
33
34
|
return NextResponse.json({ success: true, merge, drop });
|
|
34
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
2
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { setPriority } from "@/lib/tasks/queue";
|
|
3
4
|
import { isPriority } from "@/lib/tasks/schedule-types";
|
|
4
5
|
|
|
@@ -9,6 +10,8 @@ export async function PATCH(
|
|
|
9
10
|
{ params }: { params: Promise<{ id: string }> }
|
|
10
11
|
) {
|
|
11
12
|
const { id } = await params;
|
|
13
|
+
const refused = refuse(request as NextRequest, id, "open");
|
|
14
|
+
if (refused) return refused;
|
|
12
15
|
const body = (await request.json().catch(() => null)) as {
|
|
13
16
|
priority?: unknown;
|
|
14
17
|
} | null;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { refuse } from "@/lib/tasks/contract/authorize";
|
|
1
2
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
3
|
import { resumeHere } from "@/lib/tasks/move-recover";
|
|
3
|
-
import { callingDevice } from "@/lib/security/device-me";
|
|
4
|
-
import { heldRefusal } from "@/lib/tasks/held";
|
|
5
4
|
|
|
6
5
|
// POST /api/tasks/:id/resume {force?} - start its agent again here, on its
|
|
7
6
|
// conversation. A task left moving asks its target first unless forced.
|
|
@@ -10,8 +9,8 @@ export async function POST(
|
|
|
10
9
|
{ params }: { params: Promise<{ id: string }> }
|
|
11
10
|
) {
|
|
12
11
|
const id = (await params).id;
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
12
|
+
const refused = refuse(request, id, "open");
|
|
13
|
+
if (refused) return refused;
|
|
15
14
|
const { force } = await request.json().catch(() => ({}));
|
|
16
15
|
try {
|
|
17
16
|
const result = await resumeHere(id, force === true);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest } from "next/server";
|
|
2
2
|
import { stopSession } from "@/lib/sessions/stop";
|
|
3
|
-
import {
|
|
3
|
+
import { taskRoute } from "@/lib/tasks/contract/authorize";
|
|
4
4
|
|
|
5
5
|
// POST /api/tasks/:id/stop - its orchestrator stops the agent; the
|
|
6
6
|
// conversation and worktree are kept.
|
|
@@ -8,7 +8,7 @@ export async function POST(
|
|
|
8
8
|
request: NextRequest,
|
|
9
9
|
ctx: { params: Promise<{ id: string }> }
|
|
10
10
|
) {
|
|
11
|
-
return
|
|
11
|
+
return taskRoute(request, ctx, "holder", async (task) => ({
|
|
12
12
|
text: await stopSession(task),
|
|
13
13
|
}));
|
|
14
14
|
}
|
package/app/api/tasks/route.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|
|
3
3
|
import { createTask, listTasks, movedTasks } from "@/lib/tasks";
|
|
4
4
|
import { stampPriority, startOrQueue } from "@/lib/tasks/queue";
|
|
5
5
|
import { isPriority } from "@/lib/tasks/schedule-types";
|
|
6
|
-
import { TASK_CAPABILITIES } from "@/lib/tasks/
|
|
6
|
+
import { TASK_CAPABILITIES } from "@/lib/tasks/contract/types";
|
|
7
7
|
import { ensureProject, type ProjectRef } from "@/lib/tasks/project-ref";
|
|
8
8
|
import { callingDevice } from "@/lib/security/device-me";
|
|
9
9
|
import {
|
|
@@ -46,6 +46,7 @@ export async function POST(request: NextRequest) {
|
|
|
46
46
|
plan,
|
|
47
47
|
plan_reason,
|
|
48
48
|
plan_only,
|
|
49
|
+
plan_waits,
|
|
49
50
|
priority,
|
|
50
51
|
machine,
|
|
51
52
|
orchestrated,
|
|
@@ -76,7 +77,7 @@ export async function POST(request: NextRequest) {
|
|
|
76
77
|
? model
|
|
77
78
|
: (parseOverride(model) ?? undefined),
|
|
78
79
|
plan: typeof plan === "boolean" ? plan : undefined,
|
|
79
|
-
planOnly: plan_only === true ? true : undefined,
|
|
80
|
+
planOnly: plan_only === true || plan_waits === true ? true : undefined,
|
|
80
81
|
planReason:
|
|
81
82
|
typeof plan_reason === "string" && plan_reason.trim()
|
|
82
83
|
? plan_reason.trim().slice(0, 200)
|
|
@@ -107,6 +108,8 @@ export async function POST(request: NextRequest) {
|
|
|
107
108
|
id,
|
|
108
109
|
...opts,
|
|
109
110
|
held,
|
|
111
|
+
// Another machine's plan stops for it to decide what follows.
|
|
112
|
+
planWaits: plan_waits === true && !!device ? true : undefined,
|
|
110
113
|
startedVia: "remote",
|
|
111
114
|
startedByDevice: device ?? undefined,
|
|
112
115
|
});
|
package/app/globals.css
CHANGED
|
@@ -110,6 +110,34 @@
|
|
|
110
110
|
0 0 0 4px hsl(var(--primary) / 0.15);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/* One look for every link in the chat feed: it reacts to hover, keyboard focus
|
|
114
|
+
and touch (:active), since a phone has no hover. */
|
|
115
|
+
@utility chat-link {
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
overflow-wrap: anywhere;
|
|
118
|
+
border-radius: 0.125rem;
|
|
119
|
+
text-decoration-line: underline;
|
|
120
|
+
text-decoration-thickness: 1px;
|
|
121
|
+
text-underline-offset: 2px;
|
|
122
|
+
transition:
|
|
123
|
+
color 120ms,
|
|
124
|
+
background-color 120ms,
|
|
125
|
+
text-decoration-thickness 120ms;
|
|
126
|
+
&:hover {
|
|
127
|
+
color: hsl(var(--primary));
|
|
128
|
+
background-color: hsl(var(--primary) / 0.1);
|
|
129
|
+
text-decoration-thickness: 2px;
|
|
130
|
+
}
|
|
131
|
+
&:active {
|
|
132
|
+
color: hsl(var(--primary));
|
|
133
|
+
background-color: hsl(var(--primary) / 0.2);
|
|
134
|
+
}
|
|
135
|
+
&:focus-visible {
|
|
136
|
+
outline: 2px solid hsl(var(--ring));
|
|
137
|
+
outline-offset: 2px;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
113
141
|
@utility label-mono {
|
|
114
142
|
font-family: var(--font-geist-mono);
|
|
115
143
|
font-size: 0.6875rem;
|
|
@@ -62,7 +62,7 @@ import { NeedsYouStrip } from "@/components/Orchestrator/NeedsYouStrip";
|
|
|
62
62
|
import { useResolveQuestion } from "@/data/orchestrators";
|
|
63
63
|
import { needsYouFocus, needsYouFocusActions } from "@/stores/needsYouFocus";
|
|
64
64
|
import { hrefWithSession } from "@/lib/session-url";
|
|
65
|
-
import {
|
|
65
|
+
import { questionSource } from "@/lib/chat/turns";
|
|
66
66
|
import { useViewport } from "@/hooks/useViewport";
|
|
67
67
|
import { useChatCommands } from "./useChatCommands";
|
|
68
68
|
import { copySelection } from "./copy-selection";
|
|
@@ -751,16 +751,7 @@ export function ChatPanel({
|
|
|
751
751
|
)
|
|
752
752
|
);
|
|
753
753
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
754
|
-
} else
|
|
755
|
-
goTo((all) => {
|
|
756
|
-
const asked = all.findLast(
|
|
757
|
-
(i) =>
|
|
758
|
-
isQuestionTool(i) &&
|
|
759
|
-
(i as { input?: { text?: string } }).input?.text ===
|
|
760
|
-
item.question.text
|
|
761
|
-
);
|
|
762
|
-
return asked?.id ?? null;
|
|
763
|
-
});
|
|
754
|
+
} else goTo((all) => questionSource(all, item.question.text));
|
|
764
755
|
}}
|
|
765
756
|
onReply={(item) => {
|
|
766
757
|
setReplyTo({ id: item.question.id, text: item.question.text });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import { ChevronRight, Loader2
|
|
4
|
+
import { ChevronRight, Loader2 } from "lucide-react";
|
|
5
5
|
import type { ChatItem } from "@/lib/chat/events";
|
|
6
6
|
import type { EventTurn } from "@/lib/chat/turns";
|
|
7
7
|
import { cn } from "@/lib/utils";
|
|
@@ -60,12 +60,6 @@ export function EventTurnRow({
|
|
|
60
60
|
<span className="text-foreground/80 max-w-[40%] min-w-0 shrink-0 truncate font-medium">
|
|
61
61
|
{turn.task}
|
|
62
62
|
</span>
|
|
63
|
-
{turn.hasQuestion && (
|
|
64
|
-
<MessageCircleQuestion
|
|
65
|
-
className="h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400"
|
|
66
|
-
aria-label="Asked a question"
|
|
67
|
-
/>
|
|
68
|
-
)}
|
|
69
63
|
<span className="min-w-0 flex-1 truncate">{turn.summary}</span>
|
|
70
64
|
{turn.live && (
|
|
71
65
|
<Loader2
|
|
@@ -42,7 +42,7 @@ function UserLink({ href }: { href: string }) {
|
|
|
42
42
|
target={id ? undefined : "_blank"}
|
|
43
43
|
rel={id ? undefined : "noopener noreferrer"}
|
|
44
44
|
onClick={id ? (e) => openSessionLink(e, id) : undefined}
|
|
45
|
-
className="font-medium
|
|
45
|
+
className="chat-link font-medium"
|
|
46
46
|
>
|
|
47
47
|
{href}
|
|
48
48
|
</a>
|
package/lib/approvals/enable.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// brings what already exists in line with it.
|
|
3
3
|
|
|
4
4
|
import { db } from "../db";
|
|
5
|
+
import { isRemoteHost } from "../hosts";
|
|
5
6
|
import { reapplyChatModes } from "../chat/settings";
|
|
6
7
|
import { approvePlanNow } from "../models/plan-step";
|
|
7
8
|
import { autoApproveAsk, type AskRow } from "../orchestrator/asks";
|
|
@@ -28,10 +29,16 @@ async function sweep(): Promise<void> {
|
|
|
28
29
|
}
|
|
29
30
|
const plans = db
|
|
30
31
|
.prepare(
|
|
31
|
-
`SELECT id FROM
|
|
32
|
+
`SELECT id, host_id, plan_waits_for_caller FROM session_rows WHERE plan_phase = 'awaiting' AND task_status = 'running'`
|
|
32
33
|
)
|
|
33
|
-
.all() as {
|
|
34
|
+
.all() as {
|
|
35
|
+
id: string;
|
|
36
|
+
host_id: string;
|
|
37
|
+
plan_waits_for_caller: number | null;
|
|
38
|
+
}[];
|
|
34
39
|
for (const p of plans) {
|
|
40
|
+
// A plan placed here by another machine is that machine's to decide.
|
|
41
|
+
if (p.plan_waits_for_caller && !isRemoteHost(p.host_id)) continue;
|
|
35
42
|
try {
|
|
36
43
|
await approvePlanNow(p.id, undefined, "no-approvals");
|
|
37
44
|
} catch (e) {
|