pilotswarm-sdk 0.1.19 → 0.1.21
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 +6 -0
- package/dist/artifact-tools.d.ts.map +1 -1
- package/dist/artifact-tools.js +20 -5
- package/dist/artifact-tools.js.map +1 -1
- package/dist/blob-store.d.ts +6 -4
- package/dist/blob-store.d.ts.map +1 -1
- package/dist/blob-store.js +55 -12
- package/dist/blob-store.js.map +1 -1
- package/dist/client.d.ts +4 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +628 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +145 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +288 -17
- package/dist/cms.js.map +1 -1
- package/dist/facts-migrations.d.ts.map +1 -1
- package/dist/facts-migrations.js +227 -0
- package/dist/facts-migrations.js.map +1 -1
- package/dist/facts-store.d.ts +21 -0
- package/dist/facts-store.d.ts.map +1 -1
- package/dist/facts-store.js +34 -1
- package/dist/facts-store.js.map +1 -1
- package/dist/facts-tools.d.ts +7 -0
- package/dist/facts-tools.d.ts.map +1 -1
- package/dist/facts-tools.js +29 -2
- package/dist/facts-tools.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect-tools.d.ts +42 -0
- package/dist/inspect-tools.d.ts.map +1 -0
- package/dist/inspect-tools.js +800 -0
- package/dist/inspect-tools.js.map +1 -0
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +76 -35
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +64 -2
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +109 -0
- package/dist/management-client.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +6 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +3 -3
- package/dist/orchestration.d.ts.map +1 -1
- package/dist/orchestration.js +27 -4
- package/dist/orchestration.js.map +1 -1
- package/dist/orchestration_1_0_43.d.ts +12 -0
- package/dist/orchestration_1_0_43.d.ts.map +1 -0
- package/dist/orchestration_1_0_43.js +2710 -0
- package/dist/orchestration_1_0_43.js.map +1 -0
- package/dist/orchestration_1_0_44.d.ts +12 -0
- package/dist/orchestration_1_0_44.d.ts.map +1 -0
- package/dist/orchestration_1_0_44.js +2710 -0
- package/dist/orchestration_1_0_44.js.map +1 -0
- package/dist/session-manager.d.ts +9 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +40 -3
- package/dist/session-manager.js.map +1 -1
- package/dist/session-owner-utils.d.ts +25 -0
- package/dist/session-owner-utils.d.ts.map +1 -0
- package/dist/session-owner-utils.js +82 -0
- package/dist/session-owner-utils.js.map +1 -0
- package/dist/session-proxy.d.ts +5 -1
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +70 -8
- package/dist/session-proxy.js.map +1 -1
- package/dist/session-store.d.ts +38 -6
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +187 -9
- package/dist/session-store.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +11 -2
- package/dist/worker.js.map +1 -1
- package/package.json +10 -4
- package/plugins/mgmt/agents/agent-tuner.agent.md +222 -0
- package/plugins/mgmt/agents/facts-manager.agent.md +8 -1
- package/plugins/mgmt/agents/pilotswarm.agent.md +13 -10
- package/plugins/mgmt/agents/resourcemgr.agent.md +11 -4
- package/plugins/mgmt/agents/sweeper.agent.md +5 -4
- package/plugins/mgmt/skills/cost-latency-analysis/SKILL.md +117 -0
- package/plugins/mgmt/skills/orchestration-session-lifecycle/SKILL.md +117 -0
- package/plugins/mgmt/skills/resourcemgr/SKILL.md +1 -1
- package/plugins/mgmt/skills/sweeper/SKILL.md +4 -4
- package/plugins/system/agents/default.agent.md +22 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspect Tools — read-only inspection tools for agents.
|
|
3
|
+
*
|
|
4
|
+
* `read_agent_events` is available to every session. It lets an ancestor
|
|
5
|
+
* read the durable event stream of a descendant in its spawn tree using
|
|
6
|
+
* the existing `session_events.seq` cursor.
|
|
7
|
+
*
|
|
8
|
+
* A small read-only subset is exposed to permanent system agents so they can
|
|
9
|
+
* inspect sessions and owner-scoped usage without mutating state.
|
|
10
|
+
* The deeper diagnostic tools remain restricted to the `agent-tuner`
|
|
11
|
+
* system agent. They give the tuner unrestricted, read-only access to CMS
|
|
12
|
+
* state, per-session and fleet metric summaries, duroxide orchestration
|
|
13
|
+
* stats, and execution history for the purpose of diagnosing why a
|
|
14
|
+
* session, agent, or orchestration is not behaving as expected.
|
|
15
|
+
*
|
|
16
|
+
* Tuner tools never mutate state. The `agent-tuner` agent definition is
|
|
17
|
+
* the only intended consumer.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
import { defineTool } from "@github/copilot-sdk";
|
|
23
|
+
import { formatOwnerBucketLabel, formatSessionOwnerLabel, getSessionOwnerKind, matchesOwnerBucketFilters, matchesSessionOwnerFilters } from "./session-owner-utils.js";
|
|
24
|
+
const TUNER_AGENT_ID = "agent-tuner";
|
|
25
|
+
const SYSTEM_AGENT_IDS = new Set([
|
|
26
|
+
"pilotswarm",
|
|
27
|
+
"facts-manager",
|
|
28
|
+
"sweeper",
|
|
29
|
+
"resourcemgr",
|
|
30
|
+
"agent-tuner",
|
|
31
|
+
]);
|
|
32
|
+
const DEFAULT_LIMIT = 50;
|
|
33
|
+
const MAX_LIMIT = 200;
|
|
34
|
+
const MAX_DATA_BYTES = 4 * 1024;
|
|
35
|
+
const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
36
|
+
function normalizeSessionId(raw) {
|
|
37
|
+
return raw?.startsWith("session-") ? raw.slice("session-".length) : raw;
|
|
38
|
+
}
|
|
39
|
+
function clampLimit(limit) {
|
|
40
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0)
|
|
41
|
+
return DEFAULT_LIMIT;
|
|
42
|
+
return Math.min(Math.floor(limit), MAX_LIMIT);
|
|
43
|
+
}
|
|
44
|
+
function truncateData(data) {
|
|
45
|
+
if (data == null)
|
|
46
|
+
return { data, truncated: false };
|
|
47
|
+
let serialized;
|
|
48
|
+
try {
|
|
49
|
+
serialized = JSON.stringify(data);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { data: "[unserializable]", truncated: true };
|
|
53
|
+
}
|
|
54
|
+
if (serialized.length <= MAX_DATA_BYTES) {
|
|
55
|
+
return { data, truncated: false };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
data: serialized.slice(0, MAX_DATA_BYTES) + "…",
|
|
59
|
+
truncated: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function eventTimestamp(event) {
|
|
63
|
+
const t = event.createdAt;
|
|
64
|
+
if (t instanceof Date)
|
|
65
|
+
return t.toISOString();
|
|
66
|
+
if (typeof t === "string")
|
|
67
|
+
return t;
|
|
68
|
+
if (typeof t === "number")
|
|
69
|
+
return new Date(t).toISOString();
|
|
70
|
+
return new Date().toISOString();
|
|
71
|
+
}
|
|
72
|
+
function serializeEvents(events) {
|
|
73
|
+
const out = [];
|
|
74
|
+
let total = 0;
|
|
75
|
+
let hasMore = false;
|
|
76
|
+
for (const event of events) {
|
|
77
|
+
const { data, truncated } = truncateData(event.data);
|
|
78
|
+
const item = {
|
|
79
|
+
seq: Number(event.seq),
|
|
80
|
+
eventType: event.eventType,
|
|
81
|
+
createdAt: eventTimestamp(event),
|
|
82
|
+
...(event.workerNodeId ? { workerNodeId: event.workerNodeId } : {}),
|
|
83
|
+
...(data !== undefined ? { data } : {}),
|
|
84
|
+
...(truncated ? { _truncated: true } : {}),
|
|
85
|
+
};
|
|
86
|
+
const itemSize = JSON.stringify(item).length;
|
|
87
|
+
if (total + itemSize > MAX_RESPONSE_BYTES && out.length > 0) {
|
|
88
|
+
hasMore = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
out.push(item);
|
|
92
|
+
total += itemSize;
|
|
93
|
+
}
|
|
94
|
+
return { serialized: out, hasMore };
|
|
95
|
+
}
|
|
96
|
+
export function createInspectTools(opts) {
|
|
97
|
+
const { catalog, agentIdentity, duroxideClient, factStore } = opts;
|
|
98
|
+
const isTuner = agentIdentity === TUNER_AGENT_ID;
|
|
99
|
+
const isSystemAgent = SYSTEM_AGENT_IDS.has(agentIdentity || "");
|
|
100
|
+
const readAgentEventsTool = defineTool("read_agent_events", {
|
|
101
|
+
description: "Read durable events from a descendant agent in your spawn tree, paginated by seq cursor. " +
|
|
102
|
+
"Use cursor=null (or omit) for the most recent page; pass the returned prevCursor to walk backwards in time. " +
|
|
103
|
+
"Use this when check_agents / wait_for_agents / store_fact / read_facts are not enough to understand what the descendant did " +
|
|
104
|
+
"(e.g. you need to see the child's reasoning, tool calls, or intermediate outputs). " +
|
|
105
|
+
"Default page is newest-first, returned in chronological order inside the page. " +
|
|
106
|
+
"Use the event_types filter to keep token cost low.",
|
|
107
|
+
parameters: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
agent_id: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "Descendant session id (must be a direct or transitive child you spawned). " +
|
|
113
|
+
"Either the raw UUID or the 'session-<uuid>' form is accepted.",
|
|
114
|
+
},
|
|
115
|
+
cursor: {
|
|
116
|
+
type: "number",
|
|
117
|
+
description: "Optional seq cursor. Omit (or pass 0) for the most recent page; " +
|
|
118
|
+
"pass a positive integer to return events strictly older than that seq.",
|
|
119
|
+
},
|
|
120
|
+
limit: {
|
|
121
|
+
type: "number",
|
|
122
|
+
description: `Max events per page. Default ${DEFAULT_LIMIT}, hard cap ${MAX_LIMIT}.`,
|
|
123
|
+
},
|
|
124
|
+
event_types: {
|
|
125
|
+
type: "array",
|
|
126
|
+
items: { type: "string" },
|
|
127
|
+
description: "Optional event-type filter, e.g. ['assistant.message','tool.invoked','turn completed']. " +
|
|
128
|
+
"Filtering happens after the page is fetched, so very narrow filters may return fewer rows than `limit`.",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ["agent_id"],
|
|
132
|
+
},
|
|
133
|
+
handler: async (args, ctx) => {
|
|
134
|
+
const callerSessionId = ctx?.sessionId;
|
|
135
|
+
if (!callerSessionId) {
|
|
136
|
+
return { error: "read_agent_events: caller session id is required" };
|
|
137
|
+
}
|
|
138
|
+
const targetSessionId = normalizeSessionId(args.agent_id || "");
|
|
139
|
+
if (!targetSessionId) {
|
|
140
|
+
return { error: "read_agent_events: agent_id is required" };
|
|
141
|
+
}
|
|
142
|
+
// Lineage / target gate
|
|
143
|
+
if (!isTuner) {
|
|
144
|
+
if (targetSessionId === callerSessionId) {
|
|
145
|
+
return { error: "read_agent_events: cannot read your own session events" };
|
|
146
|
+
}
|
|
147
|
+
let descendants = [];
|
|
148
|
+
try {
|
|
149
|
+
descendants = await catalog.getDescendantSessionIds(callerSessionId);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return { error: `read_agent_events: descendant lookup failed: ${err?.message || String(err)}` };
|
|
153
|
+
}
|
|
154
|
+
if (!descendants.includes(targetSessionId)) {
|
|
155
|
+
return {
|
|
156
|
+
error: `read_agent_events: agent_id ${targetSessionId.slice(0, 8)} is not a descendant of your session. ` +
|
|
157
|
+
`You may only read events for sessions you (or your descendants) spawned.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Target row check (existence + system-agent guard).
|
|
162
|
+
// `getSession` filters out soft-deleted rows. For non-tuner callers
|
|
163
|
+
// the lineage gate above already filters those out; for the tuner we
|
|
164
|
+
// still attempt to read events (events are not deleted with the row).
|
|
165
|
+
let targetRow;
|
|
166
|
+
try {
|
|
167
|
+
targetRow = await catalog.getSession(targetSessionId);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return { error: `read_agent_events: session lookup failed: ${err?.message || String(err)}` };
|
|
171
|
+
}
|
|
172
|
+
if (targetRow?.isSystem && !isTuner && SYSTEM_AGENT_IDS.has(targetRow.agentId ?? "")) {
|
|
173
|
+
return { error: "read_agent_events: cannot read events for a system agent session" };
|
|
174
|
+
}
|
|
175
|
+
const limit = clampLimit(args.limit);
|
|
176
|
+
const cursor = typeof args.cursor === "number" && args.cursor > 0 ? args.cursor : null;
|
|
177
|
+
// Fetch a page.
|
|
178
|
+
// - cursor == null: return newest `limit` events. We use getSessionEvents with
|
|
179
|
+
// a large after_seq=0 then take the tail. To avoid pulling massive history,
|
|
180
|
+
// call getSessionEventsBefore with before_seq = MAX_SAFE_INTEGER which the
|
|
181
|
+
// stored proc treats as "give me the newest <limit>" via its DESC + LIMIT
|
|
182
|
+
// internal path.
|
|
183
|
+
// - cursor > 0: events strictly older than cursor.
|
|
184
|
+
let pageEvents;
|
|
185
|
+
try {
|
|
186
|
+
if (cursor != null) {
|
|
187
|
+
pageEvents = await catalog.getSessionEventsBefore(targetSessionId, cursor, limit);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// The "before" proc with a huge sentinel returns the newest <limit>
|
|
191
|
+
// ascending — exactly what we want for the tail.
|
|
192
|
+
pageEvents = await catalog.getSessionEventsBefore(targetSessionId, Number.MAX_SAFE_INTEGER, limit);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
return { error: `read_agent_events: event fetch failed: ${err?.message || String(err)}` };
|
|
197
|
+
}
|
|
198
|
+
// Apply event_types filter (post-fetch — pagination cursors are still
|
|
199
|
+
// anchored to the underlying page boundaries).
|
|
200
|
+
const filterTypes = Array.isArray(args.event_types) && args.event_types.length > 0
|
|
201
|
+
? new Set(args.event_types)
|
|
202
|
+
: null;
|
|
203
|
+
const filteredEvents = filterTypes
|
|
204
|
+
? pageEvents.filter((event) => filterTypes.has(event.eventType))
|
|
205
|
+
: pageEvents;
|
|
206
|
+
const { serialized, hasMore: tokenTruncated } = serializeEvents(filteredEvents);
|
|
207
|
+
const firstSeq = pageEvents.length > 0 ? Number(pageEvents[0].seq) : null;
|
|
208
|
+
const lastSeq = pageEvents.length > 0 ? Number(pageEvents[pageEvents.length - 1].seq) : null;
|
|
209
|
+
// hasMore is anchored on the underlying (unfiltered) page so the LLM
|
|
210
|
+
// can keep walking even if its filter dropped everything in this page.
|
|
211
|
+
const hasMoreOlder = pageEvents.length === limit || tokenTruncated;
|
|
212
|
+
const prevCursor = hasMoreOlder && firstSeq != null ? firstSeq : null;
|
|
213
|
+
const nextCursor = cursor != null && lastSeq != null ? lastSeq : null;
|
|
214
|
+
const deletedAt = targetRow && targetRow.deletedAt
|
|
215
|
+
? (targetRow.deletedAt instanceof Date
|
|
216
|
+
? targetRow.deletedAt.toISOString()
|
|
217
|
+
: String(targetRow.deletedAt))
|
|
218
|
+
: null;
|
|
219
|
+
const targetMissing = !targetRow;
|
|
220
|
+
const noEvents = pageEvents.length === 0;
|
|
221
|
+
return {
|
|
222
|
+
agentId: targetSessionId,
|
|
223
|
+
events: serialized,
|
|
224
|
+
prevCursor,
|
|
225
|
+
nextCursor,
|
|
226
|
+
hasMore: hasMoreOlder,
|
|
227
|
+
...(deletedAt ? { deletedAt } : {}),
|
|
228
|
+
...(targetMissing && noEvents ? { deleted: true } : {}),
|
|
229
|
+
...(targetMissing && !noEvents ? { deletedAt: "unknown" } : {}),
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
const listAllSessionsTool = defineTool("list_all_sessions", {
|
|
234
|
+
description: "List every session in the system (CMS only, no orchestration fan-out). " +
|
|
235
|
+
"Use to locate a target by description, owner, or agent. Leave owner filters unset for normal system-session discovery; only set them when the user explicitly asks to scope by owner, user, system, or unowned sessions. " +
|
|
236
|
+
"Returns a compact view: id, title, owner, agentId, parentSessionId, model, state, isSystem, deletedAt.",
|
|
237
|
+
parameters: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
limit: { type: "number", description: "Cap returned rows (default 100, max 500)." },
|
|
241
|
+
include_system: { type: "boolean", description: "Include system-agent sessions. Default true for system agents." },
|
|
242
|
+
agent_id_filter: { type: "string", description: "Optional substring match on agentId." },
|
|
243
|
+
owner_query: { type: "string", description: "Optional substring match across owner display name, email, subject, or provider. Not for session titles or agent names." },
|
|
244
|
+
owner_kind: { type: "string", enum: ["user", "system", "unowned"], description: "Optional owner bucket filter. Use only when explicitly requested." },
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
handler: async (args) => {
|
|
248
|
+
const includeSystem = args.include_system !== false;
|
|
249
|
+
const cap = Math.min(Math.max(1, Number(args.limit) || 100), 500);
|
|
250
|
+
try {
|
|
251
|
+
const rows = await catalog.listSessions();
|
|
252
|
+
const filterAgent = (args.agent_id_filter || "").toLowerCase();
|
|
253
|
+
const filtered = rows.filter((r) => {
|
|
254
|
+
if (!matchesSessionOwnerFilters(r, {
|
|
255
|
+
includeSystem,
|
|
256
|
+
ownerQuery: args.owner_query,
|
|
257
|
+
ownerKind: args.owner_kind,
|
|
258
|
+
}))
|
|
259
|
+
return false;
|
|
260
|
+
if (filterAgent && !(r.agentId ?? "").toLowerCase().includes(filterAgent))
|
|
261
|
+
return false;
|
|
262
|
+
return true;
|
|
263
|
+
}).slice(0, cap);
|
|
264
|
+
return {
|
|
265
|
+
count: filtered.length,
|
|
266
|
+
truncated: rows.length > cap,
|
|
267
|
+
sessions: filtered.map((r) => ({
|
|
268
|
+
sessionId: r.sessionId,
|
|
269
|
+
title: r.title ?? null,
|
|
270
|
+
ownerKind: getSessionOwnerKind(r),
|
|
271
|
+
ownerLabel: formatSessionOwnerLabel(r),
|
|
272
|
+
owner: r.owner ?? null,
|
|
273
|
+
agentId: r.agentId ?? null,
|
|
274
|
+
parentSessionId: r.parentSessionId ?? null,
|
|
275
|
+
model: r.model ?? null,
|
|
276
|
+
state: r.state,
|
|
277
|
+
iterations: r.currentIteration ?? 0,
|
|
278
|
+
isSystem: !!r.isSystem,
|
|
279
|
+
createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
|
|
280
|
+
deletedAt: r.deletedAt
|
|
281
|
+
? (r.deletedAt instanceof Date ? r.deletedAt.toISOString() : String(r.deletedAt))
|
|
282
|
+
: null,
|
|
283
|
+
})),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
return { error: `list_all_sessions: ${err?.message || String(err)}` };
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const readSessionInfoTool = defineTool("read_session_info", {
|
|
292
|
+
description: "Read the full CMS row for a session (any session — not just descendants). " +
|
|
293
|
+
"Title, owner, agent, model, parent, status, iterations, last error, wait reason, timestamps.",
|
|
294
|
+
parameters: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: { session_id: { type: "string" } },
|
|
297
|
+
required: ["session_id"],
|
|
298
|
+
},
|
|
299
|
+
handler: async (args) => {
|
|
300
|
+
const id = normalizeSessionId(args.session_id);
|
|
301
|
+
try {
|
|
302
|
+
const row = await catalog.getSession(id);
|
|
303
|
+
if (!row)
|
|
304
|
+
return { sessionId: id, exists: false };
|
|
305
|
+
return {
|
|
306
|
+
sessionId: row.sessionId,
|
|
307
|
+
exists: true,
|
|
308
|
+
title: row.title ?? null,
|
|
309
|
+
ownerKind: getSessionOwnerKind(row),
|
|
310
|
+
ownerLabel: formatSessionOwnerLabel(row),
|
|
311
|
+
owner: row.owner ?? null,
|
|
312
|
+
agentId: row.agentId ?? null,
|
|
313
|
+
parentSessionId: row.parentSessionId ?? null,
|
|
314
|
+
model: row.model ?? null,
|
|
315
|
+
state: row.state,
|
|
316
|
+
iterations: row.currentIteration ?? 0,
|
|
317
|
+
isSystem: !!row.isSystem,
|
|
318
|
+
lastError: row.lastError ?? null,
|
|
319
|
+
waitReason: row.waitReason ?? null,
|
|
320
|
+
splash: row.splash ?? null,
|
|
321
|
+
createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt),
|
|
322
|
+
updatedAt: row.updatedAt
|
|
323
|
+
? (row.updatedAt instanceof Date ? row.updatedAt.toISOString() : String(row.updatedAt))
|
|
324
|
+
: null,
|
|
325
|
+
deletedAt: row.deletedAt
|
|
326
|
+
? (row.deletedAt instanceof Date ? row.deletedAt.toISOString() : String(row.deletedAt))
|
|
327
|
+
: null,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
return { error: `read_session_info: ${err?.message || String(err)}` };
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const readUserStatsTool = defineTool("read_user_stats", {
|
|
336
|
+
description: "Read owner-bucketed session, token, snapshot, and orchestration-history totals. " +
|
|
337
|
+
"Use this for ownership-aware usage questions and to compare specific users or cohorts.",
|
|
338
|
+
parameters: {
|
|
339
|
+
type: "object",
|
|
340
|
+
properties: {
|
|
341
|
+
include_deleted: { type: "boolean", description: "Default false." },
|
|
342
|
+
since_iso: { type: "string", description: "Optional ISO timestamp lower bound on session_created_at." },
|
|
343
|
+
owner_query: { type: "string", description: "Optional substring match across owner display name, email, subject, or provider." },
|
|
344
|
+
owner_kind: { type: "string", enum: ["user", "system", "unowned"], description: "Optional owner bucket filter." },
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
handler: async (args) => {
|
|
348
|
+
try {
|
|
349
|
+
const opts = {};
|
|
350
|
+
if (args.include_deleted)
|
|
351
|
+
opts.includeDeleted = true;
|
|
352
|
+
if (args.since_iso) {
|
|
353
|
+
const d = new Date(args.since_iso);
|
|
354
|
+
if (Number.isNaN(d.getTime()))
|
|
355
|
+
return { error: "read_user_stats: invalid since_iso" };
|
|
356
|
+
opts.since = d;
|
|
357
|
+
}
|
|
358
|
+
const stats = await catalog.getUserStats(opts);
|
|
359
|
+
const users = stats.users
|
|
360
|
+
.filter((bucket) => matchesOwnerBucketFilters(bucket, {
|
|
361
|
+
ownerQuery: args.owner_query,
|
|
362
|
+
ownerKind: args.owner_kind,
|
|
363
|
+
}))
|
|
364
|
+
.map((bucket) => ({
|
|
365
|
+
...bucket,
|
|
366
|
+
ownerLabel: formatOwnerBucketLabel(bucket),
|
|
367
|
+
}));
|
|
368
|
+
const totals = users.reduce((acc, bucket) => {
|
|
369
|
+
acc.sessionCount += bucket.sessionCount || 0;
|
|
370
|
+
acc.totalSnapshotSizeBytes += bucket.totalSnapshotSizeBytes || 0;
|
|
371
|
+
acc.totalOrchestrationHistorySizeBytes += bucket.totalOrchestrationHistorySizeBytes || 0;
|
|
372
|
+
acc.totalTokensInput += bucket.totalTokensInput || 0;
|
|
373
|
+
acc.totalTokensOutput += bucket.totalTokensOutput || 0;
|
|
374
|
+
acc.totalTokensCacheRead += bucket.totalTokensCacheRead || 0;
|
|
375
|
+
acc.totalTokensCacheWrite += bucket.totalTokensCacheWrite || 0;
|
|
376
|
+
return acc;
|
|
377
|
+
}, {
|
|
378
|
+
sessionCount: 0,
|
|
379
|
+
totalSnapshotSizeBytes: 0,
|
|
380
|
+
totalOrchestrationHistorySizeBytes: 0,
|
|
381
|
+
totalTokensInput: 0,
|
|
382
|
+
totalTokensOutput: 0,
|
|
383
|
+
totalTokensCacheRead: 0,
|
|
384
|
+
totalTokensCacheWrite: 0,
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
windowStart: stats.windowStart,
|
|
388
|
+
earliestSessionCreatedAt: stats.earliestSessionCreatedAt,
|
|
389
|
+
users,
|
|
390
|
+
totals: {
|
|
391
|
+
...totals,
|
|
392
|
+
cacheHitRatio: totals.totalTokensInput > 0
|
|
393
|
+
? totals.totalTokensCacheRead / totals.totalTokensInput
|
|
394
|
+
: null,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
return { error: `read_user_stats: ${err?.message || String(err)}` };
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
if (!isSystemAgent) {
|
|
404
|
+
return [readAgentEventsTool];
|
|
405
|
+
}
|
|
406
|
+
const systemReadTools = [listAllSessionsTool, readSessionInfoTool, readUserStatsTool];
|
|
407
|
+
if (!isTuner) {
|
|
408
|
+
return [readAgentEventsTool, ...systemReadTools];
|
|
409
|
+
}
|
|
410
|
+
// ─── Tuner-only read tools ─────────────────────────────────────────────
|
|
411
|
+
// Bypass the lineage gate; expose CMS state, metric summaries, and
|
|
412
|
+
// (when a duroxide client is provided) orchestration stats and history.
|
|
413
|
+
const readSessionMetricSummaryTool = defineTool("read_session_metric_summary", {
|
|
414
|
+
description: "Read durable metric summary for a session: tokens (input/output/cache_read/cache_write), " +
|
|
415
|
+
"snapshot bytes, dehydration / hydration / lossy-handoff counts, last hydrated/dehydrated/checkpoint timestamps.",
|
|
416
|
+
parameters: {
|
|
417
|
+
type: "object",
|
|
418
|
+
properties: { session_id: { type: "string" } },
|
|
419
|
+
required: ["session_id"],
|
|
420
|
+
},
|
|
421
|
+
handler: async (args) => {
|
|
422
|
+
const id = normalizeSessionId(args.session_id);
|
|
423
|
+
try {
|
|
424
|
+
const summary = await catalog.getSessionMetricSummary(id);
|
|
425
|
+
if (!summary)
|
|
426
|
+
return { sessionId: id, exists: false };
|
|
427
|
+
return { sessionId: id, exists: true, summary };
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
return { error: `read_session_metric_summary: ${err?.message || String(err)}` };
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
const readSessionTreeStatsTool = defineTool("read_session_tree_stats", {
|
|
435
|
+
description: "Read rolled-up stats across the spawn tree rooted at the given session: " +
|
|
436
|
+
"tokens, snapshot bytes, dehydrations, hydrations, per-descendant breakdown.",
|
|
437
|
+
parameters: {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: { session_id: { type: "string" } },
|
|
440
|
+
required: ["session_id"],
|
|
441
|
+
},
|
|
442
|
+
handler: async (args) => {
|
|
443
|
+
const id = normalizeSessionId(args.session_id);
|
|
444
|
+
try {
|
|
445
|
+
const tree = await catalog.getSessionTreeStats(id);
|
|
446
|
+
if (!tree)
|
|
447
|
+
return { sessionId: id, exists: false };
|
|
448
|
+
return { sessionId: id, exists: true, tree };
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
return { error: `read_session_tree_stats: ${err?.message || String(err)}` };
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
const readFleetStatsTool = defineTool("read_fleet_stats", {
|
|
456
|
+
description: "Read fleet-wide stats aggregates broken down by agent and model. " +
|
|
457
|
+
"Use for cross-session baselines and to spot outliers.",
|
|
458
|
+
parameters: {
|
|
459
|
+
type: "object",
|
|
460
|
+
properties: {
|
|
461
|
+
include_deleted: { type: "boolean", description: "Default false." },
|
|
462
|
+
since_iso: { type: "string", description: "Optional ISO timestamp lower bound on session_created_at." },
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
handler: async (args) => {
|
|
466
|
+
try {
|
|
467
|
+
const opts = {};
|
|
468
|
+
if (args.include_deleted)
|
|
469
|
+
opts.includeDeleted = true;
|
|
470
|
+
if (args.since_iso) {
|
|
471
|
+
const d = new Date(args.since_iso);
|
|
472
|
+
if (Number.isNaN(d.getTime()))
|
|
473
|
+
return { error: "read_fleet_stats: invalid since_iso" };
|
|
474
|
+
opts.since = d;
|
|
475
|
+
}
|
|
476
|
+
const stats = await catalog.getFleetStats(opts);
|
|
477
|
+
return stats;
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
return { error: `read_fleet_stats: ${err?.message || String(err)}` };
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
const readSessionSkillUsageTool = defineTool("read_session_skill_usage", {
|
|
485
|
+
description: "Read per-session skill usage. Returns one row per (kind, name, plugin) " +
|
|
486
|
+
"where kind is 'static' (Copilot SDK skill.invoked) or 'learned' (read_facts " +
|
|
487
|
+
"against the skills/ knowledge namespace). Useful for verifying which skills " +
|
|
488
|
+
"an agent actually consumed during a session.",
|
|
489
|
+
parameters: {
|
|
490
|
+
type: "object",
|
|
491
|
+
properties: {
|
|
492
|
+
session_id: { type: "string" },
|
|
493
|
+
since_iso: { type: "string", description: "Optional ISO timestamp lower bound." },
|
|
494
|
+
},
|
|
495
|
+
required: ["session_id"],
|
|
496
|
+
},
|
|
497
|
+
handler: async (args) => {
|
|
498
|
+
const id = normalizeSessionId(args.session_id);
|
|
499
|
+
try {
|
|
500
|
+
const opts = {};
|
|
501
|
+
if (args.since_iso) {
|
|
502
|
+
const d = new Date(args.since_iso);
|
|
503
|
+
if (Number.isNaN(d.getTime()))
|
|
504
|
+
return { error: "read_session_skill_usage: invalid since_iso" };
|
|
505
|
+
opts.since = d;
|
|
506
|
+
}
|
|
507
|
+
const skills = await catalog.getSessionSkillUsage(id, opts);
|
|
508
|
+
return { sessionId: id, skills, totalInvocations: skills.reduce((a, s) => a + s.invocations, 0) };
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
return { error: `read_session_skill_usage: ${err?.message || String(err)}` };
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
const readSessionTreeSkillUsageTool = defineTool("read_session_tree_skill_usage", {
|
|
516
|
+
description: "Read skill usage rolled up across the spawn tree rooted at the given session. " +
|
|
517
|
+
"Returns per-session breakdown plus a flat rolled-up summary across the whole tree. " +
|
|
518
|
+
"Each row carries kind ('static' | 'learned'), name, plugin metadata, and counts.",
|
|
519
|
+
parameters: {
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: {
|
|
522
|
+
session_id: { type: "string" },
|
|
523
|
+
since_iso: { type: "string", description: "Optional ISO timestamp lower bound." },
|
|
524
|
+
},
|
|
525
|
+
required: ["session_id"],
|
|
526
|
+
},
|
|
527
|
+
handler: async (args) => {
|
|
528
|
+
const id = normalizeSessionId(args.session_id);
|
|
529
|
+
try {
|
|
530
|
+
const opts = {};
|
|
531
|
+
if (args.since_iso) {
|
|
532
|
+
const d = new Date(args.since_iso);
|
|
533
|
+
if (Number.isNaN(d.getTime()))
|
|
534
|
+
return { error: "read_session_tree_skill_usage: invalid since_iso" };
|
|
535
|
+
opts.since = d;
|
|
536
|
+
}
|
|
537
|
+
const tree = await catalog.getSessionTreeSkillUsage(id, opts);
|
|
538
|
+
return tree;
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
return { error: `read_session_tree_skill_usage: ${err?.message || String(err)}` };
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
const readFleetSkillUsageTool = defineTool("read_fleet_skill_usage", {
|
|
546
|
+
description: "Read fleet-wide skill usage broken down by agent and skill kind (static | learned). " +
|
|
547
|
+
"Use for spotting unused or hot skills across all agents. Always pass since_iso for " +
|
|
548
|
+
"the default UI window (e.g. last 7 days) to keep the scan bounded.",
|
|
549
|
+
parameters: {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties: {
|
|
552
|
+
include_deleted: { type: "boolean", description: "Default false." },
|
|
553
|
+
since_iso: { type: "string", description: "Optional ISO timestamp lower bound on event time." },
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
handler: async (args) => {
|
|
557
|
+
try {
|
|
558
|
+
const opts = {};
|
|
559
|
+
if (args.include_deleted)
|
|
560
|
+
opts.includeDeleted = true;
|
|
561
|
+
if (args.since_iso) {
|
|
562
|
+
const d = new Date(args.since_iso);
|
|
563
|
+
if (Number.isNaN(d.getTime()))
|
|
564
|
+
return { error: "read_fleet_skill_usage: invalid since_iso" };
|
|
565
|
+
opts.since = d;
|
|
566
|
+
}
|
|
567
|
+
return await catalog.getFleetSkillUsage(opts);
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
return { error: `read_fleet_skill_usage: ${err?.message || String(err)}` };
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
const factsTools = [];
|
|
575
|
+
if (factStore) {
|
|
576
|
+
factsTools.push(defineTool("read_session_facts_stats", {
|
|
577
|
+
description: "Read per-session non-shared facts grouped by knowledge namespace " +
|
|
578
|
+
"(skills | asks | intake | config | (other)). Returns counts and " +
|
|
579
|
+
"value-byte totals only — never the fact values themselves. " +
|
|
580
|
+
"Use to spot sessions producing unusually large facts payloads.",
|
|
581
|
+
parameters: {
|
|
582
|
+
type: "object",
|
|
583
|
+
properties: { session_id: { type: "string" } },
|
|
584
|
+
required: ["session_id"],
|
|
585
|
+
},
|
|
586
|
+
handler: async (args) => {
|
|
587
|
+
const id = normalizeSessionId(args.session_id);
|
|
588
|
+
try {
|
|
589
|
+
const rows = await factStore.getSessionFactsStats(id);
|
|
590
|
+
return {
|
|
591
|
+
sessionId: id,
|
|
592
|
+
rows,
|
|
593
|
+
totalCount: rows.reduce((a, r) => a + r.factCount, 0),
|
|
594
|
+
totalBytes: rows.reduce((a, r) => a + r.totalValueBytes, 0),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
return { error: `read_session_facts_stats: ${err?.message || String(err)}` };
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
}));
|
|
602
|
+
factsTools.push(defineTool("read_session_tree_facts_stats", {
|
|
603
|
+
description: "Read facts stats rolled up across the spawn tree rooted at a session. " +
|
|
604
|
+
"Resolves descendants from the CMS first, then aggregates in the " +
|
|
605
|
+
"facts schema. Same row shape as read_session_facts_stats.",
|
|
606
|
+
parameters: {
|
|
607
|
+
type: "object",
|
|
608
|
+
properties: { session_id: { type: "string" } },
|
|
609
|
+
required: ["session_id"],
|
|
610
|
+
},
|
|
611
|
+
handler: async (args) => {
|
|
612
|
+
const id = normalizeSessionId(args.session_id);
|
|
613
|
+
try {
|
|
614
|
+
const descendants = await catalog.getDescendantSessionIds(id);
|
|
615
|
+
const ids = Array.from(new Set([id, ...descendants]));
|
|
616
|
+
const rolledUp = await factStore.getFactsStatsForSessions(ids);
|
|
617
|
+
return {
|
|
618
|
+
rootSessionId: id,
|
|
619
|
+
sessionIds: ids,
|
|
620
|
+
rolledUp,
|
|
621
|
+
totalCount: rolledUp.reduce((a, r) => a + r.factCount, 0),
|
|
622
|
+
totalBytes: rolledUp.reduce((a, r) => a + r.totalValueBytes, 0),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
return { error: `read_session_tree_facts_stats: ${err?.message || String(err)}` };
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
}));
|
|
630
|
+
factsTools.push(defineTool("read_shared_facts_stats", {
|
|
631
|
+
description: "Read shared (cross-session) facts grouped by knowledge namespace. " +
|
|
632
|
+
"Use to verify Facts Manager output (curated 'skills/' growth) and to " +
|
|
633
|
+
"spot stalled or runaway shared-fact production.",
|
|
634
|
+
parameters: { type: "object", properties: {} },
|
|
635
|
+
handler: async () => {
|
|
636
|
+
try {
|
|
637
|
+
const rows = await factStore.getSharedFactsStats();
|
|
638
|
+
return {
|
|
639
|
+
rows,
|
|
640
|
+
totalCount: rows.reduce((a, r) => a + r.factCount, 0),
|
|
641
|
+
totalBytes: rows.reduce((a, r) => a + r.totalValueBytes, 0),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
catch (err) {
|
|
645
|
+
return { error: `read_shared_facts_stats: ${err?.message || String(err)}` };
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
const tools = [
|
|
651
|
+
readAgentEventsTool,
|
|
652
|
+
...systemReadTools,
|
|
653
|
+
readSessionMetricSummaryTool,
|
|
654
|
+
readSessionTreeStatsTool,
|
|
655
|
+
readFleetStatsTool,
|
|
656
|
+
readSessionSkillUsageTool,
|
|
657
|
+
readSessionTreeSkillUsageTool,
|
|
658
|
+
readFleetSkillUsageTool,
|
|
659
|
+
...factsTools,
|
|
660
|
+
];
|
|
661
|
+
if (duroxideClient) {
|
|
662
|
+
const readOrchestrationStatsTool = defineTool("read_orchestration_stats", {
|
|
663
|
+
description: "Read duroxide runtime stats for the orchestration backing a session: " +
|
|
664
|
+
"history event count + bytes, queue pending count, KV key count + bytes, current orchestrationVersion.",
|
|
665
|
+
parameters: {
|
|
666
|
+
type: "object",
|
|
667
|
+
properties: { session_id: { type: "string" } },
|
|
668
|
+
required: ["session_id"],
|
|
669
|
+
},
|
|
670
|
+
handler: async (args) => {
|
|
671
|
+
const id = normalizeSessionId(args.session_id);
|
|
672
|
+
const orchId = `session-${id}`;
|
|
673
|
+
try {
|
|
674
|
+
const [statsRes, infoRes] = await Promise.allSettled([
|
|
675
|
+
duroxideClient.getOrchestrationStats(orchId),
|
|
676
|
+
duroxideClient.getInstanceInfo(orchId),
|
|
677
|
+
]);
|
|
678
|
+
const out = { sessionId: id };
|
|
679
|
+
if (statsRes.status === "fulfilled" && statsRes.value && typeof statsRes.value === "object") {
|
|
680
|
+
const s = statsRes.value;
|
|
681
|
+
for (const k of [
|
|
682
|
+
"historyEventCount", "historySizeBytes", "queuePendingCount",
|
|
683
|
+
"kvUserKeyCount", "kvTotalValueBytes",
|
|
684
|
+
]) {
|
|
685
|
+
const n = Number(s[k]);
|
|
686
|
+
if (Number.isFinite(n))
|
|
687
|
+
out[k] = n;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (infoRes.status === "fulfilled" && infoRes.value) {
|
|
691
|
+
const info = infoRes.value;
|
|
692
|
+
if (typeof info.orchestrationVersion === "string")
|
|
693
|
+
out.orchestrationVersion = info.orchestrationVersion;
|
|
694
|
+
if (typeof info.status === "string")
|
|
695
|
+
out.orchestrationStatus = info.status;
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
return { error: `read_orchestration_stats: ${err?.message || String(err)}` };
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
const readExecutionHistoryTool = defineTool("read_execution_history", {
|
|
705
|
+
description: "Read the raw duroxide execution history for a session's current (or specified) execution. " +
|
|
706
|
+
"Definitive ground truth for replay and nondeterminism investigations. " +
|
|
707
|
+
"Use sparingly — history can be large; prefer paginating via limit / offset.",
|
|
708
|
+
parameters: {
|
|
709
|
+
type: "object",
|
|
710
|
+
properties: {
|
|
711
|
+
session_id: { type: "string" },
|
|
712
|
+
execution_id: { type: "number", description: "Optional. Defaults to the latest execution." },
|
|
713
|
+
limit: { type: "number", description: "Max events to return (default 100, hard cap 500)." },
|
|
714
|
+
offset: { type: "number", description: "Number of events to skip from the start." },
|
|
715
|
+
},
|
|
716
|
+
required: ["session_id"],
|
|
717
|
+
},
|
|
718
|
+
handler: async (args) => {
|
|
719
|
+
const id = normalizeSessionId(args.session_id);
|
|
720
|
+
const orchId = `session-${id}`;
|
|
721
|
+
const cap = Math.min(Math.max(1, Number(args.limit) || 100), 500);
|
|
722
|
+
const offset = Math.max(0, Number(args.offset) || 0);
|
|
723
|
+
try {
|
|
724
|
+
let execId = args.execution_id;
|
|
725
|
+
if (execId == null) {
|
|
726
|
+
const executions = await duroxideClient.listExecutions(orchId);
|
|
727
|
+
if (!Array.isArray(executions) || executions.length === 0) {
|
|
728
|
+
return { sessionId: id, executionId: null, events: [], hasMore: false };
|
|
729
|
+
}
|
|
730
|
+
execId = executions[executions.length - 1];
|
|
731
|
+
}
|
|
732
|
+
const events = await duroxideClient.readExecutionHistory(orchId, execId);
|
|
733
|
+
if (!Array.isArray(events)) {
|
|
734
|
+
return { sessionId: id, executionId: execId, events: [], hasMore: false };
|
|
735
|
+
}
|
|
736
|
+
const slice = events.slice(offset, offset + cap);
|
|
737
|
+
return {
|
|
738
|
+
sessionId: id,
|
|
739
|
+
executionId: execId,
|
|
740
|
+
totalCount: events.length,
|
|
741
|
+
offset,
|
|
742
|
+
events: slice.map((e) => ({
|
|
743
|
+
eventId: Number(e.eventId) || 0,
|
|
744
|
+
kind: String(e.kind || ""),
|
|
745
|
+
...(e.sourceEventId != null ? { sourceEventId: Number(e.sourceEventId) } : {}),
|
|
746
|
+
timestampMs: Number(e.timestampMs) || 0,
|
|
747
|
+
...(e.data != null ? { data: String(e.data).slice(0, MAX_DATA_BYTES) } : {}),
|
|
748
|
+
})),
|
|
749
|
+
hasMore: offset + slice.length < events.length,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
catch (err) {
|
|
753
|
+
return { error: `read_execution_history: ${err?.message || String(err)}` };
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
const listOrchestrationsByStatusTool = defineTool("list_orchestrations_by_status", {
|
|
758
|
+
description: "List duroxide orchestration instances by lifecycle status. " +
|
|
759
|
+
"Use to find every Running / Failed / Suspended / Completed / Terminated orchestration across the fleet.",
|
|
760
|
+
parameters: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
status: {
|
|
764
|
+
type: "string",
|
|
765
|
+
enum: ["Running", "Failed", "Suspended", "Completed", "Terminated"],
|
|
766
|
+
},
|
|
767
|
+
limit: { type: "number", description: "Cap returned rows (default 100, max 500)." },
|
|
768
|
+
},
|
|
769
|
+
required: ["status"],
|
|
770
|
+
},
|
|
771
|
+
handler: async (args) => {
|
|
772
|
+
const cap = Math.min(Math.max(1, Number(args.limit) || 100), 500);
|
|
773
|
+
try {
|
|
774
|
+
const instances = await duroxideClient.listInstancesByStatus(args.status);
|
|
775
|
+
const arr = Array.isArray(instances) ? instances : [];
|
|
776
|
+
const slice = arr.slice(0, cap);
|
|
777
|
+
return {
|
|
778
|
+
status: args.status,
|
|
779
|
+
totalCount: arr.length,
|
|
780
|
+
truncated: arr.length > cap,
|
|
781
|
+
instances: slice.map((inst) => ({
|
|
782
|
+
orchestrationId: String(inst?.instanceId ?? inst?.orchId ?? ""),
|
|
783
|
+
sessionId: typeof inst?.instanceId === "string" && inst.instanceId.startsWith("session-")
|
|
784
|
+
? inst.instanceId.slice("session-".length)
|
|
785
|
+
: null,
|
|
786
|
+
status: String(inst?.status ?? ""),
|
|
787
|
+
...(inst?.orchestrationVersion ? { orchestrationVersion: String(inst.orchestrationVersion) } : {}),
|
|
788
|
+
})),
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
return { error: `list_orchestrations_by_status: ${err?.message || String(err)}` };
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
tools.push(readOrchestrationStatsTool, readExecutionHistoryTool, listOrchestrationsByStatusTool);
|
|
797
|
+
}
|
|
798
|
+
return tools;
|
|
799
|
+
}
|
|
800
|
+
//# sourceMappingURL=inspect-tools.js.map
|