@agentprojectcontext/apx 1.78.0 → 1.79.0
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/package.json +1 -1
- package/src/core/agent/run-agent.js +30 -5
- package/src/core/agent/tool-summary.js +65 -0
- package/src/core/agent/tools/handlers/list-commitments.js +80 -0
- package/src/core/agent/tools/handlers/list-tasks.js +66 -27
- package/src/core/agent/tools/handlers/record-commitment.js +68 -0
- package/src/core/agent/tools/handlers/send-telegram.js +68 -2
- package/src/core/agent/tools/names.js +6 -0
- package/src/core/agent/tools/registry.js +9 -0
- package/src/core/agent/tools/tool-call-parser.js +70 -1
- package/src/core/channels/telegram/ask-callbacks.js +35 -0
- package/src/core/channels/telegram/dispatch.js +3 -0
- package/src/core/channels/telegram/reply.js +30 -5
- package/src/core/config/paths.js +3 -0
- package/src/core/config/redact.js +22 -0
- package/src/core/daemon/service.js +238 -0
- package/src/core/engines/gemini.js +322 -60
- package/src/core/engines/openai-compatible.js +21 -2
- package/src/core/memory/consolidate.js +225 -0
- package/src/core/nudge/index.js +192 -0
- package/src/core/nudge/policy.js +143 -0
- package/src/core/nudge/store.js +141 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
- package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
- package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
- package/src/core/routines/runner.js +102 -3
- package/src/core/routines/signals.js +270 -0
- package/src/core/stores/commitments.js +331 -0
- package/src/core/stores/messages.js +4 -0
- package/src/core/stores/routines.js +17 -3
- package/src/core/util/thinking.js +51 -0
- package/src/host/daemon/api/commitments.js +135 -0
- package/src/host/daemon/api/nudges.js +112 -0
- package/src/host/daemon/api/routines.js +24 -0
- package/src/host/daemon/api/self-memory.js +50 -0
- package/src/host/daemon/api/telegram.js +42 -4
- package/src/host/daemon/api/voice.js +3 -1
- package/src/host/daemon/api.js +6 -0
- package/src/host/daemon/callback-reconciler.js +16 -0
- package/src/host/daemon/plugins/desktop/index.js +7 -1
- package/src/host/daemon/plugins/telegram/index.js +7 -2
- package/src/host/daemon/wakeup.js +17 -3
- package/src/interfaces/cli/commands/commitment.js +154 -0
- package/src/interfaces/cli/commands/daemon.js +57 -0
- package/src/interfaces/cli/commands/memory.js +73 -0
- package/src/interfaces/cli/commands/nudge.js +130 -0
- package/src/interfaces/cli/help/index.js +2 -2
- package/src/interfaces/cli/routes/commitment.js +19 -0
- package/src/interfaces/cli/routes/daemon.js +7 -1
- package/src/interfaces/cli/routes/index.js +4 -0
- package/src/interfaces/cli/routes/memory.js +10 -2
- package/src/interfaces/cli/routes/nudge.js +17 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +11 -10
- package/src/interfaces/web/src/components/Section.tsx +18 -3
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
- package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
- package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +34 -4
- package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +12 -2
- package/src/interfaces/web/src/components/routines/shared.ts +14 -5
- package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
- package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
- package/src/interfaces/web/src/components/ui.tsx +1 -0
- package/src/interfaces/web/src/constants/index.ts +1 -0
- package/src/interfaces/web/src/hooks/useChat.ts +5 -1
- package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
- package/src/interfaces/web/src/i18n/en.ts +127 -0
- package/src/interfaces/web/src/i18n/es.ts +127 -0
- package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
- package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
- package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
- package/src/interfaces/web/src/lib/cron.ts +196 -0
- package/src/interfaces/web/src/lib/when.ts +32 -0
- package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
- package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +102 -19
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +15 -0
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +21 -3
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +13 -11
- package/src/interfaces/web/src/types/daemon.ts +10 -1
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +0 -824
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +0 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
+
import { Plus } from "lucide-react";
|
|
2
3
|
import { useNavigate } from "react-router-dom";
|
|
3
4
|
import { Tasks } from "../../lib/api";
|
|
4
5
|
import type { TaskStatus } from "../../types/daemon";
|
|
5
6
|
import { Section } from "../../components/Section";
|
|
6
7
|
import { PagedList, usePagedQuery } from "../../components/Pager";
|
|
7
|
-
import { Badge, Button, Empty, Loading } from "../../components/ui";
|
|
8
|
+
import { Badge, Button, Dialog, Empty, Field, FilterChips, Input, Loading } from "../../components/ui";
|
|
9
|
+
import { UiSelect } from "../../components/UiSelect";
|
|
10
|
+
import { useProjects } from "../../hooks/useProjects";
|
|
11
|
+
import { useToast } from "../../components/Toast";
|
|
8
12
|
import { t } from "../../i18n";
|
|
9
13
|
|
|
10
14
|
// All projects' tasks, aggregated (GET /tasks), server-paginated.
|
|
@@ -15,38 +19,79 @@ export function GlobalTasksTab() {
|
|
|
15
19
|
// right now" is not "what is open". Only meaningful for open tasks.
|
|
16
20
|
const [status, setStatus] = useState<TaskStatus | "">("");
|
|
17
21
|
const effectiveStatus = state === "open" ? status : "";
|
|
22
|
+
// The per-project Tasks screen has always had an inline add form; this one
|
|
23
|
+
// did not, so on the base workspace the only way to file a task was to ask
|
|
24
|
+
// the agent. A picker is the price of the cross-project view.
|
|
25
|
+
const [adding, setAdding] = useState(false);
|
|
26
|
+
const [title, setTitle] = useState("");
|
|
27
|
+
const [target, setTarget] = useState("");
|
|
28
|
+
const [due, setDue] = useState("");
|
|
29
|
+
const [busy, setBusy] = useState(false);
|
|
30
|
+
const { projects } = useProjects();
|
|
31
|
+
const toast = useToast();
|
|
32
|
+
|
|
18
33
|
const paged = usePagedQuery({
|
|
19
34
|
key: `/api/tasks?state=${state}&status=${effectiveStatus}`,
|
|
20
35
|
fetchPage: (limit, offset) => Tasks.globalPage({ state, limit, offset, status: effectiveStatus }),
|
|
21
36
|
resetKey: `${state}|${effectiveStatus}`,
|
|
22
37
|
});
|
|
23
38
|
|
|
39
|
+
const create = async () => {
|
|
40
|
+
if (!title.trim() || !target) return;
|
|
41
|
+
setBusy(true);
|
|
42
|
+
try {
|
|
43
|
+
await Tasks.add(String(target), { title: title.trim(), due: due || null });
|
|
44
|
+
await paged.mutate();
|
|
45
|
+
setAdding(false);
|
|
46
|
+
setTitle(""); setDue("");
|
|
47
|
+
} catch (e) {
|
|
48
|
+
toast.error((e as Error).message);
|
|
49
|
+
} finally {
|
|
50
|
+
setBusy(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
24
54
|
return (
|
|
25
55
|
<Section
|
|
26
56
|
fullHeight
|
|
27
57
|
title={t("project.global_tasks.title")}
|
|
28
58
|
description={t("project.global_tasks.subtitle")}
|
|
29
59
|
action={
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
))}
|
|
34
|
-
|
|
60
|
+
<Button
|
|
61
|
+
size="sm"
|
|
62
|
+
variant="primary"
|
|
63
|
+
onClick={() => { setTarget(String(projects[0]?.id ?? "")); setAdding(true); }}
|
|
64
|
+
>
|
|
65
|
+
<Plus size={14} /> {t("project.global_tasks.add")}
|
|
66
|
+
</Button>
|
|
67
|
+
}
|
|
68
|
+
filters={
|
|
69
|
+
<>
|
|
70
|
+
<FilterChips
|
|
71
|
+
value={state}
|
|
72
|
+
onChange={setState}
|
|
73
|
+
label={t("project.global_tasks.title")}
|
|
74
|
+
options={(["open", "done", "dropped", "all"] as const).map((s) => ({ value: s, label: s }))}
|
|
75
|
+
/>
|
|
76
|
+
{state === "open" ? (
|
|
77
|
+
<>
|
|
78
|
+
<span className="mx-1 h-4 w-px bg-border" aria-hidden />
|
|
79
|
+
<FilterChips
|
|
80
|
+
value={status}
|
|
81
|
+
onChange={setStatus}
|
|
82
|
+
label={t("project.global_tasks.any_status")}
|
|
83
|
+
options={[
|
|
84
|
+
{ value: "" as const, label: t("project.global_tasks.any_status") },
|
|
85
|
+
...(["pending", "running", "in_review", "blocked"] as const).map((s) => ({
|
|
86
|
+
value: s, label: s.replace("_", " "),
|
|
87
|
+
})),
|
|
88
|
+
]}
|
|
89
|
+
/>
|
|
90
|
+
</>
|
|
91
|
+
) : null}
|
|
92
|
+
</>
|
|
35
93
|
}
|
|
36
94
|
>
|
|
37
|
-
{state === "open" ? (
|
|
38
|
-
<div className="mb-3 flex flex-wrap gap-1">
|
|
39
|
-
<Button size="sm" variant={status === "" ? "primary" : "ghost"} onClick={() => setStatus("")}>
|
|
40
|
-
{t("project.global_tasks.any_status")}
|
|
41
|
-
</Button>
|
|
42
|
-
{(["pending", "running", "in_review", "blocked"] as const).map((s) => (
|
|
43
|
-
<Button key={s} size="sm" variant={status === s ? "primary" : "ghost"} onClick={() => setStatus(s)}>
|
|
44
|
-
{s.replace("_", " ")}
|
|
45
|
-
</Button>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
) : null}
|
|
49
|
-
|
|
50
95
|
{paged.isLoading && <Loading />}
|
|
51
96
|
{!paged.isLoading && paged.total === 0 && <Empty>{t("project.global_tasks.empty")}</Empty>}
|
|
52
97
|
<PagedList paged={paged} fullHeight>
|
|
@@ -73,6 +118,44 @@ export function GlobalTasksTab() {
|
|
|
73
118
|
))}
|
|
74
119
|
</ul>
|
|
75
120
|
</PagedList>
|
|
121
|
+
|
|
122
|
+
<Dialog
|
|
123
|
+
open={adding}
|
|
124
|
+
onClose={() => setAdding(false)}
|
|
125
|
+
title={t("project.global_tasks.add_title")}
|
|
126
|
+
footer={
|
|
127
|
+
<>
|
|
128
|
+
<Button onClick={() => setAdding(false)}>{t("common.cancel")}</Button>
|
|
129
|
+
<Button variant="primary" loading={busy} disabled={!title.trim() || !target} onClick={create}>
|
|
130
|
+
{t("project.global_tasks.add")}
|
|
131
|
+
</Button>
|
|
132
|
+
</>
|
|
133
|
+
}
|
|
134
|
+
>
|
|
135
|
+
<div className="flex flex-col gap-3 p-5">
|
|
136
|
+
<Field label={t("project.global_tasks.field_title")}>
|
|
137
|
+
<Input
|
|
138
|
+
value={title}
|
|
139
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
140
|
+
autoFocus
|
|
141
|
+
onKeyDown={(e) => { if (e.key === "Enter" && title.trim() && target) create(); }}
|
|
142
|
+
/>
|
|
143
|
+
</Field>
|
|
144
|
+
{/* Required, unlike the per-project screen: a cross-project view has
|
|
145
|
+
no "here" to default to, and guessing one would file the task
|
|
146
|
+
somewhere the user did not look. */}
|
|
147
|
+
<Field label={t("project.global_tasks.field_project")}>
|
|
148
|
+
<UiSelect
|
|
149
|
+
value={String(target)}
|
|
150
|
+
onChange={setTarget}
|
|
151
|
+
options={projects.map((p) => ({ value: String(p.id), label: p.name || p.path }))}
|
|
152
|
+
/>
|
|
153
|
+
</Field>
|
|
154
|
+
<Field label={t("project.global_tasks.field_due")}>
|
|
155
|
+
<Input type="date" value={due} onChange={(e) => setDue(e.target.value)} />
|
|
156
|
+
</Field>
|
|
157
|
+
</div>
|
|
158
|
+
</Dialog>
|
|
76
159
|
</Section>
|
|
77
160
|
);
|
|
78
161
|
}
|
|
@@ -23,6 +23,16 @@ function actorLabel(m: MessageEntry): string {
|
|
|
23
23
|
return m.agent_slug || m.actor_id || m.author || m.actor_kind || "—";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** Structured lifecycle events APX records as they happen (a routine created,
|
|
27
|
+
* a routine updated). Returns the label to badge, or null for ordinary rows. */
|
|
28
|
+
function lifecycleEvent(m: MessageEntry): string | null {
|
|
29
|
+
const e = m.meta?.event;
|
|
30
|
+
if (typeof e !== "string") return null;
|
|
31
|
+
if (e === "routine_created") return t("logs.event_routine_created");
|
|
32
|
+
if (e === "routine_updated") return t("logs.event_routine_updated");
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
/** Model that produced this record — persisted in meta by every channel that
|
|
27
37
|
* runs an agent turn. Absent on user/system rows and on pre-1.74 history. */
|
|
28
38
|
function modelOf(m: MessageEntry): string | null {
|
|
@@ -56,6 +66,11 @@ function LogRow({ m }: { m: MessageEntry }) {
|
|
|
56
66
|
<span className="font-mono">{fmtTs(m.ts)}</span>
|
|
57
67
|
<Badge tone="info">{m.channel}</Badge>
|
|
58
68
|
{m.type && <Badge>{m.type}</Badge>}
|
|
69
|
+
{/* Lifecycle events get their own chip rather than blending into the
|
|
70
|
+
body text. "A routine now exists" is the kind of thing people
|
|
71
|
+
scroll back looking for, and a green badge is findable in a way a
|
|
72
|
+
sentence in a log line is not. */}
|
|
73
|
+
{lifecycleEvent(m) && <Badge tone="success">{lifecycleEvent(m)}</Badge>}
|
|
59
74
|
<span className="font-medium text-foreground">{actorLabel(m)}</span>
|
|
60
75
|
{model && <span className="font-mono text-[11px] text-sky-400/90">{model}</span>}
|
|
61
76
|
{tokens !== null && <span className="font-mono text-[11px]">{tokens} tok</span>}
|
|
@@ -20,7 +20,21 @@ import type { AgentEntry } from "../../types/daemon";
|
|
|
20
20
|
// with a real APC agent slug (which must match /^[a-z][a-z0-9_-]*$/).
|
|
21
21
|
const ROBY_SLUG = "__super_agent__";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* `hideSidebar` lets another screen (the agent inbox) supply its own
|
|
25
|
+
* conversation list and embed just the thread. The chat surface is not forked
|
|
26
|
+
* — one implementation, two frames around it.
|
|
27
|
+
*/
|
|
28
|
+
export function ChatTab({
|
|
29
|
+
pid,
|
|
30
|
+
hideSidebar = false,
|
|
31
|
+
initialSelection,
|
|
32
|
+
}: {
|
|
33
|
+
pid: string;
|
|
34
|
+
hideSidebar?: boolean;
|
|
35
|
+
/** Open a specific conversation on mount instead of a fresh live session. */
|
|
36
|
+
initialSelection?: ChatKey;
|
|
37
|
+
}) {
|
|
24
38
|
const toast = useToast();
|
|
25
39
|
const [params, setSearchParams] = useSearchParams();
|
|
26
40
|
const agents = useSWR(`/api/projects/${pid}/agents`, () => Agents.list(pid));
|
|
@@ -36,6 +50,10 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
36
50
|
// defaulting to a live session with the super-agent so the chat works even on
|
|
37
51
|
// a brand-new project with zero agents and zero conversations.
|
|
38
52
|
const [selected, setSelected] = useState<ChatKey>(() => {
|
|
53
|
+
// An embedding screen that already knows which conversation the user
|
|
54
|
+
// picked wins over the URL — otherwise clicking a row in the inbox opens
|
|
55
|
+
// an empty new session and the message you clicked on is nowhere.
|
|
56
|
+
if (initialSelection) return initialSelection;
|
|
39
57
|
const agent = params.get("agent");
|
|
40
58
|
const conv = params.get("conv");
|
|
41
59
|
const channel = params.get("channel");
|
|
@@ -183,7 +201,7 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
183
201
|
|
|
184
202
|
return (
|
|
185
203
|
<div className="flex h-full overflow-hidden rounded-xl border border-border bg-card/40">
|
|
186
|
-
<ChatList
|
|
204
|
+
{hideSidebar ? null : <ChatList
|
|
187
205
|
pid={pid}
|
|
188
206
|
agents={agentList}
|
|
189
207
|
superAgentSlug={ROBY_SLUG}
|
|
@@ -191,7 +209,7 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
191
209
|
selected={selected}
|
|
192
210
|
onSelect={selectChat}
|
|
193
211
|
onNewChat={onNewChat}
|
|
194
|
-
/>
|
|
212
|
+
/>}
|
|
195
213
|
|
|
196
214
|
<section className="flex min-w-0 flex-1 flex-col">
|
|
197
215
|
<header className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3">
|
|
@@ -5,6 +5,7 @@ import { Plus } from "lucide-react";
|
|
|
5
5
|
import { Routines, type RoutineEntry } from "../../lib/api";
|
|
6
6
|
import { Button, Dialog, Empty, Loading } from "../../components/ui";
|
|
7
7
|
import { useToast } from "../../components/Toast";
|
|
8
|
+
import { Section } from "../../components/Section";
|
|
8
9
|
import { t } from "../../i18n";
|
|
9
10
|
import { RoutineList } from "../../components/routines/RoutineList";
|
|
10
11
|
import { RoutineDetail } from "../../components/routines/RoutineDetail";
|
|
@@ -75,23 +76,24 @@ export function RoutinesTab({ pid }: { pid: string }) {
|
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
return (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// Same frame as every other list page. This one used to draw its own
|
|
80
|
+
// header outside a card while Tasks and Commitments sat inside one, so
|
|
81
|
+
// moving between them the whole page shifted.
|
|
82
|
+
<Section
|
|
83
|
+
fullHeight
|
|
84
|
+
title={t("project.routines.title")}
|
|
85
|
+
description={t("project.routines.subtitle")}
|
|
86
|
+
action={
|
|
85
87
|
<Button size="sm" variant="primary" onClick={() => setEditing({ kind: "super_agent", schedule: "every:10m", enabled: true })}>
|
|
86
88
|
<Plus size={14} /> {t("project.routines.new_btn")}
|
|
87
89
|
</Button>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
}
|
|
91
|
+
>
|
|
90
92
|
{list.isLoading && <Loading />}
|
|
91
93
|
{!list.isLoading && rows.length === 0 && <Empty>{t("project.routines.empty")}</Empty>}
|
|
92
94
|
|
|
93
95
|
{rows.length > 0 && (
|
|
94
|
-
<div className="grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)] grid-cols-[minmax(200px,260px)_1fr] overflow-hidden rounded-
|
|
96
|
+
<div className="grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)] grid-cols-[minmax(200px,260px)_1fr] overflow-hidden rounded-lg border border-border">
|
|
95
97
|
<RoutineList routines={rows} selectedName={selected?.name ?? null} onSelect={selectRoutine} />
|
|
96
98
|
<div className="min-h-0 min-w-0 overflow-hidden">
|
|
97
99
|
{selected
|
|
@@ -148,6 +150,6 @@ export function RoutinesTab({ pid }: { pid: string }) {
|
|
|
148
150
|
>
|
|
149
151
|
<p className="text-sm text-muted-fg">{t("project.routines.run_confirm_body")}</p>
|
|
150
152
|
</Dialog>
|
|
151
|
-
</
|
|
153
|
+
</Section>
|
|
152
154
|
);
|
|
153
155
|
}
|
|
@@ -55,7 +55,7 @@ export interface AgentDetail extends AgentEntry {
|
|
|
55
55
|
|
|
56
56
|
export interface RoutineEntry {
|
|
57
57
|
name: string;
|
|
58
|
-
kind: "heartbeat" | "exec_agent" | "super_agent" | "telegram" | "shell";
|
|
58
|
+
kind: "heartbeat" | "exec_agent" | "super_agent" | "telegram" | "shell" | "watch";
|
|
59
59
|
schedule: string;
|
|
60
60
|
spec: Record<string, unknown>;
|
|
61
61
|
enabled: boolean;
|
|
@@ -228,6 +228,15 @@ export interface ConversationMessage {
|
|
|
228
228
|
actor_kind?: string;
|
|
229
229
|
model?: string;
|
|
230
230
|
usage?: ChatUsage;
|
|
231
|
+
/** Compact record of what the turn did, written at the time (the live tool
|
|
232
|
+
* events are long gone by the time a thread is read back). */
|
|
233
|
+
tool_summary?: ToolSummary;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface ToolSummary {
|
|
237
|
+
total: number;
|
|
238
|
+
failed: number;
|
|
239
|
+
tools: { name: string; count: number; failed: number }[];
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
export interface ConversationDetail {
|