@saadnvd1/agent-os 0.3.715 → 0.3.728
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 +2 -0
- package/app/api/agent-roles/route.test.ts +67 -0
- package/app/api/agent-roles/route.ts +61 -0
- package/app/api/analyst/[id]/recommendations/[recId]/again/route.ts +15 -0
- package/app/api/analyst/[id]/recommendations/[recId]/dismiss/route.ts +13 -0
- package/app/api/analyst/[id]/recommendations/[recId]/send/route.ts +13 -0
- package/app/api/analyst/[id]/review/route.ts +22 -0
- package/app/api/analyst/[id]/route.ts +44 -0
- package/app/api/analyst/route.ts +10 -0
- package/app/api/bus/spawn/route.ts +22 -1
- package/app/api/loops/route.ts +17 -0
- package/app/api/models/availability/route.ts +9 -0
- package/app/api/models/choices/route.ts +9 -0
- package/app/api/sessions/[id]/analyst/route.ts +11 -0
- package/app/api/sessions/[id]/crew/route.ts +38 -0
- package/app/api/sessions/[id]/export/estimate/route.ts +23 -0
- package/app/api/sessions/[id]/export/route.test.ts +125 -0
- package/app/api/sessions/[id]/export/route.ts +34 -0
- package/app/api/sessions/[id]/model/route.ts +49 -0
- package/app/api/sessions/route.ts +16 -0
- package/app/api/tasks/[id]/loop/cycles/[n]/log/route.ts +29 -0
- package/app/api/tasks/[id]/loop/draft-criteria/route.ts +17 -0
- package/app/api/tasks/[id]/loop/next/route.ts +25 -0
- package/app/api/tasks/[id]/loop/route.ts +72 -0
- package/app/api/tasks/[id]/loop/run/route.ts +12 -0
- package/app/api/tasks/[id]/loop/stop/route.ts +12 -0
- package/app/api/tasks/[id]/mode/route.ts +29 -0
- package/app/api/tasks/[id]/report/draft/route.ts +54 -0
- package/app/api/tasks/[id]/report/evidence/[evId]/route.ts +18 -0
- package/app/api/tasks/[id]/report/route.ts +39 -0
- package/app/api/workspaces/[id]/mode/route.ts +23 -0
- package/app/api/workspaces/[id]/reports/route.ts +17 -0
- package/app/page.tsx +13 -0
- package/components/Analyst/PairChip.tsx +131 -0
- package/components/Analyst/PairSwitch.tsx +76 -0
- package/components/Analyst/ReviewCard.tsx +272 -0
- package/components/Analyst/SignalChips.tsx +39 -0
- package/components/Analyst/StartAnalystSheet.tsx +109 -0
- package/components/Analyst/cardState.test.ts +109 -0
- package/components/Analyst/cardState.ts +95 -0
- package/components/Analyst/pair.ts +81 -0
- package/components/Chat/Code.tsx +3 -0
- package/components/Chat/Messages.tsx +110 -11
- package/components/Chat/itemContext.ts +14 -0
- package/components/Draft/DraftChips.tsx +16 -1
- package/components/Draft/DraftPanel.tsx +58 -2
- package/components/Draft/ModelUnavailableDialog.tsx +78 -0
- package/components/Draft/RoleChips.tsx +110 -0
- package/components/Export/ExportActions.tsx +136 -0
- package/components/Export/ExportSheet.tsx +217 -0
- package/components/Export/RangePicker.tsx +144 -0
- package/components/Export/Seg.tsx +38 -0
- package/components/Export/TurnSelectBar.tsx +45 -0
- package/components/Models/AgentRoleCard.tsx +114 -0
- package/components/Models/AgentRolesSection.tsx +132 -0
- package/components/Models/ModelBadges.tsx +48 -0
- package/components/Models/ModelsSection.tsx +4 -2
- package/components/Models/RoleModelSelect.tsx +87 -0
- package/components/Pane/DesktopTabBar.tsx +8 -0
- package/components/Pane/MobileTabBar.tsx +29 -0
- package/components/Pane/MobileViewPicker.tsx +4 -0
- package/components/Pane/PaneMenu.test.ts +21 -0
- package/components/Pane/PaneMenu.tsx +26 -0
- package/components/Pane/PaneViewToggle.tsx +12 -0
- package/components/Pane/index.tsx +19 -0
- package/components/Report/DraftCard.tsx +89 -0
- package/components/Report/EstimateBar.tsx +51 -0
- package/components/Report/EvidenceSheet.tsx +98 -0
- package/components/Report/ModeChooser.tsx +43 -0
- package/components/Report/ReportView.tsx +289 -0
- package/components/SessionList/SessionRow.tsx +18 -7
- package/components/SessionList/SessionRowMenu.tsx +12 -0
- package/components/Sessions/RolePill.tsx +203 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Share/ShareDialog.tsx +13 -0
- package/components/Tasks/Loop/LoopButton.tsx +74 -0
- package/components/Tasks/Loop/LoopSetup.tsx +146 -0
- package/components/Tasks/Loop/LoopView.tsx +350 -0
- package/components/Tasks/Loop/labels.ts +17 -0
- package/components/Tasks/TaskRow.tsx +2 -0
- package/components/Workspaces/WorkspaceModeForm.tsx +22 -0
- package/components/Workspaces/WorkspaceSettings.tsx +2 -0
- package/components/ui/segmented.tsx +55 -0
- package/components/ui/status-pill.tsx +3 -0
- package/contexts/PaneContext.tsx +21 -1
- package/data/analyst/index.ts +3 -0
- package/data/analyst/keys.ts +6 -0
- package/data/analyst/mutations.ts +116 -0
- package/data/analyst/queries.ts +35 -0
- package/data/exports/index.ts +2 -0
- package/data/exports/keys.ts +3 -0
- package/data/exports/queries.ts +51 -0
- package/data/loops/index.ts +3 -0
- package/data/loops/keys.ts +4 -0
- package/data/loops/mutations.ts +68 -0
- package/data/loops/queries.ts +40 -0
- package/data/models/index.ts +98 -0
- package/data/push/topics.ts +11 -0
- package/data/reports/index.ts +115 -0
- package/data/sessions/queries.ts +26 -0
- package/lib/agents/brief.ts +1 -0
- package/lib/analyst/__fixtures__/items.ts +57 -0
- package/lib/analyst/__fixtures__/mocks.ts +49 -0
- package/lib/analyst/brief.ts +17 -0
- package/lib/analyst/buildDigest.ts +204 -0
- package/lib/analyst/createLink.ts +98 -0
- package/lib/analyst/endLink.ts +31 -0
- package/lib/analyst/forward.ts +109 -0
- package/lib/analyst/index.ts +16 -0
- package/lib/analyst/linkView.ts +38 -0
- package/lib/analyst/onTurnEnd.ts +119 -0
- package/lib/analyst/parseReview.ts +41 -0
- package/lib/analyst/pushDigest.ts +143 -0
- package/lib/analyst/resume.ts +35 -0
- package/lib/analyst/route.ts +50 -0
- package/lib/analyst/signals.ts +121 -0
- package/lib/analyst/store.ts +303 -0
- package/lib/analyst/types.ts +140 -0
- package/lib/bus/condense.ts +5 -1
- package/lib/bus/index.ts +5 -0
- package/lib/bus/read-scope.ts +12 -0
- package/lib/bus/role-alias.ts +70 -0
- package/lib/bus/targets.ts +3 -0
- package/lib/chat/drivers/opencode-mapper.ts +55 -1
- package/lib/chat/drivers/pi-approvals.ts +7 -4
- package/lib/chat/drivers/pi-mapper.ts +38 -0
- package/lib/chat/drivers/pi-text-tools.ts +104 -0
- package/lib/chat/drivers/pi.ts +8 -3
- package/lib/chat/drivers/turn-items.ts +36 -11
- package/lib/chat/drivers/turn-outcome.ts +108 -0
- package/lib/chat/events.ts +8 -0
- package/lib/chat/registry.ts +13 -0
- package/lib/chat/store.ts +19 -0
- package/lib/chat/turns.ts +1 -0
- package/lib/chat/worker/extras.ts +4 -2
- package/lib/claims/collectLocal.ts +3 -1
- package/lib/db/changes.ts +13 -0
- package/lib/db/migrations/20261021T1400_session_agent_roles.ts +20 -0
- package/lib/db/migrations/20261021T1430_analyst_links.ts +52 -0
- package/lib/db/migrations/20261021T1600_task_loops.ts +81 -0
- package/lib/db/migrations/20261021T1800_health_reports.ts +38 -0
- package/lib/db/session-parts/columns.ts +2 -0
- package/lib/db/types.ts +3 -0
- package/lib/done/archive.ts +2 -0
- package/lib/done/index.ts +2 -0
- package/lib/drafts.ts +6 -0
- package/lib/export/build.ts +470 -0
- package/lib/export/compact.ts +156 -0
- package/lib/export/estimate.ts +83 -0
- package/lib/export/fixtures.ts +45 -0
- package/lib/export/html.tsx +195 -0
- package/lib/export/json.ts +39 -0
- package/lib/export/load.ts +64 -0
- package/lib/export/markdown.ts +127 -0
- package/lib/export/request.ts +36 -0
- package/lib/export/service.ts +142 -0
- package/lib/export/summary.ts +86 -0
- package/lib/export/turns.ts +63 -0
- package/lib/export/view.ts +173 -0
- package/lib/loops/analyse.ts +46 -0
- package/lib/loops/analyst-prompt.ts +203 -0
- package/lib/loops/checkCriteria.ts +114 -0
- package/lib/loops/compactEvidence.ts +140 -0
- package/lib/loops/controls.ts +317 -0
- package/lib/loops/decideStop.ts +113 -0
- package/lib/loops/deps.ts +128 -0
- package/lib/loops/diffMap.ts +39 -0
- package/lib/loops/draftCriteria.ts +71 -0
- package/lib/loops/gatherEvidence.ts +265 -0
- package/lib/loops/hooks.ts +40 -0
- package/lib/loops/index.ts +7 -0
- package/lib/loops/route.ts +83 -0
- package/lib/loops/runCycle.ts +323 -0
- package/lib/loops/screenInstruction.ts +59 -0
- package/lib/loops/sendInstruction.ts +83 -0
- package/lib/loops/snapshot.ts +38 -0
- package/lib/loops/stopLoop.ts +86 -0
- package/lib/loops/store.ts +481 -0
- package/lib/loops/summary.ts +27 -0
- package/lib/loops/tickLoops.ts +141 -0
- package/lib/loops/types.ts +223 -0
- package/lib/loops/world.test-helpers.ts +223 -0
- package/lib/models/agent-binding.ts +102 -0
- package/lib/models/agent-roles-view.ts +157 -0
- package/lib/models/agent-roles.ts +87 -0
- package/lib/models/availability-refs.ts +17 -0
- package/lib/models/availability.ts +91 -0
- package/lib/models/catalog.ts +156 -0
- package/lib/models/config.ts +27 -0
- package/lib/models/locality.ts +33 -0
- package/lib/models/plan-step.ts +4 -1
- package/lib/models/providers.ts +11 -0
- package/lib/models/resolve.ts +50 -12
- package/lib/models/roles.ts +12 -0
- package/lib/needs-you.ts +1 -1
- package/lib/orchestrator/digest.ts +2 -1
- package/lib/orchestrator/facts.ts +7 -2
- package/lib/orchestrator/loop-guard.ts +5 -2
- package/lib/orchestrator/pr-wake.ts +4 -0
- package/lib/orchestrator/read.ts +5 -1
- package/lib/orchestrator/untrusted.ts +5 -0
- package/lib/orchestrator/watcher.ts +8 -1
- package/lib/panes.ts +52 -0
- package/lib/plans/acceptance.ts +23 -0
- package/lib/projects.ts +1 -0
- package/lib/reports/drafts.ts +87 -0
- package/lib/reports/end-hook.ts +17 -0
- package/lib/reports/estimate.ts +107 -0
- package/lib/reports/evidence/build.ts +193 -0
- package/lib/reports/evidence/gather.ts +229 -0
- package/lib/reports/fixtures.ts +17 -0
- package/lib/reports/generate.ts +159 -0
- package/lib/reports/hooks.ts +30 -0
- package/lib/reports/mode.ts +65 -0
- package/lib/reports/model.ts +115 -0
- package/lib/reports/read.ts +26 -0
- package/lib/reports/store.ts +180 -0
- package/lib/reports/template.ts +117 -0
- package/lib/reports/types.ts +135 -0
- package/lib/reports/validate.ts +142 -0
- package/lib/session-artifacts/agentos-files.ts +94 -0
- package/lib/session-artifacts/collect.ts +17 -0
- package/lib/sessions/crew.ts +72 -0
- package/lib/sessions/launch.ts +49 -9
- package/lib/settings-url.ts +1 -0
- package/lib/share/markdown.ts +2 -2
- package/lib/share/redact.ts +16 -10
- package/lib/share/render.tsx +1 -1
- package/lib/share/snapshot.ts +2 -2
- package/lib/sidebar/row-menu.ts +15 -1
- package/lib/tasks/contract/routes.ts +5 -0
- package/lib/tasks/contract/types.ts +3 -0
- package/lib/tasks/finish.ts +2 -0
- package/lib/tasks/index.ts +6 -0
- package/lib/tasks/move-carry.ts +1 -0
- package/lib/worktree-deps/caches.ts +3 -2
- package/lib/worktree-deps/clone.ts +3 -1
- package/lib/worktree-deps/paths.ts +26 -0
- package/package.json +2 -2
- package/server.ts +9 -1
- package/stores/analystUi.ts +19 -0
- package/stores/exportUi.ts +87 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ChatItem,
|
|
4
|
+
DriverEvent,
|
|
5
|
+
FileDiff,
|
|
6
|
+
ToolStatus,
|
|
7
|
+
TurnEndReason,
|
|
8
|
+
} from "../events";
|
|
3
9
|
import { clipOutput } from "../tools";
|
|
4
10
|
|
|
5
11
|
type Tool = Extract<ChatItem, { kind: "tool" }>;
|
|
@@ -41,6 +47,8 @@ export function readableError(text: string): string {
|
|
|
41
47
|
export class TurnItems {
|
|
42
48
|
private texts = new Map<string, Text>();
|
|
43
49
|
private tools = new Map<string, Tool>();
|
|
50
|
+
// Texts that finished in this turn, so one can be corrected afterwards.
|
|
51
|
+
private done = new Map<string, Text>();
|
|
44
52
|
private startedAt = 0;
|
|
45
53
|
inTurn = false;
|
|
46
54
|
|
|
@@ -81,15 +89,23 @@ export class TurnItems {
|
|
|
81
89
|
this.texts.delete(key);
|
|
82
90
|
const text = full ?? current?.text ?? "";
|
|
83
91
|
if (!text.trim()) return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
const item: Text = {
|
|
93
|
+
id: current?.id ?? randomUUID(),
|
|
94
|
+
kind,
|
|
95
|
+
text,
|
|
96
|
+
createdAt: current?.createdAt ?? Date.now(),
|
|
97
|
+
};
|
|
98
|
+
this.done.set(key, item);
|
|
99
|
+
this.emit({ type: "item", item });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A finished text replaced by `text` (blank hides it): the agent's final
|
|
103
|
+
// message differs from what streamed.
|
|
104
|
+
retext(key: string, text: string): void {
|
|
105
|
+
const item = this.done.get(key);
|
|
106
|
+
if (!item) return;
|
|
107
|
+
this.done.set(key, { ...item, text });
|
|
108
|
+
this.emit({ type: "item", item: { ...item, text } });
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
tool(id: string, start: ToolStart): void {
|
|
@@ -158,7 +174,13 @@ export class TurnItems {
|
|
|
158
174
|
|
|
159
175
|
// Closes the turn: text still streaming settles, tools still running
|
|
160
176
|
// were cut off.
|
|
161
|
-
endTurn(end: {
|
|
177
|
+
endTurn(end: {
|
|
178
|
+
interrupted?: boolean;
|
|
179
|
+
error?: string;
|
|
180
|
+
costUsd?: number;
|
|
181
|
+
finishReason?: string;
|
|
182
|
+
ended?: { reason: TurnEndReason; notice: string };
|
|
183
|
+
}) {
|
|
162
184
|
if (!this.inTurn) return;
|
|
163
185
|
this.inTurn = false;
|
|
164
186
|
for (const [key, text] of [...this.texts]) this.finishText(key, text.kind);
|
|
@@ -172,9 +194,12 @@ export class TurnItems {
|
|
|
172
194
|
durationMs: Date.now() - this.startedAt,
|
|
173
195
|
costUsd: end.costUsd,
|
|
174
196
|
interrupted: end.interrupted,
|
|
197
|
+
finishReason: end.finishReason,
|
|
198
|
+
ended: end.interrupted ? undefined : end.ended,
|
|
175
199
|
createdAt: Date.now(),
|
|
176
200
|
},
|
|
177
201
|
});
|
|
202
|
+
this.done.clear();
|
|
178
203
|
this.emit({ type: "state", state: "idle" });
|
|
179
204
|
}
|
|
180
205
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { redact } from "../../orchestrator/untrusted";
|
|
2
|
+
import type { TurnEndReason } from "../events";
|
|
3
|
+
|
|
4
|
+
export interface TurnEnded {
|
|
5
|
+
reason: TurnEndReason;
|
|
6
|
+
notice: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Said where a reply stopped short, so the fix is in the notice itself.
|
|
10
|
+
const SERVER_HINT =
|
|
11
|
+
"If you run the model yourself, start its server with tool calling on (llama.cpp: --jinja; vLLM: --enable-auto-tool-choice --tool-call-parser; LM Studio: a model with tool use).";
|
|
12
|
+
|
|
13
|
+
const TAIL = 300;
|
|
14
|
+
const LOG_TAIL = 240;
|
|
15
|
+
|
|
16
|
+
const CLOSERS =
|
|
17
|
+
/\b(let me know|i'?ll (wait|leave|stop|hold)|i will (wait|leave|stop)|if you'?d like|feel free|hope (this|that)|want me to)\b/i;
|
|
18
|
+
const ANNOUNCE =
|
|
19
|
+
/\b(let me|let'?s|i'?ll|i will|i'?m going to|i am going to|going to|i need to|i should|now i|first,? i|next,? i|then i)\s+(\w+)/i;
|
|
20
|
+
|
|
21
|
+
// Whether the end of a reply says the model is about to do something.
|
|
22
|
+
export function announcesAction(text: string): boolean {
|
|
23
|
+
const tail = text.trim().slice(-TAIL);
|
|
24
|
+
if (!tail) return false;
|
|
25
|
+
if (tail.endsWith("?")) return false;
|
|
26
|
+
const last =
|
|
27
|
+
tail
|
|
28
|
+
.split(/(?<=[.!:])\s+|\n+/)
|
|
29
|
+
.filter((s) => s.trim())
|
|
30
|
+
.slice(-2)
|
|
31
|
+
.join(" ") || tail;
|
|
32
|
+
if (CLOSERS.test(last)) return false;
|
|
33
|
+
return ANNOUNCE.test(last) || /:\s*$/.test(tail);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Tool-call markup left in a reply: a call that was written, not made.
|
|
37
|
+
export function hasToolMarkup(text: string): boolean {
|
|
38
|
+
return (
|
|
39
|
+
/<tool_call>|<function=|<\|tool_call\|>/.test(text) ||
|
|
40
|
+
/\{\s*"name"\s*:\s*"[^"]+"\s*,\s*"(arguments|parameters)"\s*:\s*\{[\s\S]*\}\s*$/.test(
|
|
41
|
+
text
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function turnOutcome(r: {
|
|
47
|
+
finishReason?: string;
|
|
48
|
+
text: string;
|
|
49
|
+
toolCalls: number;
|
|
50
|
+
error?: string;
|
|
51
|
+
}): TurnEnded | undefined {
|
|
52
|
+
if (r.finishReason === "length")
|
|
53
|
+
return {
|
|
54
|
+
reason: "length",
|
|
55
|
+
notice:
|
|
56
|
+
"The model hit its output limit (finish reason: length) before it finished. Raise maxTokens for this model.",
|
|
57
|
+
};
|
|
58
|
+
if (r.error)
|
|
59
|
+
return {
|
|
60
|
+
reason: "error",
|
|
61
|
+
notice: "The model's server returned an error, shown above.",
|
|
62
|
+
};
|
|
63
|
+
if (r.toolCalls > 0) return undefined;
|
|
64
|
+
if (hasToolMarkup(r.text))
|
|
65
|
+
return {
|
|
66
|
+
reason: "parse_failure",
|
|
67
|
+
notice: `The model wrote a tool call as text that AgentOS couldn't run. ${SERVER_HINT}`,
|
|
68
|
+
};
|
|
69
|
+
if (!r.text.trim())
|
|
70
|
+
return {
|
|
71
|
+
reason: "empty",
|
|
72
|
+
notice: `The model ended its turn with an empty reply. ${SERVER_HINT}`,
|
|
73
|
+
};
|
|
74
|
+
if (announcesAction(r.text))
|
|
75
|
+
return {
|
|
76
|
+
reason: "no_tool_call",
|
|
77
|
+
notice: `The model ended its turn without calling a tool. ${SERVER_HINT}`,
|
|
78
|
+
};
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// One line to diagnose a turn that ended early: where, why and how the
|
|
83
|
+
// reply ended. Never the prompt, and the reply's tail is scrubbed and cut.
|
|
84
|
+
export function logTurnEnd(o: {
|
|
85
|
+
driver: string;
|
|
86
|
+
provider?: string;
|
|
87
|
+
model?: string;
|
|
88
|
+
api?: string;
|
|
89
|
+
finishReason?: string;
|
|
90
|
+
reason: TurnEndReason;
|
|
91
|
+
text: string;
|
|
92
|
+
}): void {
|
|
93
|
+
const tail = redact(
|
|
94
|
+
o.text.slice(-LOG_TAIL).replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
95
|
+
);
|
|
96
|
+
console.warn(
|
|
97
|
+
"[chat] turn ended early",
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
driver: o.driver,
|
|
100
|
+
provider: o.provider,
|
|
101
|
+
model: o.model,
|
|
102
|
+
api: o.api,
|
|
103
|
+
finishReason: o.finishReason,
|
|
104
|
+
reason: o.reason,
|
|
105
|
+
tail,
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
}
|
package/lib/chat/events.ts
CHANGED
|
@@ -150,6 +150,9 @@ interface Base {
|
|
|
150
150
|
createdAt: number;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
export type TurnEndReason =
|
|
154
|
+
"no_tool_call" | "length" | "parse_failure" | "empty" | "error";
|
|
155
|
+
|
|
153
156
|
export type ChatItem =
|
|
154
157
|
| (Base & {
|
|
155
158
|
kind: "user";
|
|
@@ -205,6 +208,11 @@ export type ChatItem =
|
|
|
205
208
|
durationMs?: number;
|
|
206
209
|
costUsd?: number;
|
|
207
210
|
interrupted?: boolean;
|
|
211
|
+
// The model's last finish reason, as its agent reported it.
|
|
212
|
+
finishReason?: string;
|
|
213
|
+
// Set when the turn stopped short of finished work: the reader sees
|
|
214
|
+
// why instead of a plain "Done".
|
|
215
|
+
ended?: { reason: TurnEndReason; notice: string };
|
|
208
216
|
})
|
|
209
217
|
| (Base & { kind: "command_output"; text: string })
|
|
210
218
|
// The conversation's MCP servers, as /mcp reports them.
|
package/lib/chat/registry.ts
CHANGED
|
@@ -78,6 +78,17 @@ interface Registry {
|
|
|
78
78
|
listeners: Map<string, Set<Listener>>;
|
|
79
79
|
// What each agent offers per folder: commands, skills, models.
|
|
80
80
|
caps: Map<string, Capabilities>;
|
|
81
|
+
// Told of every finished turn (lib/analyst), set by the server.
|
|
82
|
+
turnEndHook?: TurnEndHook;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type TurnEndHook = (
|
|
86
|
+
sessionId: string,
|
|
87
|
+
item: Extract<ChatItem, { kind: "turn_end" }>
|
|
88
|
+
) => void;
|
|
89
|
+
|
|
90
|
+
export function setTurnEndHook(fn: TurnEndHook): void {
|
|
91
|
+
registry.turnEndHook = fn;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
// Shared across every module instance in the process (custom server and the
|
|
@@ -102,6 +113,8 @@ export function emit(sessionId: string, m: ChatServerMessage): void {
|
|
|
102
113
|
(m.type === "item" && m.item.kind === "turn_end")
|
|
103
114
|
)
|
|
104
115
|
touchActive(sessionId);
|
|
116
|
+
if (m.type === "item" && m.item.kind === "turn_end")
|
|
117
|
+
registry.turnEndHook?.(sessionId, m.item);
|
|
105
118
|
registry.listeners.get(sessionId)?.forEach((fn) => fn(m));
|
|
106
119
|
// Idle, running, waiting and back, and a finished turn (unread): the
|
|
107
120
|
// sidebar hears at once.
|
package/lib/chat/store.ts
CHANGED
|
@@ -153,6 +153,25 @@ export function lastItems(
|
|
|
153
153
|
return rows.reverse().map((r) => JSON.parse(r.data) as ChatItem);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// The items after `afterItemId`, oldest first, at most the latest `limit`;
|
|
157
|
+
// with no item given (or one since deleted), just the latest `limit`.
|
|
158
|
+
export function itemsAfter(
|
|
159
|
+
sessionId: string,
|
|
160
|
+
afterItemId: string | null,
|
|
161
|
+
limit = 400
|
|
162
|
+
): ChatItem[] {
|
|
163
|
+
flushItems();
|
|
164
|
+
const rows = db
|
|
165
|
+
.prepare(
|
|
166
|
+
`SELECT data FROM chat_items WHERE session_id = ?
|
|
167
|
+
AND seq > COALESCE((SELECT seq FROM chat_items
|
|
168
|
+
WHERE session_id = ? AND item_id = ?), -1)
|
|
169
|
+
ORDER BY seq DESC LIMIT ?`
|
|
170
|
+
)
|
|
171
|
+
.all(sessionId, sessionId, afterItemId, limit) as { data: string }[];
|
|
172
|
+
return rows.reverse().map((r) => JSON.parse(r.data) as ChatItem);
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
// What the agent has said since the last message to it, as text: where a
|
|
157
176
|
// chat task's BLOCKED: line stays until someone answers it.
|
|
158
177
|
export function saidSinceLastMessage(sessionId: string, n = 20): string {
|
package/lib/chat/turns.ts
CHANGED
|
@@ -38,11 +38,13 @@ async function ownExtras(session: Session): Promise<RoleExtras> {
|
|
|
38
38
|
return (await import("../../orchestrator/worker")).orchestratorExtras(
|
|
39
39
|
session
|
|
40
40
|
);
|
|
41
|
-
|
|
41
|
+
// A role's brief (and an analyst's) is the session's own system prompt.
|
|
42
|
+
const own = session.system_prompt?.trim();
|
|
43
|
+
if (!session.task_brief) return own ? { systemAppend: own } : {};
|
|
42
44
|
const atTokens =
|
|
43
45
|
session.task_status === "running" ? refreshSettings().taskTokens : 0;
|
|
44
46
|
return {
|
|
45
|
-
systemAppend: session.task_brief,
|
|
47
|
+
systemAppend: [session.task_brief, own].filter(Boolean).join("\n\n"),
|
|
46
48
|
...(atTokens > 0 && { refresh: { atTokens, role: "task" as const } }),
|
|
47
49
|
};
|
|
48
50
|
}
|
|
@@ -46,7 +46,9 @@ export async function changedFiles(
|
|
|
46
46
|
: git(dir, ["diff", "--name-only", "-z", "HEAD"], OPTS).catch(() => ""),
|
|
47
47
|
git(dir, ["ls-files", "--others", "--exclude-standard", "-z"], OPTS),
|
|
48
48
|
]);
|
|
49
|
-
return [...new Set([...paths(committed), ...paths(untracked)])]
|
|
49
|
+
return [...new Set([...paths(committed), ...paths(untracked)])].filter(
|
|
50
|
+
(p) => !p.split("/").some((seg) => seg.endsWith(".aos-partial"))
|
|
51
|
+
);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
// Sessions that can hold a claim: local, not a mirror of another machine's.
|
package/lib/db/changes.ts
CHANGED
|
@@ -49,6 +49,17 @@ export const TABLES_WATCHED_SESSION_GROUPS = ["session_groups"] as const;
|
|
|
49
49
|
|
|
50
50
|
export const TABLES_WATCHED_WORK_CLAIMS = ["work_claims"] as const;
|
|
51
51
|
|
|
52
|
+
export const TABLES_WATCHED_ANALYST = [
|
|
53
|
+
"analyst_links",
|
|
54
|
+
"analyst_recommendations",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export const TABLES_WATCHED_LOOPS = [
|
|
58
|
+
"task_loops",
|
|
59
|
+
"task_loop_criteria",
|
|
60
|
+
"task_loop_cycles",
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
52
63
|
// Every table any migration watches, for the browser's topic map to cover.
|
|
53
64
|
export const WATCHED_TABLES: readonly string[] = [
|
|
54
65
|
...TABLES_WATCHED_45,
|
|
@@ -61,6 +72,8 @@ export const WATCHED_TABLES: readonly string[] = [
|
|
|
61
72
|
...TABLES_WATCHED_LEARNING_CARDS,
|
|
62
73
|
...TABLES_WATCHED_SESSION_GROUPS,
|
|
63
74
|
...TABLES_WATCHED_WORK_CLAIMS,
|
|
75
|
+
...TABLES_WATCHED_ANALYST,
|
|
76
|
+
...TABLES_WATCHED_LOOPS,
|
|
64
77
|
];
|
|
65
78
|
|
|
66
79
|
export function installChangeTriggers(
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Migration } from "../migration-types";
|
|
2
|
+
import { createSessionRowsView } from "../session-parts/view";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "20261021T1400_session_agent_roles",
|
|
6
|
+
up: (db) => {
|
|
7
|
+
// The role a session was started in (developer, analyst, reviewer,
|
|
8
|
+
// supervisor) and the crew it works with: the id of the session that
|
|
9
|
+
// began it. Additive.
|
|
10
|
+
const cols = (
|
|
11
|
+
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
12
|
+
).map((c) => c.name);
|
|
13
|
+
if (!cols.includes("agent_role"))
|
|
14
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN agent_role TEXT`);
|
|
15
|
+
if (!cols.includes("crew_id"))
|
|
16
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN crew_id TEXT`);
|
|
17
|
+
db.exec(`CREATE INDEX IF NOT EXISTS sessions_crew ON sessions(crew_id)`);
|
|
18
|
+
createSessionRowsView(db);
|
|
19
|
+
},
|
|
20
|
+
} satisfies Migration;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Migration } from "../migration-types";
|
|
2
|
+
import { installChangeTriggers, TABLES_WATCHED_ANALYST } from "../changes";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "20261021T1430_analyst_links",
|
|
6
|
+
up: (db) => {
|
|
7
|
+
// A coding chat paired with an analyst chat that reviews each of its
|
|
8
|
+
// turns (lib/analyst). Rows are kept after unlinking.
|
|
9
|
+
db.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS analyst_links (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
coder_session_id TEXT NOT NULL,
|
|
13
|
+
analyst_session_id TEXT NOT NULL,
|
|
14
|
+
paused INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
reviewed_through TEXT,
|
|
16
|
+
pending_digest_id TEXT,
|
|
17
|
+
pending_through TEXT,
|
|
18
|
+
digest_signals TEXT,
|
|
19
|
+
created_at INTEGER NOT NULL,
|
|
20
|
+
ended_at INTEGER
|
|
21
|
+
);
|
|
22
|
+
CREATE UNIQUE INDEX IF NOT EXISTS analyst_links_coder_live
|
|
23
|
+
ON analyst_links(coder_session_id) WHERE ended_at IS NULL;
|
|
24
|
+
CREATE UNIQUE INDEX IF NOT EXISTS analyst_links_analyst
|
|
25
|
+
ON analyst_links(analyst_session_id);
|
|
26
|
+
CREATE TABLE IF NOT EXISTS analyst_recommendations (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
link_id TEXT NOT NULL,
|
|
29
|
+
analyst_item_id TEXT NOT NULL,
|
|
30
|
+
coder_through TEXT,
|
|
31
|
+
verdict TEXT,
|
|
32
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
33
|
+
issues TEXT NOT NULL DEFAULT '[]',
|
|
34
|
+
signals TEXT,
|
|
35
|
+
prompt TEXT,
|
|
36
|
+
state TEXT NOT NULL DEFAULT 'open',
|
|
37
|
+
error TEXT,
|
|
38
|
+
sent_text TEXT,
|
|
39
|
+
sent_item_id TEXT,
|
|
40
|
+
edited INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
created_at INTEGER NOT NULL,
|
|
42
|
+
decided_at INTEGER
|
|
43
|
+
);
|
|
44
|
+
CREATE UNIQUE INDEX IF NOT EXISTS analyst_recs_item
|
|
45
|
+
ON analyst_recommendations(link_id, analyst_item_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS analyst_recs_link
|
|
47
|
+
ON analyst_recommendations(link_id, created_at);
|
|
48
|
+
`);
|
|
49
|
+
// The pane's card and the sidebar refetch when either changes.
|
|
50
|
+
installChangeTriggers(db, TABLES_WATCHED_ANALYST);
|
|
51
|
+
},
|
|
52
|
+
} satisfies Migration;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Migration } from "../migration-types";
|
|
2
|
+
import { installChangeTriggers, TABLES_WATCHED_LOOPS } from "../changes";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "20261021T1600_task_loops",
|
|
6
|
+
up: (db) => {
|
|
7
|
+
// The developer-analyst loop (lib/loops): one loop per run on a task, its
|
|
8
|
+
// acceptance checklist, and one row per cycle. Rows are kept for good.
|
|
9
|
+
db.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS task_loops (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
session_id TEXT NOT NULL,
|
|
13
|
+
workspace_id TEXT,
|
|
14
|
+
state TEXT NOT NULL DEFAULT 'running',
|
|
15
|
+
stop_reason TEXT,
|
|
16
|
+
stop_detail TEXT,
|
|
17
|
+
goal TEXT NOT NULL DEFAULT '',
|
|
18
|
+
max_cycles INTEGER NOT NULL,
|
|
19
|
+
max_cost_usd REAL NOT NULL,
|
|
20
|
+
max_minutes INTEGER NOT NULL,
|
|
21
|
+
grace_seconds INTEGER NOT NULL,
|
|
22
|
+
stall_cycles INTEGER NOT NULL,
|
|
23
|
+
repeat_cycles INTEGER NOT NULL,
|
|
24
|
+
cost_base_usd REAL NOT NULL DEFAULT 0,
|
|
25
|
+
started_at INTEGER NOT NULL,
|
|
26
|
+
ended_at INTEGER,
|
|
27
|
+
last_cycle_at INTEGER,
|
|
28
|
+
run_now INTEGER NOT NULL DEFAULT 0
|
|
29
|
+
);
|
|
30
|
+
CREATE UNIQUE INDEX IF NOT EXISTS task_loops_live
|
|
31
|
+
ON task_loops(session_id) WHERE state = 'running';
|
|
32
|
+
CREATE INDEX IF NOT EXISTS task_loops_session
|
|
33
|
+
ON task_loops(session_id, started_at);
|
|
34
|
+
CREATE TABLE IF NOT EXISTS task_loop_criteria (
|
|
35
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
36
|
+
loop_id TEXT NOT NULL,
|
|
37
|
+
position INTEGER NOT NULL,
|
|
38
|
+
text TEXT NOT NULL,
|
|
39
|
+
source TEXT NOT NULL DEFAULT 'user',
|
|
40
|
+
evidence_kind TEXT,
|
|
41
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
42
|
+
evidence TEXT,
|
|
43
|
+
verified INTEGER NOT NULL DEFAULT 1,
|
|
44
|
+
met_cycle INTEGER,
|
|
45
|
+
waived_by TEXT,
|
|
46
|
+
removed INTEGER NOT NULL DEFAULT 0
|
|
47
|
+
);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS task_loop_criteria_loop
|
|
49
|
+
ON task_loop_criteria(loop_id, position);
|
|
50
|
+
CREATE TABLE IF NOT EXISTS task_loop_cycles (
|
|
51
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
52
|
+
loop_id TEXT NOT NULL,
|
|
53
|
+
n INTEGER NOT NULL,
|
|
54
|
+
trigger TEXT NOT NULL,
|
|
55
|
+
state TEXT NOT NULL DEFAULT 'claimed',
|
|
56
|
+
snapshot_sha TEXT,
|
|
57
|
+
tree TEXT,
|
|
58
|
+
changed INTEGER NOT NULL DEFAULT 1,
|
|
59
|
+
evidence TEXT,
|
|
60
|
+
log_path TEXT,
|
|
61
|
+
analysis TEXT,
|
|
62
|
+
signature TEXT,
|
|
63
|
+
score TEXT,
|
|
64
|
+
instruction TEXT,
|
|
65
|
+
instruction_sent TEXT,
|
|
66
|
+
sender TEXT,
|
|
67
|
+
delivery TEXT,
|
|
68
|
+
send_at INTEGER,
|
|
69
|
+
input TEXT,
|
|
70
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
71
|
+
error TEXT,
|
|
72
|
+
started_at INTEGER NOT NULL,
|
|
73
|
+
ended_at INTEGER
|
|
74
|
+
);
|
|
75
|
+
CREATE UNIQUE INDEX IF NOT EXISTS task_loop_cycles_n
|
|
76
|
+
ON task_loop_cycles(loop_id, n);
|
|
77
|
+
`);
|
|
78
|
+
// The loop view refetches when any of them changes.
|
|
79
|
+
installChangeTriggers(db, TABLES_WATCHED_LOOPS);
|
|
80
|
+
},
|
|
81
|
+
} satisfies Migration;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Migration } from "../migration-types";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "20261021T1800_health_reports",
|
|
5
|
+
up: (db) => {
|
|
6
|
+
// Plain-English health reports (lib/reports): one row per report, the
|
|
7
|
+
// report itself as JSON, and the next-prompt draft of a task with no
|
|
8
|
+
// analyst. Rows are kept for good.
|
|
9
|
+
db.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS health_reports (
|
|
11
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12
|
+
session_id TEXT NOT NULL,
|
|
13
|
+
workspace_id TEXT,
|
|
14
|
+
head TEXT,
|
|
15
|
+
trigger TEXT NOT NULL,
|
|
16
|
+
evidence_hash TEXT NOT NULL,
|
|
17
|
+
data TEXT NOT NULL,
|
|
18
|
+
model TEXT,
|
|
19
|
+
cost_usd REAL,
|
|
20
|
+
draft_text TEXT,
|
|
21
|
+
draft_status TEXT,
|
|
22
|
+
draft_decided_at TEXT,
|
|
23
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
24
|
+
);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS health_reports_session
|
|
26
|
+
ON health_reports(session_id, id);
|
|
27
|
+
-- The operating mode (manual / assisted / autonomous) of a workspace
|
|
28
|
+
-- (the default) or of one task (the override).
|
|
29
|
+
CREATE TABLE IF NOT EXISTS operating_modes (
|
|
30
|
+
scope TEXT NOT NULL,
|
|
31
|
+
scope_id TEXT NOT NULL,
|
|
32
|
+
mode TEXT NOT NULL,
|
|
33
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
34
|
+
PRIMARY KEY (scope, scope_id)
|
|
35
|
+
);
|
|
36
|
+
`);
|
|
37
|
+
},
|
|
38
|
+
} satisfies Migration;
|
package/lib/db/types.ts
CHANGED
|
@@ -111,6 +111,9 @@ export interface Session {
|
|
|
111
111
|
pinned: boolean;
|
|
112
112
|
// The user's group it sits in (session_groups), or null.
|
|
113
113
|
session_group_id?: string | null;
|
|
114
|
+
// The agent role it was started in (lib/models/agent-roles) and its crew.
|
|
115
|
+
agent_role?: string | null;
|
|
116
|
+
crew_id?: string | null;
|
|
114
117
|
// A workspace's standing orchestrator chat (and that workspace), or null.
|
|
115
118
|
role: "orchestrator" | null;
|
|
116
119
|
// An orchestrator's tool-call secret.
|
package/lib/done/archive.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { db, queries, type Session } from "../db";
|
|
|
6
6
|
import { statusOf } from "../orchestrator/facts";
|
|
7
7
|
import { kickWaits } from "../bus/waits/kick";
|
|
8
8
|
import { peerSessionLink } from "../hosts/peer-actions";
|
|
9
|
+
import { endLinkOf } from "../analyst/endLink";
|
|
9
10
|
|
|
10
11
|
export interface ArchivedView {
|
|
11
12
|
id: string;
|
|
@@ -27,6 +28,7 @@ export function archiveSession(id: string): void {
|
|
|
27
28
|
db.prepare(
|
|
28
29
|
`UPDATE sessions SET archived_at = datetime('now') WHERE id = ? AND archived_at IS NULL`
|
|
29
30
|
).run(id);
|
|
31
|
+
endLinkOf(id);
|
|
30
32
|
kickWaits();
|
|
31
33
|
}
|
|
32
34
|
|
package/lib/done/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { doneOnPeer, peerSessionLink } from "../hosts/peer-actions";
|
|
|
33
33
|
import { hostLink } from "../hosts/remote-api";
|
|
34
34
|
import { doneHeldTask, isHeldRemote } from "../tasks/contract/held-calls";
|
|
35
35
|
import { captureMergedTask } from "../session-logs";
|
|
36
|
+
import { taskEnded } from "../reports/end-hook";
|
|
36
37
|
import { sessionEnded } from "../experiments/hooks";
|
|
37
38
|
import { pushMerged } from "../notify/push/events";
|
|
38
39
|
import { setPr, setTask } from "../db/session-parts";
|
|
@@ -119,6 +120,7 @@ function mark(s: Session, status: "merged" | "done", pr: TaskPR | null) {
|
|
|
119
120
|
if (status === "merged") {
|
|
120
121
|
pushMerged(s.id);
|
|
121
122
|
void captureMergedTask(s.id, { head: pr?.head });
|
|
123
|
+
taskEnded(s.id);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
package/lib/drafts.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface Draft {
|
|
|
29
29
|
// A task's own model, "provider/model"; unset, its roles' models
|
|
30
30
|
// (lib/models).
|
|
31
31
|
taskModel?: string | null;
|
|
32
|
+
// Start in an agent role (Developer, Analyst, Reviewer, Supervisor): its
|
|
33
|
+
// binding picks the model; roleModel overrides it for this session.
|
|
34
|
+
agentRole?: string | null;
|
|
35
|
+
roleModel?: string | null;
|
|
36
|
+
// Pair it with an analyst that reviews each turn (a local chat only).
|
|
37
|
+
withAnalyst?: boolean;
|
|
32
38
|
// A task's priority in the queue; unset: normal.
|
|
33
39
|
taskPriority?: Priority;
|
|
34
40
|
createdAt: number;
|