@saadnvd1/agent-os 0.3.510 → 0.3.513
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/route.ts +1 -7
- package/components/Pane/index.tsx +1 -1
- package/components/SessionList/SessionRowMenu.tsx +1 -1
- package/lib/away/activity.ts +27 -0
- package/lib/away/brief.ts +4 -10
- package/lib/away/run.ts +16 -2
- package/lib/chat/activity.ts +6 -12
- package/lib/chat/events.ts +1 -1
- package/lib/chat/runner.ts +6 -16
- package/lib/chat/socket.ts +2 -3
- package/lib/chat/store.ts +34 -7
- package/lib/db/index.ts +1 -1
- package/lib/db/migrations.ts +13 -0
- package/lib/db/queries.ts +38 -1
- package/lib/db/types.ts +2 -0
- package/lib/done/archive.ts +2 -1
- package/lib/needs-you.ts +1 -1
- package/lib/orchestrator/digest.ts +1 -1
- package/lib/tasks/index.ts +1 -1
- package/lib/tasks/session.ts +13 -3
- package/package.json +1 -1
|
@@ -13,13 +13,7 @@ export async function GET() {
|
|
|
13
13
|
// Merged and dropped tasks live in the Tasks panel (a moved one lives on
|
|
14
14
|
// the machine it moved to), archived sessions
|
|
15
15
|
// in the Archived view: neither is in the sidebar.
|
|
16
|
-
const sessions =
|
|
17
|
-
(s) =>
|
|
18
|
-
!s.archived_at &&
|
|
19
|
-
s.task_status !== "merged" &&
|
|
20
|
-
s.task_status !== "dropped" &&
|
|
21
|
-
s.task_status !== "moved"
|
|
22
|
-
);
|
|
16
|
+
const sessions = queries.getSidebarSessions(db).all() as Session[];
|
|
23
17
|
const groups = queries.getAllGroups(db).all() as Group[];
|
|
24
18
|
|
|
25
19
|
// Convert expanded from 0/1 to boolean
|
|
@@ -305,7 +305,7 @@ export const Pane = memo(function Pane({
|
|
|
305
305
|
sessionName={chat.name}
|
|
306
306
|
accessLocked={chat.role === "orchestrator"}
|
|
307
307
|
freshStart={
|
|
308
|
-
(chat.role === "orchestrator" || !!chat.
|
|
308
|
+
(chat.role === "orchestrator" || !!chat.has_brief) &&
|
|
309
309
|
!chat.archived_at
|
|
310
310
|
}
|
|
311
311
|
orchestratorOf={
|
|
@@ -175,7 +175,7 @@ export function SessionRowMenu({
|
|
|
175
175
|
{summarizing ? "Summarizing..." : "Fresh start"}
|
|
176
176
|
</Item>
|
|
177
177
|
)}
|
|
178
|
-
{(session.role === "orchestrator" || session.
|
|
178
|
+
{(session.role === "orchestrator" || session.has_brief) &&
|
|
179
179
|
session.view === "chat" &&
|
|
180
180
|
!session.archived_at &&
|
|
181
181
|
!blocked && (
|
package/lib/away/activity.ts
CHANGED
|
@@ -116,6 +116,33 @@ function chatTurn(sessionId: string): number {
|
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// How recently Saad must have messaged an orchestrator for turning Away on
|
|
120
|
+
// to tell it.
|
|
121
|
+
export const SAAD_MESSAGE_WINDOW_MS = 24 * 60 * 60_000;
|
|
122
|
+
|
|
123
|
+
// When Saad last typed to a chat himself: a user item with no origin, so
|
|
124
|
+
// not an event, a peer, a schedule or AgentOS, or his own decision made
|
|
125
|
+
// outside the composer. 0 when he never has.
|
|
126
|
+
function saadMessageAt(sessionId: string): number {
|
|
127
|
+
const row = db
|
|
128
|
+
.prepare(
|
|
129
|
+
`SELECT json_extract(data, '$.createdAt') AS at FROM chat_items
|
|
130
|
+
WHERE session_id = ? AND kind = 'user'
|
|
131
|
+
AND (json_extract(data, '$.origin') IS NULL
|
|
132
|
+
OR json_extract(data, '$.origin.kind') = 'decision')
|
|
133
|
+
ORDER BY seq DESC LIMIT 1`
|
|
134
|
+
)
|
|
135
|
+
.get(sessionId) as { at: number | null } | undefined;
|
|
136
|
+
return row?.at ?? 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Whether Saad has been talking to this session lately. A terminal keeps
|
|
140
|
+
// no record of who typed, so its last turn stands in.
|
|
141
|
+
export function saadMessagedRecently(s: Session, now = Date.now()): boolean {
|
|
142
|
+
const at = s.view === "chat" ? saadMessageAt(s.id) : terminalTurn(s);
|
|
143
|
+
return at > 0 && now - at <= SAAD_MESSAGE_WINDOW_MS;
|
|
144
|
+
}
|
|
145
|
+
|
|
119
146
|
// A terminal's Claude conversation file is written on every turn and on
|
|
120
147
|
// nothing else; other agents fall back to tmux's output clock, which a
|
|
121
148
|
// status poll (capture-pane) doesn't move.
|
package/lib/away/brief.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
activeAway,
|
|
8
8
|
clockTime,
|
|
9
9
|
fromSql,
|
|
10
|
-
reachedWorkspaces,
|
|
11
10
|
untilText,
|
|
12
11
|
type AwayRow,
|
|
13
12
|
} from "./state";
|
|
@@ -18,14 +17,9 @@ export function awayOnLine(row: AwayRow, now = Date.now()): string {
|
|
|
18
17
|
return `Saad is away (Away mode, since ${clockTime(fromSql(row.started_at), now)}, ${untilText(row, now)}). Follow work through rather than only reacting to events: keep each task moving to merged and deployed, sign off what passes its gates, and pick up what stalled. Every ${SWEEP_EVERY_MS / 60000} minutes AgentOS sweeps this workspace without messaging any session: it runs lost reviews again, checks the deploy after each merge, and sends you one digest of what's new (stalled tasks, idle ones with uncommitted work and no PR, Saad's own sessions idle with uncommitted work). You decide what needs work and act with your tools. Only sessions with real activity in the last ${awayWindowMinutes()} minutes are listed: leave the rest alone, since a message wakes an agent and re-reads its whole context. Interactive sessions (not tasks) are Saad's: ask a listed one at most once to wrap up, never drive them further. Hard lines still go to ask_saad; while he's away his asks reach his phone in one batch per sweep, so raise them as usual. \`aos notify\` is held for that batch too, so don't send one per item.`;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
// For the digest: the same line
|
|
22
|
-
//
|
|
23
|
-
export function awayDigestLine(
|
|
24
|
-
workspaceId: string,
|
|
25
|
-
now = Date.now()
|
|
26
|
-
): string | null {
|
|
20
|
+
// For the digest: the same line whenever Away is on, messaged or not, so an
|
|
21
|
+
// orchestrator skipped at the toggle still knows when it next starts.
|
|
22
|
+
export function awayDigestLine(now = Date.now()): string | null {
|
|
27
23
|
const row = activeAway();
|
|
28
|
-
return row
|
|
29
|
-
? awayOnLine(row, now)
|
|
30
|
-
: null;
|
|
24
|
+
return row ? awayOnLine(row, now) : null;
|
|
31
25
|
}
|
package/lib/away/run.ts
CHANGED
|
@@ -45,7 +45,12 @@ import {
|
|
|
45
45
|
SWEEP_EVERY_MS,
|
|
46
46
|
type Candidate,
|
|
47
47
|
} from "./sweep";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
agentLive,
|
|
50
|
+
awayWindowMs,
|
|
51
|
+
lastTurnAt,
|
|
52
|
+
saadMessagedRecently,
|
|
53
|
+
} from "./activity";
|
|
49
54
|
import { checkDeploy } from "./deploy";
|
|
50
55
|
import { buildAwayReport, phoneSummary, reportMarkdown } from "./report";
|
|
51
56
|
import { awayOnLine } from "./brief";
|
|
@@ -92,6 +97,14 @@ function tellAway(
|
|
|
92
97
|
// keep an orchestrator going that nothing else has.
|
|
93
98
|
if (!orchestratorActive(orchestratorId, now)) return false;
|
|
94
99
|
const told = reachedWorkspaces(away.id).has(workspaceId);
|
|
100
|
+
// A first message goes only to one Saad has messaged lately: waking the
|
|
101
|
+
// rest re-reads their whole context for nothing, and they read the away
|
|
102
|
+
// state in their brief when they next start. One already told keeps its
|
|
103
|
+
// sweeps for the rest of the period.
|
|
104
|
+
if (!told) {
|
|
105
|
+
const s = sessionRow(orchestratorId);
|
|
106
|
+
if (!s || !saadMessagedRecently(s, now)) return false;
|
|
107
|
+
}
|
|
95
108
|
// One transaction: a crash can't leave the marker set with the event
|
|
96
109
|
// never queued.
|
|
97
110
|
db.transaction(() => {
|
|
@@ -133,8 +146,9 @@ export function turnAwayOn(
|
|
|
133
146
|
): AwayRow {
|
|
134
147
|
const { row, started } = startAway(input, now);
|
|
135
148
|
// Sent again when the end moves, so the orchestrator has the new one.
|
|
136
|
-
for (const { workspaceId, orchestratorId } of orchestrated())
|
|
149
|
+
for (const { workspaceId, orchestratorId } of orchestrated()) {
|
|
137
150
|
tellAway(row, workspaceId, orchestratorId, now, !started);
|
|
151
|
+
}
|
|
138
152
|
return row;
|
|
139
153
|
}
|
|
140
154
|
|
package/lib/chat/activity.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { formatTokens } from "./context";
|
|
|
3
3
|
import { formatElapsed } from "./elapsed";
|
|
4
4
|
import { registry, type Live } from "./registry";
|
|
5
5
|
import { stallThresholdMs } from "./stall";
|
|
6
|
-
import {
|
|
6
|
+
import { lastItems, runningItems } from "./store";
|
|
7
7
|
import type { WorkerEvent } from "./worker/protocol";
|
|
8
8
|
|
|
9
9
|
const lastWhat = (i: ChatItem): string =>
|
|
@@ -55,23 +55,17 @@ export function track(live: Live, e: WorkerEvent): void {
|
|
|
55
55
|
|
|
56
56
|
// After a reattach, what's still running comes from the saved items.
|
|
57
57
|
export function restoreActivity(sessionId: string, state: ChatState) {
|
|
58
|
-
const items = listItems(sessionId);
|
|
59
58
|
const activity: Live["activity"] = { tools: new Map(), tasks: new Set() };
|
|
60
|
-
if (state !== "idle")
|
|
61
|
-
activity.turnStartedAt = items.findLast(
|
|
62
|
-
(i) => i.kind === "user"
|
|
63
|
-
)?.createdAt;
|
|
64
59
|
if (state !== "idle") {
|
|
65
|
-
|
|
60
|
+
activity.turnStartedAt = lastItems(sessionId, 1, "user")[0]?.createdAt;
|
|
61
|
+
const last = lastItems(sessionId, 1)[0];
|
|
66
62
|
activity.lastAt = last?.createdAt ?? activity.turnStartedAt;
|
|
67
63
|
activity.lastWhat = last ? lastWhat(last) : undefined;
|
|
68
|
-
|
|
69
|
-
for (const i of items) {
|
|
70
|
-
if (i.kind === "tool" && i.status === "running" && state !== "idle")
|
|
64
|
+
for (const i of runningItems(sessionId, "tool"))
|
|
71
65
|
activity.tools.set(i.id, { label: i.title, since: i.createdAt });
|
|
72
|
-
if (i.kind === "task" && i.status === "running" && !i.ambient)
|
|
73
|
-
activity.tasks.add(i.taskId);
|
|
74
66
|
}
|
|
67
|
+
for (const i of runningItems(sessionId, "task"))
|
|
68
|
+
if (!i.ambient) activity.tasks.add(i.taskId);
|
|
75
69
|
return activity;
|
|
76
70
|
}
|
|
77
71
|
|
package/lib/chat/events.ts
CHANGED
|
@@ -311,7 +311,7 @@ export interface ToolBody {
|
|
|
311
311
|
export type ChatServerMessage =
|
|
312
312
|
| {
|
|
313
313
|
type: "snapshot";
|
|
314
|
-
//
|
|
314
|
+
// The latest page; older ones come with `history`.
|
|
315
315
|
items: ChatItem[];
|
|
316
316
|
// Where older items start, and whether there are any.
|
|
317
317
|
cursor: number | null;
|
package/lib/chat/runner.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
getItem,
|
|
38
38
|
hasItem,
|
|
39
39
|
itemsOfKind,
|
|
40
|
-
|
|
40
|
+
recentTasks,
|
|
41
41
|
saveItem,
|
|
42
42
|
settle,
|
|
43
43
|
} from "./store";
|
|
@@ -850,26 +850,16 @@ function present(sessionId: string, items: ChatItem[]): ChatItem[] {
|
|
|
850
850
|
return runningWorkers().includes(sessionId) ? items : settle(items);
|
|
851
851
|
}
|
|
852
852
|
|
|
853
|
-
// Snapshot then live updates. Returns the unsubscribe function.
|
|
854
|
-
//
|
|
855
|
-
export function watchChat(
|
|
856
|
-
sessionId: string,
|
|
857
|
-
listener: Listener,
|
|
858
|
-
paged = false
|
|
859
|
-
): () => void {
|
|
853
|
+
// Snapshot then live updates. Returns the unsubscribe function. Every
|
|
854
|
+
// client gets the latest page and asks for older ones.
|
|
855
|
+
export function watchChat(sessionId: string, listener: Listener): () => void {
|
|
860
856
|
const live = registry.live.get(sessionId);
|
|
861
|
-
const page =
|
|
862
|
-
? readPage(sessionId)
|
|
863
|
-
: { items: listItems(sessionId), cursor: null, hasMore: false };
|
|
857
|
+
const page = readPage(sessionId);
|
|
864
858
|
const onPage = new Set(page.items.map((i) => i.id));
|
|
865
859
|
// The composer lists background work still going or just done, even when
|
|
866
860
|
// it started before the page.
|
|
867
861
|
const recent = Date.now() - 60 * 60 * 1000;
|
|
868
|
-
const tasks =
|
|
869
|
-
(t) =>
|
|
870
|
-
!onPage.has(t.id) &&
|
|
871
|
-
(t.status === "running" || (t.endedAt ?? t.createdAt) > recent)
|
|
872
|
-
);
|
|
862
|
+
const tasks = recentTasks(sessionId, recent).filter((t) => !onPage.has(t.id));
|
|
873
863
|
listener({
|
|
874
864
|
type: "snapshot",
|
|
875
865
|
items: present(sessionId, page.items),
|
package/lib/chat/socket.ts
CHANGED
|
@@ -102,17 +102,16 @@ export function serveChatSocket(
|
|
|
102
102
|
// A linked machine's session: its own AgentOS serves the chat.
|
|
103
103
|
const link = demo ? null : peerChatLink(sessionId);
|
|
104
104
|
if (link) {
|
|
105
|
-
const paged = params.get("paged");
|
|
106
105
|
return relayChatSocket(
|
|
107
106
|
ws,
|
|
108
107
|
link,
|
|
109
|
-
{ session: sessionId,
|
|
108
|
+
{ session: sessionId, paged: "1" },
|
|
110
109
|
send,
|
|
111
110
|
(message) => reply(errorItem(message))
|
|
112
111
|
);
|
|
113
112
|
}
|
|
114
113
|
try {
|
|
115
|
-
unwatch = watchChat(sessionId, reply
|
|
114
|
+
unwatch = watchChat(sessionId, reply);
|
|
116
115
|
} catch (err) {
|
|
117
116
|
fail(err);
|
|
118
117
|
if (!closed) ws.close();
|
package/lib/chat/store.ts
CHANGED
|
@@ -176,22 +176,49 @@ export function itemsOfKind<K extends ChatItem["kind"]>(
|
|
|
176
176
|
).map((r) => JSON.parse(r.data));
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
// A chat's background tasks still running, or ended after `since` (ms): what
|
|
180
|
+
// the composer lists, read without parsing the rest of a long conversation.
|
|
181
|
+
export function recentTasks(
|
|
182
|
+
sessionId: string,
|
|
183
|
+
since: number
|
|
184
|
+
): Extract<ChatItem, { kind: "task" }>[] {
|
|
185
|
+
flushItems();
|
|
186
|
+
return (
|
|
187
|
+
db
|
|
188
|
+
.prepare(
|
|
189
|
+
`SELECT data FROM chat_items WHERE session_id = ? AND kind = 'task'
|
|
190
|
+
AND (json_extract(data, '$.status') = 'running'
|
|
191
|
+
OR COALESCE(json_extract(data, '$.endedAt'),
|
|
192
|
+
json_extract(data, '$.createdAt')) > ?)
|
|
193
|
+
ORDER BY seq DESC LIMIT 200`
|
|
194
|
+
)
|
|
195
|
+
.all(sessionId, since) as { data: string }[]
|
|
196
|
+
)
|
|
197
|
+
.reverse()
|
|
198
|
+
.map((r) => JSON.parse(r.data));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Items of a kind saved as still running, read from the running index.
|
|
202
|
+
export function runningItems<K extends "tool" | "task">(
|
|
203
|
+
sessionId: string,
|
|
204
|
+
kind: K
|
|
205
|
+
): Extract<ChatItem, { kind: K }>[] {
|
|
183
206
|
flushItems();
|
|
184
207
|
return (
|
|
185
208
|
db
|
|
186
209
|
.prepare(
|
|
187
210
|
`SELECT data FROM chat_items WHERE session_id = ?
|
|
188
|
-
AND kind = '
|
|
189
|
-
|
|
211
|
+
AND kind = ? AND json_extract(data, '$.status') = 'running'
|
|
212
|
+
ORDER BY seq`
|
|
190
213
|
)
|
|
191
|
-
.all(sessionId) as { data: string }[]
|
|
214
|
+
.all(sessionId, kind) as { data: string }[]
|
|
192
215
|
).map((r) => JSON.parse(r.data));
|
|
193
216
|
}
|
|
194
217
|
|
|
218
|
+
// Tool calls saved as still running.
|
|
219
|
+
export const runningTools = (sessionId: string) =>
|
|
220
|
+
runningItems(sessionId, "tool");
|
|
221
|
+
|
|
195
222
|
export function deleteItems(sessionId: string): void {
|
|
196
223
|
pending.delete(sessionId);
|
|
197
224
|
seqs.delete(sessionId);
|
package/lib/db/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { runMigrations } from "./migrations";
|
|
|
6
6
|
|
|
7
7
|
// Re-export types and queries
|
|
8
8
|
export * from "./types";
|
|
9
|
-
export { queries } from "./queries";
|
|
9
|
+
export { queries, sessionListColumns } from "./queries";
|
|
10
10
|
export * from "./lumifyhub";
|
|
11
11
|
export * from "./lumifyhub-docs";
|
|
12
12
|
export * from "./stacks";
|
package/lib/db/migrations.ts
CHANGED
|
@@ -1617,6 +1617,19 @@ const migrations: Migration[] = [
|
|
|
1617
1617
|
`);
|
|
1618
1618
|
},
|
|
1619
1619
|
},
|
|
1620
|
+
{
|
|
1621
|
+
id: 70,
|
|
1622
|
+
name: "chat_items_running",
|
|
1623
|
+
up: (db) => {
|
|
1624
|
+
// What is still running (tools, background tasks) is read on every
|
|
1625
|
+
// attach; the index holds only those rows, so it's a lookup, not a
|
|
1626
|
+
// scan that parses every tool output of a long conversation.
|
|
1627
|
+
db.exec(
|
|
1628
|
+
`CREATE INDEX IF NOT EXISTS idx_chat_items_running ON chat_items(session_id, kind, seq)
|
|
1629
|
+
WHERE json_extract(data, '$.status') = 'running'`
|
|
1630
|
+
);
|
|
1631
|
+
},
|
|
1632
|
+
},
|
|
1620
1633
|
];
|
|
1621
1634
|
|
|
1622
1635
|
// `upTo`: stop after this id (tests that start from an older database).
|
package/lib/db/queries.ts
CHANGED
|
@@ -13,6 +13,28 @@ function getStmt(db: Database.Database, sql: string): Database.Statement {
|
|
|
13
13
|
return stmt;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Every sessions column but the two large text ones, which only the single
|
|
17
|
+
// session paths read; a list gets has_brief for whether a brief exists.
|
|
18
|
+
const listColumns = new WeakMap<Database.Database, string>();
|
|
19
|
+
export function sessionListColumns(db: Database.Database, alias = ""): string {
|
|
20
|
+
let cols = listColumns.get(db);
|
|
21
|
+
if (!cols) {
|
|
22
|
+
cols = (
|
|
23
|
+
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
24
|
+
)
|
|
25
|
+
.map((c) => c.name)
|
|
26
|
+
.filter((n) => n !== "task_brief" && n !== "system_prompt")
|
|
27
|
+
.join(",");
|
|
28
|
+
listColumns.set(db, cols);
|
|
29
|
+
}
|
|
30
|
+
const prefix = alias ? `${alias}.` : "";
|
|
31
|
+
const list = cols
|
|
32
|
+
.split(",")
|
|
33
|
+
.map((c) => `${prefix}${c}`)
|
|
34
|
+
.join(", ");
|
|
35
|
+
return `${list}, (${prefix}task_brief IS NOT NULL AND ${prefix}task_brief != '') AS has_brief`;
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
export const queries = {
|
|
17
39
|
// Sessions
|
|
18
40
|
createSession: (db: Database.Database) =>
|
|
@@ -26,7 +48,22 @@ export const queries = {
|
|
|
26
48
|
getStmt(db, `SELECT * FROM sessions WHERE id = ?`),
|
|
27
49
|
|
|
28
50
|
getAllSessions: (db: Database.Database) =>
|
|
29
|
-
getStmt(
|
|
51
|
+
getStmt(
|
|
52
|
+
db,
|
|
53
|
+
`SELECT ${sessionListColumns(db)} FROM sessions ORDER BY updated_at DESC`
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
// The sidebar's sessions: not archived, and not a task that is over or
|
|
57
|
+
// lives on another machine.
|
|
58
|
+
getSidebarSessions: (db: Database.Database) =>
|
|
59
|
+
getStmt(
|
|
60
|
+
db,
|
|
61
|
+
`SELECT ${sessionListColumns(db)} FROM sessions
|
|
62
|
+
WHERE archived_at IS NULL
|
|
63
|
+
AND (task_status IS NULL
|
|
64
|
+
OR task_status NOT IN ('merged', 'dropped', 'moved'))
|
|
65
|
+
ORDER BY updated_at DESC`
|
|
66
|
+
),
|
|
30
67
|
|
|
31
68
|
// The tmux names AgentOS owns, for telling discovered sessions apart.
|
|
32
69
|
getManagedTmuxNames: (db: Database.Database) =>
|
package/lib/db/types.ts
CHANGED
|
@@ -51,6 +51,8 @@ export interface Session {
|
|
|
51
51
|
setup_ms?: number | null;
|
|
52
52
|
setup_error?: string | null;
|
|
53
53
|
task_brief?: string | null;
|
|
54
|
+
// Lists carry this instead of the brief itself (0/1).
|
|
55
|
+
has_brief?: number;
|
|
54
56
|
// How the session is shown: a chat conversation or a terminal.
|
|
55
57
|
view: "chat" | "terminal";
|
|
56
58
|
chat_access: ChatAccess;
|
package/lib/done/archive.ts
CHANGED
|
@@ -44,7 +44,8 @@ export function unarchiveSession(id: string): Session {
|
|
|
44
44
|
export function listArchived(workspaceId?: string): ArchivedView[] {
|
|
45
45
|
const rows = db
|
|
46
46
|
.prepare(
|
|
47
|
-
`SELECT s
|
|
47
|
+
`SELECT s.id, s.name, s.project_id, s.view, s.task_status, s.branch_name,
|
|
48
|
+
s.pr_url, s.worktree_path, s.archived_at, p.name AS project_name, p.workspace_id AS project_workspace
|
|
48
49
|
FROM sessions s LEFT JOIN projects p ON p.id = s.project_id
|
|
49
50
|
WHERE s.archived_at IS NOT NULL
|
|
50
51
|
AND (? IS NULL OR p.workspace_id = ?)
|
package/lib/needs-you.ts
CHANGED
|
@@ -56,7 +56,7 @@ function pendingApproval(
|
|
|
56
56
|
const row = getDb()
|
|
57
57
|
.prepare(
|
|
58
58
|
`SELECT data FROM chat_items WHERE session_id = ?
|
|
59
|
-
AND
|
|
59
|
+
AND kind = 'approval'
|
|
60
60
|
AND json_extract(data, '$.status') = 'pending'
|
|
61
61
|
ORDER BY seq DESC LIMIT 1`
|
|
62
62
|
)
|
|
@@ -324,7 +324,7 @@ export function buildDigest(
|
|
|
324
324
|
): string {
|
|
325
325
|
const checks = checksBySession(workspaceId);
|
|
326
326
|
const failures = failuresBySession(workspaceId);
|
|
327
|
-
const away = awayDigestLine(
|
|
327
|
+
const away = awayDigestLine();
|
|
328
328
|
const head = `## Where things stand (read from AgentOS's database as you started)${away ? `\n\n${away}` : ""}`;
|
|
329
329
|
const body = renderDigest(
|
|
330
330
|
[
|
package/lib/tasks/index.ts
CHANGED
|
@@ -410,7 +410,7 @@ export function movedTasks(): { id: string; movedTo: string | null }[] {
|
|
|
410
410
|
|
|
411
411
|
// Archived tasks aren't listed.
|
|
412
412
|
export async function listTasks(): Promise<TaskView[]> {
|
|
413
|
-
const sessions = taskSessions()
|
|
413
|
+
const sessions = taskSessions(false);
|
|
414
414
|
const [local, remote] = await Promise.all([
|
|
415
415
|
Promise.all(sessions.filter((s) => !isMirror(s)).map(taskView)),
|
|
416
416
|
remoteTaskViews(sessions.filter(isMirror)),
|
package/lib/tasks/session.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import os from "os";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
db,
|
|
5
|
+
queries,
|
|
6
|
+
sessionListColumns,
|
|
7
|
+
stackQueries,
|
|
8
|
+
type Session,
|
|
9
|
+
} from "../db";
|
|
4
10
|
import { getDefaultBranch, isBranchName } from "../git";
|
|
5
11
|
import { parentBranchOf } from "../stacks/guard";
|
|
6
12
|
import { voidApprovalsOn } from "../orchestrator/ask-approvals";
|
|
@@ -15,10 +21,14 @@ import type { TaskPR } from "./state";
|
|
|
15
21
|
|
|
16
22
|
export const expandHome = (p: string) => p.replace(/^~/, os.homedir());
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
// Without the brief and system prompt: a list doesn't need them.
|
|
25
|
+
export function taskSessions(includeArchived = true): Session[] {
|
|
19
26
|
return db
|
|
20
27
|
.prepare(
|
|
21
|
-
`SELECT
|
|
28
|
+
`SELECT ${sessionListColumns(db)} FROM sessions
|
|
29
|
+
WHERE task_status IS NOT NULL AND task_status != 'moved'
|
|
30
|
+
${includeArchived ? "" : "AND archived_at IS NULL"}
|
|
31
|
+
ORDER BY created_at DESC`
|
|
22
32
|
)
|
|
23
33
|
.all() as Session[];
|
|
24
34
|
}
|