@saadnvd1/agent-os 0.3.533 → 0.3.534
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -2
- package/app/api/features/pieces/[id]/release/route.ts +16 -0
- package/app/api/features/route.ts +8 -0
- package/app/api/recipes/[id]/carry/clear/route.ts +20 -0
- package/app/api/recipes/[id]/preview/route.ts +19 -0
- package/app/api/recipes/[id]/route.ts +51 -0
- package/app/api/recipes/[id]/run/route.ts +35 -0
- package/app/api/recipes/[id]/runs/route.ts +23 -0
- package/app/api/recipes/draft/route.ts +14 -0
- package/app/api/recipes/route.ts +35 -0
- package/app/api/tasks/[id]/plan-edits/route.ts +34 -0
- package/components/CommandPalette/useAppCommands.ts +20 -0
- package/components/Features/FeaturesSection.tsx +100 -0
- package/components/Features/index.ts +1 -0
- package/components/Recipes/RecipeForm.tsx +251 -0
- package/components/Recipes/RecipePage.tsx +127 -0
- package/components/Recipes/RecipesPanel.tsx +225 -0
- package/components/Recipes/RunChip.tsx +16 -0
- package/components/Recipes/RunHistory.tsx +88 -0
- package/components/Recipes/RunSheet.tsx +84 -0
- package/components/Recipes/TimerFields.tsx +193 -0
- package/components/Recipes/format.ts +51 -0
- package/components/Recipes/index.ts +1 -0
- package/components/Schedules/SchedulesPanel.tsx +1 -0
- package/components/SessionList/RowSubtitle.tsx +16 -0
- package/components/SessionList/SessionRowMenu.tsx +29 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +20 -4
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Tasks/PlanReview.tsx +253 -0
- package/components/Tasks/TaskRow.tsx +19 -44
- package/components/Tasks/TasksDialog.tsx +2 -0
- package/data/features/index.ts +40 -0
- package/data/push/topics.ts +8 -0
- package/data/recipes/index.ts +129 -0
- package/data/tasks/index.ts +14 -0
- package/lib/away/run.ts +4 -1
- package/lib/db/changes.ts +8 -0
- package/lib/db/migrations.ts +127 -0
- package/lib/db/queries.ts +4 -1
- package/lib/db/types.ts +6 -0
- package/lib/models/plan-step.ts +67 -6
- package/lib/notify/copy.ts +28 -10
- package/lib/notify/push/events.ts +36 -2
- package/lib/notify/push/policy.ts +2 -0
- package/lib/notify/push-types.ts +7 -3
- package/lib/orchestrator/brief.ts +13 -0
- package/lib/orchestrator/conditions.ts +3 -1
- package/lib/orchestrator/deliver.ts +2 -0
- package/lib/orchestrator/digest.ts +27 -1
- package/lib/orchestrator/plan-pieces.ts +41 -0
- package/lib/orchestrator/recipe-tools.ts +159 -0
- package/lib/orchestrator/review.ts +2 -1
- package/lib/orchestrator/serve.ts +25 -0
- package/lib/orchestrator/tool-names.ts +5 -0
- package/lib/orchestrator/tool-schemas.ts +54 -0
- package/lib/orchestrator/tools.ts +10 -0
- package/lib/plans/features.ts +200 -0
- package/lib/plans/pieces.ts +366 -0
- package/lib/plans/release.ts +323 -0
- package/lib/plans/view.ts +85 -0
- package/lib/recipes/api.ts +19 -0
- package/lib/recipes/chip.ts +40 -0
- package/lib/recipes/compose.ts +78 -0
- package/lib/recipes/draft.ts +93 -0
- package/lib/recipes/index.ts +54 -0
- package/lib/recipes/result.ts +59 -0
- package/lib/recipes/run.ts +202 -0
- package/lib/recipes/settle.ts +465 -0
- package/lib/recipes/store.ts +378 -0
- package/lib/recipes/test-fixtures.ts +49 -0
- package/lib/recipes/timer.ts +107 -0
- package/lib/recipes/types.ts +116 -0
- package/lib/schedules/index.ts +4 -1
- package/lib/schedules/start.ts +33 -9
- package/lib/schedules/store.ts +6 -2
- package/lib/security/demo.ts +1 -0
- package/lib/settings-url.ts +1 -0
- package/lib/tasks/loop-types.ts +23 -0
- package/lib/tasks/plan-view.ts +38 -1
- package/package.json +1 -1
- package/server.ts +2 -0
- package/stores/recipesUi.ts +53 -0
package/lib/schedules/start.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { notifyStatusChanged } from "../status/hub";
|
|
|
17
17
|
import { formatRunTime } from "./cron";
|
|
18
18
|
import { Braked, fromLabel, type RunDeps, type Started } from "./run";
|
|
19
19
|
import { braked, BrakeRefused } from "../orchestrator/brakes";
|
|
20
|
+
import { startRun } from "../recipes/run";
|
|
21
|
+
import { hasLiveRun } from "../recipes/store";
|
|
20
22
|
import { startedSessions, type Schedule, type ScheduleRun } from "./store";
|
|
21
23
|
|
|
22
24
|
function projectOf(schedule: Schedule) {
|
|
@@ -220,6 +222,8 @@ export async function stillRunning(
|
|
|
220
222
|
return schedule.target_session_id
|
|
221
223
|
? sessionBusy(schedule.target_session_id)
|
|
222
224
|
: false;
|
|
225
|
+
case "recipe":
|
|
226
|
+
return schedule.recipe_id ? hasLiveRun(schedule.recipe_id) : false;
|
|
223
227
|
case "orchestrator": {
|
|
224
228
|
// A message: done once it's delivered. An agent's check-in, like one
|
|
225
229
|
// to any session, waits while the orchestrator is still working.
|
|
@@ -279,17 +283,37 @@ async function throughBrakes(
|
|
|
279
283
|
}
|
|
280
284
|
}
|
|
281
285
|
|
|
286
|
+
// A recipe's timer starts a run; the recipe's own checks (one live run,
|
|
287
|
+
// brakes) decide the rest.
|
|
288
|
+
async function startRecipe(
|
|
289
|
+
schedule: Schedule,
|
|
290
|
+
onSession: OnSession,
|
|
291
|
+
trigger: "timer" | "catch-up"
|
|
292
|
+
): Promise<string> {
|
|
293
|
+
if (!schedule.recipe_id) throw new Error("Its recipe is gone");
|
|
294
|
+
try {
|
|
295
|
+
const { run } = await startRun(schedule.recipe_id, { trigger });
|
|
296
|
+
if (run.session_id) onSession(run.session_id);
|
|
297
|
+
return run.session_id ?? "";
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error instanceof BrakeRefused) throw new Braked(error.reason);
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
282
304
|
export const realDeps: RunDeps = {
|
|
283
305
|
start: (schedule, onSession) =>
|
|
284
|
-
schedule.kind === "
|
|
285
|
-
?
|
|
286
|
-
: schedule.kind === "
|
|
287
|
-
? throughBrakes(schedule, "
|
|
288
|
-
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
:
|
|
306
|
+
schedule.kind === "recipe"
|
|
307
|
+
? startRecipe(schedule, onSession, "timer")
|
|
308
|
+
: schedule.kind === "task"
|
|
309
|
+
? throughBrakes(schedule, "task", () => startTask(schedule, onSession))
|
|
310
|
+
: schedule.kind === "session"
|
|
311
|
+
? throughBrakes(schedule, "session", () =>
|
|
312
|
+
startSession(schedule, onSession)
|
|
313
|
+
)
|
|
314
|
+
: schedule.kind === "message"
|
|
315
|
+
? messageSession(schedule, onSession)
|
|
316
|
+
: postToOrchestrator(schedule, onSession),
|
|
293
317
|
stillRunning,
|
|
294
318
|
notify,
|
|
295
319
|
};
|
package/lib/schedules/store.ts
CHANGED
|
@@ -25,7 +25,9 @@ export const SCHEDULE_KINDS = [
|
|
|
25
25
|
"orchestrator",
|
|
26
26
|
"message",
|
|
27
27
|
] as const;
|
|
28
|
-
|
|
28
|
+
// "recipe" is a recipe's timer (lib/recipes/timer): it runs a recipe, and is
|
|
29
|
+
// made and edited through the recipe, never through the Schedules form.
|
|
30
|
+
export type ScheduleKind = (typeof SCHEDULE_KINDS)[number] | "recipe";
|
|
29
31
|
|
|
30
32
|
export type RunTrigger = "schedule" | "catch-up" | "manual";
|
|
31
33
|
// "claimed" only between taking a slot and starting its work.
|
|
@@ -44,6 +46,8 @@ export interface Schedule {
|
|
|
44
46
|
// A "message" schedule's session. Its id is the address; its name is
|
|
45
47
|
// only looked up to show.
|
|
46
48
|
target_session_id: string | null;
|
|
49
|
+
// A recipe timer's recipe.
|
|
50
|
+
recipe_id?: string | null;
|
|
47
51
|
// The agent session that made it (aos schedule add), or null for Saad.
|
|
48
52
|
created_by_session_id: string | null;
|
|
49
53
|
enabled: boolean;
|
|
@@ -223,7 +227,7 @@ function validate(
|
|
|
223
227
|
throw new Error("A schedule name can't use quotes or [ ]");
|
|
224
228
|
const prompt = input.prompt?.trim();
|
|
225
229
|
if (!prompt) throw new Error("Write the prompt it runs");
|
|
226
|
-
if (!SCHEDULE_KINDS.includes(input.kind))
|
|
230
|
+
if (!(SCHEDULE_KINDS as readonly string[]).includes(input.kind))
|
|
227
231
|
throw new Error("Pick task, session, orchestrator or message");
|
|
228
232
|
const timezone = input.timezone?.trim() || DEFAULT_TIMEZONE;
|
|
229
233
|
if (!isTimezone(timezone)) throw new Error(`Unknown time zone "${timezone}"`);
|
package/lib/security/demo.ts
CHANGED
package/lib/settings-url.ts
CHANGED
package/lib/tasks/loop-types.ts
CHANGED
|
@@ -149,7 +149,30 @@ export interface TaskChecks {
|
|
|
149
149
|
|
|
150
150
|
// -- A task's plan ---------------------------------------------------------
|
|
151
151
|
|
|
152
|
+
export interface PlanPieceView {
|
|
153
|
+
position: number;
|
|
154
|
+
name: string;
|
|
155
|
+
slice: string;
|
|
156
|
+
project: string;
|
|
157
|
+
afterPosition: number | null;
|
|
158
|
+
afterKind: "merged" | "deployed" | null;
|
|
159
|
+
reasons: string[];
|
|
160
|
+
owns: string[];
|
|
161
|
+
// Merging into the piece before would break why it was split (a).
|
|
162
|
+
forced: boolean;
|
|
163
|
+
// Saad's pending edit, applied at approval.
|
|
164
|
+
edit: "merge" | "drop" | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface PlanPiecesView {
|
|
168
|
+
pieces: PlanPieceView[];
|
|
169
|
+
folded: { name: string; into: number; why: string }[];
|
|
170
|
+
warnings: string[];
|
|
171
|
+
}
|
|
172
|
+
|
|
152
173
|
export interface TaskPlan {
|
|
174
|
+
// Set once a plan waits for approval: how the checker split it.
|
|
175
|
+
pieces?: PlanPiecesView | null;
|
|
153
176
|
// As the starter decided (null reason when unstated).
|
|
154
177
|
planned: boolean;
|
|
155
178
|
reason: string | null;
|
package/lib/tasks/plan-view.ts
CHANGED
|
@@ -9,7 +9,13 @@ import fs from "fs";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { db, type Session } from "../db";
|
|
11
11
|
import { PLAN_FILE, planSummary } from "../models/plan-step";
|
|
12
|
-
import {
|
|
12
|
+
import { planFor } from "../plans/features";
|
|
13
|
+
import { parseEdits } from "../plans/pieces";
|
|
14
|
+
import {
|
|
15
|
+
PLAN_TEXT_MAX,
|
|
16
|
+
type PlanPiecesView,
|
|
17
|
+
type TaskPlan,
|
|
18
|
+
} from "./loop-types";
|
|
13
19
|
import { expandHome } from "./session";
|
|
14
20
|
|
|
15
21
|
const TITLE_MAX = 120;
|
|
@@ -61,6 +67,36 @@ function capped(text: string) {
|
|
|
61
67
|
return { text: over ? text.slice(0, PLAN_TEXT_MAX) : text, truncated: over };
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
const PIECES_READ_MAX = 400_000;
|
|
71
|
+
|
|
72
|
+
// How the plan splits, read from the whole file (the block is at its end).
|
|
73
|
+
function piecesView(session: Session, file: string): PlanPiecesView | null {
|
|
74
|
+
const text = readHead(file, PIECES_READ_MAX);
|
|
75
|
+
const result = text ? planFor(session, text) : null;
|
|
76
|
+
if (!result) return null;
|
|
77
|
+
const edits = parseEdits(session.plan_edits);
|
|
78
|
+
return {
|
|
79
|
+
pieces: result.pieces.map((p) => ({
|
|
80
|
+
position: p.position,
|
|
81
|
+
name: p.name,
|
|
82
|
+
slice: p.slice,
|
|
83
|
+
project: p.project.name,
|
|
84
|
+
afterPosition: p.afterPosition,
|
|
85
|
+
afterKind: p.afterKind,
|
|
86
|
+
reasons: p.reasons,
|
|
87
|
+
owns: p.owns,
|
|
88
|
+
forced: p.reasons.includes("a"),
|
|
89
|
+
edit: edits.drop?.includes(p.position)
|
|
90
|
+
? "drop"
|
|
91
|
+
: edits.merge?.includes(p.position)
|
|
92
|
+
? "merge"
|
|
93
|
+
: null,
|
|
94
|
+
})),
|
|
95
|
+
folded: result.folded,
|
|
96
|
+
warnings: result.warnings,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
64
100
|
export function taskPlan(session: Session): TaskPlan | null {
|
|
65
101
|
const decided = session.plan_decided ?? null;
|
|
66
102
|
const phase = session.plan_phase ?? null;
|
|
@@ -111,6 +147,7 @@ export function taskPlan(session: Session): TaskPlan | null {
|
|
|
111
147
|
source: "step",
|
|
112
148
|
title: planTitle(body),
|
|
113
149
|
path: text !== null ? PLAN_FILE : null,
|
|
150
|
+
pieces: phase === "awaiting" ? piecesView(session, file) : null,
|
|
114
151
|
...capped(body),
|
|
115
152
|
};
|
|
116
153
|
}
|
package/package.json
CHANGED
package/server.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { stackStartGate } from "./lib/orchestrator/brakes";
|
|
|
13
13
|
import { buildId } from "./lib/build";
|
|
14
14
|
import { startOrchestratorWatcher } from "./lib/orchestrator/watcher";
|
|
15
15
|
import { realDeps, schedulesEnabled, startScheduler } from "./lib/schedules";
|
|
16
|
+
import { startSettling } from "./lib/recipes";
|
|
16
17
|
import { startUsageRollup, usageRollupEnabled } from "./lib/usage/nightly";
|
|
17
18
|
import { resumePhoneOutbox } from "./lib/notify";
|
|
18
19
|
import { resumePushOutbox } from "./lib/notify/push";
|
|
@@ -411,6 +412,7 @@ app.prepare().then(async () => {
|
|
|
411
412
|
if (process.env.AGENTOS_ORCHESTRATOR !== "off") startOrchestratorWatcher();
|
|
412
413
|
// Schedules tick here, once a minute, and nowhere else.
|
|
413
414
|
if (schedulesEnabled(process.env)) startScheduler(realDeps);
|
|
415
|
+
startSettling();
|
|
414
416
|
resumePhoneOutbox();
|
|
415
417
|
resumePushOutbox();
|
|
416
418
|
// The usage ledger's daily rollup and its ratchet alerts.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
import { settingsUiActions } from "./settingsUi";
|
|
3
|
+
|
|
4
|
+
// Settings > Recipes: one workspace's, or all of them. A recipe opened from
|
|
5
|
+
// elsewhere (a session's menu, the palette), or a finished session to save
|
|
6
|
+
// as one, lands here.
|
|
7
|
+
export const recipesUi = proxy<{
|
|
8
|
+
workspaceId: string | null;
|
|
9
|
+
recipeId: string | null;
|
|
10
|
+
fromSessionId: string | null;
|
|
11
|
+
runRecipeId: string | null;
|
|
12
|
+
}>({
|
|
13
|
+
workspaceId: null,
|
|
14
|
+
recipeId: null,
|
|
15
|
+
fromSessionId: null,
|
|
16
|
+
runRecipeId: null,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const recipesUiActions = {
|
|
20
|
+
open: (workspaceId: string | null = null) => {
|
|
21
|
+
Object.assign(recipesUi, {
|
|
22
|
+
workspaceId,
|
|
23
|
+
recipeId: null,
|
|
24
|
+
fromSessionId: null,
|
|
25
|
+
runRecipeId: null,
|
|
26
|
+
});
|
|
27
|
+
settingsUiActions.open("recipes");
|
|
28
|
+
},
|
|
29
|
+
openRecipe: (recipeId: string, run = false) => {
|
|
30
|
+
Object.assign(recipesUi, {
|
|
31
|
+
recipeId,
|
|
32
|
+
fromSessionId: null,
|
|
33
|
+
runRecipeId: run ? recipeId : null,
|
|
34
|
+
});
|
|
35
|
+
settingsUiActions.open("recipes");
|
|
36
|
+
},
|
|
37
|
+
saveSessionAs: (sessionId: string, workspaceId: string | null = null) => {
|
|
38
|
+
Object.assign(recipesUi, {
|
|
39
|
+
workspaceId,
|
|
40
|
+
recipeId: null,
|
|
41
|
+
fromSessionId: sessionId,
|
|
42
|
+
runRecipeId: null,
|
|
43
|
+
});
|
|
44
|
+
settingsUiActions.open("recipes");
|
|
45
|
+
},
|
|
46
|
+
clear: () => {
|
|
47
|
+
Object.assign(recipesUi, {
|
|
48
|
+
recipeId: null,
|
|
49
|
+
fromSessionId: null,
|
|
50
|
+
runRecipeId: null,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|