pi-gentic 0.1.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/README.md +499 -0
- package/dist/commands.d.ts +93 -0
- package/dist/commands.js +422 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +407 -0
- package/dist/core.d.ts +19 -0
- package/dist/core.js +125 -0
- package/dist/extension.d.ts +2 -0
- package/dist/extension.js +476 -0
- package/dist/orchestrator.d.ts +277 -0
- package/dist/orchestrator.js +633 -0
- package/dist/policy.d.ts +43 -0
- package/dist/policy.js +136 -0
- package/dist/prompt.d.ts +12 -0
- package/dist/prompt.js +226 -0
- package/dist/runs.d.ts +99 -0
- package/dist/runs.js +540 -0
- package/dist/runtime.d.ts +127 -0
- package/dist/runtime.js +360 -0
- package/dist/sessions.d.ts +56 -0
- package/dist/sessions.js +487 -0
- package/dist/ui.d.ts +108 -0
- package/dist/ui.js +957 -0
- package/dist/worktrees.d.ts +1 -0
- package/dist/worktrees.js +86 -0
- package/docs/assets/error-card.png +0 -0
- package/docs/assets/load-agent.png +0 -0
- package/docs/assets/orchestration-tree.png +0 -0
- package/docs/assets/send-background.png +0 -0
- package/docs/assets/send-foreground.png +0 -0
- package/package.json +58 -0
package/dist/runs.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run lifecycle helpers.
|
|
3
|
+
*
|
|
4
|
+
* A send action can start work, queue work, stream activity updates, return a
|
|
5
|
+
* final answer, or report why the target session stopped.
|
|
6
|
+
*/
|
|
7
|
+
import { formatDuration, getErrorMessage, shortSessionId } from "./core.js";
|
|
8
|
+
import { getActiveState } from "./policy.js";
|
|
9
|
+
export function abortActor(ctx) {
|
|
10
|
+
const agentName = getActiveState(ctx.sessionManager).agentName;
|
|
11
|
+
return agentName ? `[${agentName}] agent` : "caller session";
|
|
12
|
+
}
|
|
13
|
+
export function shouldDeferSendCompletion({ async, awaitCompletion, } = {}) {
|
|
14
|
+
return async === true || awaitCompletion === false;
|
|
15
|
+
}
|
|
16
|
+
export function sendPendingText({ async, agentName, sessionId, message, details, }) {
|
|
17
|
+
return async === true
|
|
18
|
+
? sendConfirmationText(agentName, sessionId, message, {
|
|
19
|
+
queued: details?.status === "queued",
|
|
20
|
+
})
|
|
21
|
+
: sendStatusText(details);
|
|
22
|
+
}
|
|
23
|
+
export function sendConfirmationText(agentName, sessionId, message, options = {}) {
|
|
24
|
+
const target = agentName ? `[${agentName}] agent` : "agent";
|
|
25
|
+
const action = options.queued ? "Queued message for" : "Sent message to";
|
|
26
|
+
const timing = options.queued
|
|
27
|
+
? "The agent is already working and will read this message when ready."
|
|
28
|
+
: "The agent will return with a full answer once he's done.";
|
|
29
|
+
return `${action} ${target} in session ${shortSessionId(sessionId)}.\nMessage: ${message}\n${timing} Do not wait for it to return, and do not duplicate the delegated work yourself.`;
|
|
30
|
+
}
|
|
31
|
+
export function sendStatusText(details = {}) {
|
|
32
|
+
if (details.status === "done")
|
|
33
|
+
return `Agent ${details.agentName ?? ""} answered.`
|
|
34
|
+
.replace(/\s+/g, " ")
|
|
35
|
+
.trim();
|
|
36
|
+
if (details.status === "queued")
|
|
37
|
+
return `Queued message for ${details.agentName ?? "agent"}.`;
|
|
38
|
+
if (details.status === "stopped")
|
|
39
|
+
return details.error ?? "Agent stopped before answering.";
|
|
40
|
+
if (details.status === "error")
|
|
41
|
+
return details.error ?? "Agent call failed.";
|
|
42
|
+
return `Sending message to ${details.agentName ?? "agent"}...`;
|
|
43
|
+
}
|
|
44
|
+
export function deliverSendContextToCaller({ pi, ctx, target, message, async, fork, }) {
|
|
45
|
+
if (ctx.isIdle?.() === false)
|
|
46
|
+
return;
|
|
47
|
+
const sessionId = target.session.sessionManager.getSessionId();
|
|
48
|
+
const content = [
|
|
49
|
+
"pi-gentic sent a message to another session.",
|
|
50
|
+
`Target agent: ${target.agentName ?? "agentless"}`,
|
|
51
|
+
`Target session: ${sessionId}`,
|
|
52
|
+
`Async: ${async === true}`,
|
|
53
|
+
`Fork: ${fork === true}`,
|
|
54
|
+
`Message: ${message}`,
|
|
55
|
+
].join("\n");
|
|
56
|
+
try {
|
|
57
|
+
pi.sendMessage({
|
|
58
|
+
customType: "pi-gentic:send-context",
|
|
59
|
+
content,
|
|
60
|
+
display: false,
|
|
61
|
+
details: {
|
|
62
|
+
kind: "sendContext",
|
|
63
|
+
agentName: target.agentName,
|
|
64
|
+
sessionId,
|
|
65
|
+
message,
|
|
66
|
+
},
|
|
67
|
+
}, { triggerTurn: false });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Context delivery is best-effort because command contexts can become stale after session switches.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Delivers the target answer to the live caller, or persists it for later. */
|
|
74
|
+
export async function deliverReturnToCaller({ pi, ctx, callerSessionId, callerSessionManager, text, invoke, persist, invokeInactiveCaller, visibleSession, }) {
|
|
75
|
+
const liveDelivery = await deliverToLiveCaller({
|
|
76
|
+
pi,
|
|
77
|
+
ctx,
|
|
78
|
+
callerSessionId,
|
|
79
|
+
text,
|
|
80
|
+
invoke,
|
|
81
|
+
visibleSession,
|
|
82
|
+
});
|
|
83
|
+
if (liveDelivery.delivered)
|
|
84
|
+
return liveDelivery.mode;
|
|
85
|
+
if (invoke && invokeInactiveCaller) {
|
|
86
|
+
await invokeInactiveCaller(text);
|
|
87
|
+
return "background";
|
|
88
|
+
}
|
|
89
|
+
persistReturnForCaller({ callerSessionManager, text, invoke, persist });
|
|
90
|
+
return "persisted";
|
|
91
|
+
}
|
|
92
|
+
export async function deliverToLiveCaller({ pi, ctx, callerSessionId, text, invoke, visibleSession, }) {
|
|
93
|
+
if (!contextStillActive(ctx, callerSessionId))
|
|
94
|
+
return { delivered: false };
|
|
95
|
+
try {
|
|
96
|
+
if (visibleSession) {
|
|
97
|
+
if (invoke && typeof visibleSession.sendUserMessage === "function") {
|
|
98
|
+
await visibleSession.sendUserMessage(text, sendUserMessageOptions(ctx));
|
|
99
|
+
return { delivered: true, mode: "live" };
|
|
100
|
+
}
|
|
101
|
+
if (!invoke && typeof visibleSession.sendCustomMessage === "function") {
|
|
102
|
+
await visibleSession.sendCustomMessage(returnContextMessage(text), { triggerTurn: false });
|
|
103
|
+
return { delivered: true, mode: "live" };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (invoke)
|
|
107
|
+
pi.sendUserMessage(text, sendUserMessageOptions(ctx));
|
|
108
|
+
else
|
|
109
|
+
pi.sendMessage(returnContextMessage(text), { triggerTurn: false });
|
|
110
|
+
return { delivered: true, mode: "live" };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return { delivered: false };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function returnContextMessage(text) {
|
|
117
|
+
return {
|
|
118
|
+
customType: "pi-gentic:return-context",
|
|
119
|
+
content: text,
|
|
120
|
+
display: true,
|
|
121
|
+
details: { kind: "returnContext" },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function persistReturnForCaller({ callerSessionManager, text, invoke, persist, }) {
|
|
125
|
+
if (invoke)
|
|
126
|
+
callerSessionManager.appendMessage({
|
|
127
|
+
role: "user",
|
|
128
|
+
content: [{ type: "text", text }],
|
|
129
|
+
timestamp: Date.now(),
|
|
130
|
+
});
|
|
131
|
+
else
|
|
132
|
+
callerSessionManager.appendCustomMessageEntry?.("pi-gentic:return-context", text, true, { kind: "returnContext" });
|
|
133
|
+
persist?.(callerSessionManager);
|
|
134
|
+
}
|
|
135
|
+
export function sendUserMessageOptions(ctx) {
|
|
136
|
+
return ctx.isIdle?.() === false ? { deliverAs: "followUp" } : undefined;
|
|
137
|
+
}
|
|
138
|
+
export function persistSynchronousToolCard(ctx, input, result, persist) {
|
|
139
|
+
if (input.action !== "send")
|
|
140
|
+
return;
|
|
141
|
+
if (["running", "queued"].includes(String(result.details?.status ?? "")))
|
|
142
|
+
return;
|
|
143
|
+
ctx.sessionManager.appendCustomMessageEntry?.("pi-gentic:card", result.text, true, result.details);
|
|
144
|
+
persist?.(ctx.sessionManager);
|
|
145
|
+
}
|
|
146
|
+
export async function displayTargetAnswerIfVisible({ ctx, target, targetSessionId, text, }) {
|
|
147
|
+
if (!contextStillActive(ctx, targetSessionId))
|
|
148
|
+
return false;
|
|
149
|
+
const message = {
|
|
150
|
+
customType: "pi-gentic:return-context",
|
|
151
|
+
content: `Final answer from this session:\n${text}`,
|
|
152
|
+
display: true,
|
|
153
|
+
details: { kind: "targetAnswer", sessionId: targetSessionId },
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
if (typeof target.session.sendCustomMessage === "function") {
|
|
157
|
+
await target.session.sendCustomMessage(message, { triggerTurn: false });
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
target.session.sessionManager.appendCustomMessageEntry?.(message.customType, message.content, message.display, message.details);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function contextStillActive(ctx, callerSessionId) {
|
|
168
|
+
try {
|
|
169
|
+
void ctx.cwd;
|
|
170
|
+
const activeSessionId = ctx.sessionManager.getSessionId();
|
|
171
|
+
return !callerSessionId || activeSessionId === callerSessionId;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Tracks assistant and tool activity so cards can update while a run is live. */
|
|
178
|
+
export function createSessionActivityMonitor(baseDetails, publish) {
|
|
179
|
+
const state = {
|
|
180
|
+
...baseDetails,
|
|
181
|
+
activities: [],
|
|
182
|
+
updatedAt: baseDetails.updatedAt ?? Date.now(),
|
|
183
|
+
};
|
|
184
|
+
const publishState = (status = state.status, updates = {}) => {
|
|
185
|
+
Object.assign(state, updates, { status });
|
|
186
|
+
return publish({ ...state, activities: [...state.activities] });
|
|
187
|
+
};
|
|
188
|
+
const touch = () => {
|
|
189
|
+
state.updatedAt = Date.now();
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
get activities() {
|
|
193
|
+
return state.activities;
|
|
194
|
+
},
|
|
195
|
+
observe(event) {
|
|
196
|
+
const activity = eventToActivity(event);
|
|
197
|
+
if (!activity)
|
|
198
|
+
return;
|
|
199
|
+
touch();
|
|
200
|
+
upsertActivity(state.activities, activity);
|
|
201
|
+
publishState("running");
|
|
202
|
+
},
|
|
203
|
+
finish({ activities }) {
|
|
204
|
+
state.activities = mergeActivities(state.activities, activities);
|
|
205
|
+
return publishState("done", {
|
|
206
|
+
completedAt: Date.now(),
|
|
207
|
+
updatedAt: state.updatedAt,
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
stop(status, updates = {}) {
|
|
211
|
+
state.activities = mergeActivities(state.activities, updates.activities ?? []);
|
|
212
|
+
return publishState(status, {
|
|
213
|
+
completedAt: Date.now(),
|
|
214
|
+
updatedAt: state.updatedAt,
|
|
215
|
+
...updates,
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
fail(error) {
|
|
219
|
+
return publishState("error", {
|
|
220
|
+
completedAt: Date.now(),
|
|
221
|
+
error: getErrorMessage(error),
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export function collectSessionActivities(session) {
|
|
227
|
+
return session.agent.state.messages.flatMap((message) => {
|
|
228
|
+
if (message.role === "assistant")
|
|
229
|
+
return assistantMessageActivities(message);
|
|
230
|
+
if (message.role === "toolResult")
|
|
231
|
+
return [
|
|
232
|
+
{
|
|
233
|
+
id: message.toolCallId,
|
|
234
|
+
type: "tool",
|
|
235
|
+
name: message.toolName,
|
|
236
|
+
summary: summarizeValue(message.content),
|
|
237
|
+
status: message.isError ? "error" : "done",
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
return [];
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
export function mergeActivities(...activityLists) {
|
|
244
|
+
const merged = [];
|
|
245
|
+
for (const activity of activityLists.flat().filter(Boolean))
|
|
246
|
+
upsertActivity(merged, activity);
|
|
247
|
+
return merged;
|
|
248
|
+
}
|
|
249
|
+
export function lastRuntimeActivities(runtime) {
|
|
250
|
+
return runtime.lastActivities?.length
|
|
251
|
+
? runtime.lastActivities
|
|
252
|
+
: collectSessionActivities(runtime.session);
|
|
253
|
+
}
|
|
254
|
+
export function latestActivityLines(runtime, count = 3) {
|
|
255
|
+
return lastRuntimeActivities(runtime)
|
|
256
|
+
.slice(-count)
|
|
257
|
+
.map(formatActivityLine)
|
|
258
|
+
.filter(Boolean);
|
|
259
|
+
}
|
|
260
|
+
export function formatActivityLine(activity) {
|
|
261
|
+
if (!activity)
|
|
262
|
+
return undefined;
|
|
263
|
+
if (activity.type === "assistant")
|
|
264
|
+
return `assistant ${truncateInline(activity.text, 160)}`;
|
|
265
|
+
const status = activity.status ? ` (${activity.status})` : "";
|
|
266
|
+
return `[${activity.name ?? activity.type}] ${truncateInline(activity.summary ?? activity.text ?? "", 160)}${status}`.trim();
|
|
267
|
+
}
|
|
268
|
+
function eventToActivity(event) {
|
|
269
|
+
if (!event || typeof event !== "object")
|
|
270
|
+
return undefined;
|
|
271
|
+
if (event.type === "tool_execution_start")
|
|
272
|
+
return {
|
|
273
|
+
id: event.toolCallId,
|
|
274
|
+
type: "tool",
|
|
275
|
+
name: event.toolName,
|
|
276
|
+
summary: summarizeValue(event.args),
|
|
277
|
+
status: "running",
|
|
278
|
+
};
|
|
279
|
+
if (event.type === "tool_execution_update")
|
|
280
|
+
return {
|
|
281
|
+
id: event.toolCallId,
|
|
282
|
+
type: "tool",
|
|
283
|
+
name: event.toolName,
|
|
284
|
+
summary: summarizeValue(event.partialResult ?? event.args),
|
|
285
|
+
status: "running",
|
|
286
|
+
};
|
|
287
|
+
if (event.type === "tool_execution_end")
|
|
288
|
+
return {
|
|
289
|
+
id: event.toolCallId,
|
|
290
|
+
type: "tool",
|
|
291
|
+
name: event.toolName,
|
|
292
|
+
summary: summarizeValue(event.result),
|
|
293
|
+
status: event.isError ? "error" : "done",
|
|
294
|
+
};
|
|
295
|
+
if (event.type === "message_update" && event.message?.role === "assistant")
|
|
296
|
+
return assistantActivity(event.message);
|
|
297
|
+
if (event.type === "message_end" && event.message?.role === "assistant")
|
|
298
|
+
return assistantActivity(event.message);
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
function assistantMessageActivities(message) {
|
|
302
|
+
const activities = [];
|
|
303
|
+
const text = messageText(message);
|
|
304
|
+
if (text)
|
|
305
|
+
activities.push({
|
|
306
|
+
id: "assistant",
|
|
307
|
+
type: "assistant",
|
|
308
|
+
text,
|
|
309
|
+
status: message.stopReason === "error"
|
|
310
|
+
? "error"
|
|
311
|
+
: message.stopReason === "aborted"
|
|
312
|
+
? "aborted"
|
|
313
|
+
: undefined,
|
|
314
|
+
});
|
|
315
|
+
else if (message.stopReason === "aborted")
|
|
316
|
+
activities.push({
|
|
317
|
+
id: "assistant",
|
|
318
|
+
type: "assistant",
|
|
319
|
+
text: message.errorMessage || "Operation aborted",
|
|
320
|
+
status: "aborted",
|
|
321
|
+
});
|
|
322
|
+
else if (message.stopReason === "error")
|
|
323
|
+
activities.push({
|
|
324
|
+
id: "assistant",
|
|
325
|
+
type: "assistant",
|
|
326
|
+
text: message.errorMessage || "Unknown error",
|
|
327
|
+
status: "error",
|
|
328
|
+
});
|
|
329
|
+
if (Array.isArray(message.content)) {
|
|
330
|
+
activities.push(...message.content
|
|
331
|
+
.filter((part) => part.type === "toolCall")
|
|
332
|
+
.map((part) => ({
|
|
333
|
+
id: part.id,
|
|
334
|
+
type: "tool",
|
|
335
|
+
name: part.name,
|
|
336
|
+
summary: summarizeValue(part.arguments ?? {}),
|
|
337
|
+
})));
|
|
338
|
+
}
|
|
339
|
+
return activities;
|
|
340
|
+
}
|
|
341
|
+
function assistantActivity(message) {
|
|
342
|
+
const text = messageText(message);
|
|
343
|
+
return text ? { id: "assistant", type: "assistant", text } : undefined;
|
|
344
|
+
}
|
|
345
|
+
function upsertActivity(activities, activity) {
|
|
346
|
+
const key = activity.id ?? `${activity.type}:${activity.name ?? ""}`;
|
|
347
|
+
const index = activities.findIndex((item) => (item.id ?? `${item.type}:${item.name ?? ""}`) === key);
|
|
348
|
+
if (index === -1)
|
|
349
|
+
activities.push(activity);
|
|
350
|
+
else
|
|
351
|
+
activities[index] = { ...activities[index], ...activity };
|
|
352
|
+
}
|
|
353
|
+
function messageText(message) {
|
|
354
|
+
if (!message)
|
|
355
|
+
return "";
|
|
356
|
+
if (typeof message.content === "string")
|
|
357
|
+
return message.content;
|
|
358
|
+
if (!Array.isArray(message.content))
|
|
359
|
+
return "";
|
|
360
|
+
return message.content
|
|
361
|
+
.filter((part) => part.type === "text")
|
|
362
|
+
.map((part) => part.text)
|
|
363
|
+
.filter(Boolean)
|
|
364
|
+
.join("\n");
|
|
365
|
+
}
|
|
366
|
+
function summarizeValue(value) {
|
|
367
|
+
if (Array.isArray(value))
|
|
368
|
+
return value
|
|
369
|
+
.map((item) => item.text ?? item.data ?? JSON.stringify(item))
|
|
370
|
+
.join(" ")
|
|
371
|
+
.slice(0, 240);
|
|
372
|
+
if (value && typeof value === "object") {
|
|
373
|
+
if (Array.isArray(value.content))
|
|
374
|
+
return summarizeValue(value.content);
|
|
375
|
+
if (typeof value.text === "string")
|
|
376
|
+
return value.text.slice(0, 240);
|
|
377
|
+
return JSON.stringify(value).slice(0, 240);
|
|
378
|
+
}
|
|
379
|
+
return String(value ?? "").slice(0, 240);
|
|
380
|
+
}
|
|
381
|
+
function truncateInline(text, length) {
|
|
382
|
+
const normalized = String(text ?? "")
|
|
383
|
+
.replace(/\s+/g, " ")
|
|
384
|
+
.trim();
|
|
385
|
+
return normalized.length > length
|
|
386
|
+
? `${normalized.slice(0, Math.max(0, length - 1))}…`
|
|
387
|
+
: normalized;
|
|
388
|
+
}
|
|
389
|
+
/** Converts the target session transcript into the result returned to the caller. */
|
|
390
|
+
export function sessionRunOutcome(runtime, { request, error } = {}) {
|
|
391
|
+
const session = runtime.session;
|
|
392
|
+
const assistant = lastAssistantMessage(session.agent.state.messages);
|
|
393
|
+
const text = assistantText(assistant);
|
|
394
|
+
if (text &&
|
|
395
|
+
assistant?.stopReason !== "aborted" &&
|
|
396
|
+
assistant?.stopReason !== "error")
|
|
397
|
+
return { status: "done", text };
|
|
398
|
+
if (assistant?.stopReason === "aborted")
|
|
399
|
+
return {
|
|
400
|
+
status: "aborted",
|
|
401
|
+
text: sessionOutcomeText(runtime, "aborted", { request }),
|
|
402
|
+
};
|
|
403
|
+
if (assistant?.stopReason === "error")
|
|
404
|
+
return {
|
|
405
|
+
status: "error",
|
|
406
|
+
text: sessionOutcomeText(runtime, "error", {
|
|
407
|
+
request,
|
|
408
|
+
error: assistant.errorMessage,
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
if (error)
|
|
412
|
+
return {
|
|
413
|
+
status: "error",
|
|
414
|
+
text: sessionOutcomeText(runtime, "error", {
|
|
415
|
+
request,
|
|
416
|
+
error: getErrorMessage(error),
|
|
417
|
+
}),
|
|
418
|
+
};
|
|
419
|
+
return {
|
|
420
|
+
status: "stopped",
|
|
421
|
+
text: sessionOutcomeText(runtime, "stopped", { request }),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
export function sessionOutcomeText(runtime, kind, { request, error } = {}) {
|
|
425
|
+
const session = runtime.session;
|
|
426
|
+
const sessionId = shortSessionId(session.sessionManager.getSessionId?.());
|
|
427
|
+
const agent = runtime.agentName ? ` [${runtime.agentName}]` : "";
|
|
428
|
+
const lastAbort = runtime.lastAbort;
|
|
429
|
+
const actor = lastAbort?.actor ??
|
|
430
|
+
(kind === "aborted" ? "user in that session" : undefined);
|
|
431
|
+
const activityLines = latestActivityLines(runtime).map((line) => `- ${line}`);
|
|
432
|
+
const details = [
|
|
433
|
+
kind === "aborted"
|
|
434
|
+
? `Session ${sessionId}${agent} was aborted while handling your request.`
|
|
435
|
+
: undefined,
|
|
436
|
+
kind === "aborted" ? `Aborted by: ${actor}.` : undefined,
|
|
437
|
+
kind === "error"
|
|
438
|
+
? `Session ${sessionId}${agent} failed while handling your request.`
|
|
439
|
+
: undefined,
|
|
440
|
+
kind === "error" ? `Error: ${error || "Unknown error"}` : undefined,
|
|
441
|
+
kind === "stopped"
|
|
442
|
+
? `Session ${sessionId}${agent} stopped before returning a final answer.`
|
|
443
|
+
: undefined,
|
|
444
|
+
request ? `Request: ${request}` : undefined,
|
|
445
|
+
activityLines.length
|
|
446
|
+
? `Last activity:\n${activityLines.join("\n")}`
|
|
447
|
+
: undefined,
|
|
448
|
+
].filter(Boolean);
|
|
449
|
+
return details.join("\n");
|
|
450
|
+
}
|
|
451
|
+
function lastAssistantMessage(messages) {
|
|
452
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
453
|
+
const message = messages[index];
|
|
454
|
+
if (message.role === "assistant")
|
|
455
|
+
return message;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
function assistantText(message) {
|
|
460
|
+
if (!message)
|
|
461
|
+
return "";
|
|
462
|
+
const text = Array.isArray(message.content)
|
|
463
|
+
? message.content
|
|
464
|
+
.filter((part) => part.type === "text")
|
|
465
|
+
.map((part) => part.text)
|
|
466
|
+
.join("\n")
|
|
467
|
+
: message.content;
|
|
468
|
+
return String(text ?? "").trim();
|
|
469
|
+
}
|
|
470
|
+
/** Summarizes one runtime for status cards and tool responses. */
|
|
471
|
+
export function sessionStatus(runtime) {
|
|
472
|
+
const now = Date.now();
|
|
473
|
+
const running = runtime.session.isStreaming === true;
|
|
474
|
+
runtime.streamingStartedAt = running
|
|
475
|
+
? (runtime.runStartedAt ??
|
|
476
|
+
runtime.streamingStartedAt ??
|
|
477
|
+
runtime.lastActivityAt ??
|
|
478
|
+
runtime.createdAt ??
|
|
479
|
+
new Date(now).toISOString())
|
|
480
|
+
: undefined;
|
|
481
|
+
const lastActivityAt = runtime.lastActivityAt ?? runtime.createdAt;
|
|
482
|
+
const inactiveMs = elapsedMs(now, lastActivityAt);
|
|
483
|
+
const runningMs = running
|
|
484
|
+
? elapsedMs(now, runtime.runStartedAt ?? runtime.streamingStartedAt)
|
|
485
|
+
: undefined;
|
|
486
|
+
const pendingMessages = Number(runtime.session.pendingMessageCount ?? 0);
|
|
487
|
+
const status = {
|
|
488
|
+
sessionId: runtime.session.sessionManager.getSessionId(),
|
|
489
|
+
agentName: runtime.agentName,
|
|
490
|
+
running,
|
|
491
|
+
state: running ? "running" : pendingMessages > 0 ? "queued" : "idle",
|
|
492
|
+
pendingMessages,
|
|
493
|
+
pendingText: pendingMessages === 1
|
|
494
|
+
? "1 queued message"
|
|
495
|
+
: `${pendingMessages} queued messages`,
|
|
496
|
+
inactiveMs,
|
|
497
|
+
inactiveText: formatDuration(inactiveMs),
|
|
498
|
+
runningMs: runningMs ?? null,
|
|
499
|
+
runningText: runningMs === undefined ? null : formatDuration(runningMs),
|
|
500
|
+
lastActivities: lastRuntimeActivities(runtime).slice(-3),
|
|
501
|
+
};
|
|
502
|
+
return { ...status, text: formatSessionStatus(status) };
|
|
503
|
+
}
|
|
504
|
+
export function formatSessionStatus(status) {
|
|
505
|
+
const title = `Session ${shortSessionId(status.sessionId)}${status.agentName ? ` [${status.agentName}]` : ""}`;
|
|
506
|
+
const lines = [
|
|
507
|
+
title,
|
|
508
|
+
`State: ${status.state ?? (status.running ? "running" : "idle")}`,
|
|
509
|
+
status.runningText ? `Running for: ${status.runningText}` : undefined,
|
|
510
|
+
`Last activity: ${status.inactiveText ?? formatDuration(status.inactiveMs ?? 0)} ago`,
|
|
511
|
+
Number(status.pendingMessages ?? 0) > 0
|
|
512
|
+
? `Queued messages: ${status.pendingMessages}`
|
|
513
|
+
: undefined,
|
|
514
|
+
];
|
|
515
|
+
const activities = Array.isArray(status.lastActivities)
|
|
516
|
+
? status.lastActivities
|
|
517
|
+
: [];
|
|
518
|
+
if (activities.length > 0) {
|
|
519
|
+
lines.push("Recent activity:");
|
|
520
|
+
lines.push(...activities.map((activity) => `- ${formatStatusActivity(activity)}`));
|
|
521
|
+
}
|
|
522
|
+
return lines.filter(Boolean).join("\n");
|
|
523
|
+
}
|
|
524
|
+
function elapsedMs(now, value) {
|
|
525
|
+
const time = typeof value === "number"
|
|
526
|
+
? value
|
|
527
|
+
: value
|
|
528
|
+
? new Date(value).getTime()
|
|
529
|
+
: undefined;
|
|
530
|
+
return Number.isFinite(time) ? Math.max(0, now - time) : 0;
|
|
531
|
+
}
|
|
532
|
+
function formatStatusActivity(activity) {
|
|
533
|
+
if (!activity || typeof activity !== "object")
|
|
534
|
+
return String(activity ?? "");
|
|
535
|
+
if (activity.type === "tool")
|
|
536
|
+
return `[${activity.name ?? "tool"}] ${activity.status ?? ""}`.trim();
|
|
537
|
+
return String(activity.text ?? activity.summary ?? activity.type ?? "activity")
|
|
538
|
+
.replace(/\s+/g, " ")
|
|
539
|
+
.trim();
|
|
540
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
type LiveRuntimeState = {
|
|
2
|
+
liveRuntimes: Map<string, AnyRecord>;
|
|
3
|
+
hostSwitchSession?: (this: unknown, sessionPath: string, options?: AnyRecord) => Promise<unknown>;
|
|
4
|
+
hostAbortSession?: (this: unknown, ...args: unknown[]) => Promise<unknown>;
|
|
5
|
+
hostSetupKeyHandlers?: (this: unknown, ...args: unknown[]) => unknown;
|
|
6
|
+
activeContext?: PiContext;
|
|
7
|
+
activeSession?: PiAgentSession;
|
|
8
|
+
bridgeInstalled: boolean;
|
|
9
|
+
abortBridgeInstalled: boolean;
|
|
10
|
+
escapeBridgeInstalled: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function getLiveRuntimeState(): LiveRuntimeState;
|
|
13
|
+
type AgentCall = {
|
|
14
|
+
id: string;
|
|
15
|
+
callerSessionId?: string;
|
|
16
|
+
targetSessionId?: string;
|
|
17
|
+
abort?: (options?: AnyRecord) => Promise<void> | void;
|
|
18
|
+
startedAt?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function registerAgentCall(call: Omit<AgentCall, "id" | "startedAt"> & {
|
|
21
|
+
id?: string;
|
|
22
|
+
}): {
|
|
23
|
+
id: string;
|
|
24
|
+
unregister: () => boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function hasAgentCallsForSession(sessionId: any): boolean;
|
|
27
|
+
export declare function abortAgentCall(callId: any, options?: {}): Promise<number>;
|
|
28
|
+
export declare function abortAgentCallsForSession(sessionId: any, options?: {}): Promise<number>;
|
|
29
|
+
export declare const LIVE_SESSION_PREFIX = "pi-gentic-live:";
|
|
30
|
+
/** Installs bridge hooks once so live background sessions can be resumed in Pi. */
|
|
31
|
+
export declare function installLiveSessionBridge(): void;
|
|
32
|
+
export declare function activeVisibleContext(): PiContext;
|
|
33
|
+
export declare function activeVisibleSession(): PiAgentSession;
|
|
34
|
+
export declare function parkCurrentLiveRuntimeForSwitch(state: LiveRuntimeState, runtimeHost: PiAgentRuntimeHost | undefined): () => void;
|
|
35
|
+
export declare function livePath(sessionId: any): string;
|
|
36
|
+
/** Creates a Pi AgentSessionRuntime for a background or reopened session. */
|
|
37
|
+
export declare function createLiveRuntime({ cwd, sessionManager, }: {
|
|
38
|
+
cwd: string;
|
|
39
|
+
sessionManager: PiSessionManager;
|
|
40
|
+
}): Promise<PiAgentRuntimeHost>;
|
|
41
|
+
export declare function registerLiveRuntime(runtime: any, metadata?: {}): string;
|
|
42
|
+
export declare function unregisterLiveRuntime(sessionId: any): void;
|
|
43
|
+
export declare function getLiveRuntime(sessionId: any): AnyRecord;
|
|
44
|
+
export declare function listLiveRuntimes(): {
|
|
45
|
+
action?: string;
|
|
46
|
+
activities?: AnyRecord[];
|
|
47
|
+
agent?: string;
|
|
48
|
+
agentName?: string;
|
|
49
|
+
agents?: AnyRecord[];
|
|
50
|
+
args?: AnyRecord;
|
|
51
|
+
async?: boolean;
|
|
52
|
+
cardId?: string;
|
|
53
|
+
completedAt?: number;
|
|
54
|
+
configuration?: AnyRecord;
|
|
55
|
+
content?: unknown;
|
|
56
|
+
cwd?: string;
|
|
57
|
+
data?: AnyRecord;
|
|
58
|
+
description?: string;
|
|
59
|
+
details?: AnyRecord;
|
|
60
|
+
error?: string;
|
|
61
|
+
firstMessage?: string;
|
|
62
|
+
expanded?: boolean;
|
|
63
|
+
fork?: boolean;
|
|
64
|
+
id?: string;
|
|
65
|
+
inactiveMs?: number;
|
|
66
|
+
isError?: boolean;
|
|
67
|
+
isPartial?: boolean;
|
|
68
|
+
invokeMeLater?: boolean;
|
|
69
|
+
isLast?: boolean;
|
|
70
|
+
kind?: string;
|
|
71
|
+
label?: string;
|
|
72
|
+
lastActivityAt?: string | number | Date;
|
|
73
|
+
lastMessage?: string;
|
|
74
|
+
livePath?: string;
|
|
75
|
+
message?: string;
|
|
76
|
+
modified?: string | number | Date;
|
|
77
|
+
name?: string;
|
|
78
|
+
onRefresh?: (details?: AnyRecord) => void;
|
|
79
|
+
onSettled?: () => void;
|
|
80
|
+
onUpdate?: (update: AnyRecord) => void;
|
|
81
|
+
overrides?: AnyRecord;
|
|
82
|
+
parentSessionPath?: string;
|
|
83
|
+
path?: string;
|
|
84
|
+
resources?: {
|
|
85
|
+
agents?: string[];
|
|
86
|
+
tools?: string[];
|
|
87
|
+
skills?: string[];
|
|
88
|
+
};
|
|
89
|
+
restored?: boolean;
|
|
90
|
+
root?: string;
|
|
91
|
+
running?: boolean;
|
|
92
|
+
rx?: number;
|
|
93
|
+
ry?: number;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
sessions?: AnyRecord[];
|
|
96
|
+
settings?: AnyRecord;
|
|
97
|
+
shortId?: string;
|
|
98
|
+
signal?: AbortSignal;
|
|
99
|
+
skillRoots?: string[];
|
|
100
|
+
sourcePath?: string;
|
|
101
|
+
startedAt?: number;
|
|
102
|
+
status?: string;
|
|
103
|
+
text?: string;
|
|
104
|
+
tools?: string[];
|
|
105
|
+
systemPrompt?: string;
|
|
106
|
+
updatedAt?: number;
|
|
107
|
+
value?: string;
|
|
108
|
+
}[];
|
|
109
|
+
export declare function getRuntimeSession(sessionId: string): PiRuntimeSession;
|
|
110
|
+
export declare function findRuntimeSession(predicate: (runtime: PiRuntimeSession) => boolean): PiRuntimeSession;
|
|
111
|
+
/** Registers the latest known runtime state for session discovery and status. */
|
|
112
|
+
export declare function setRuntimeSession(sessionId: string, runtime: PiRuntimeSession): PiRuntimeSession;
|
|
113
|
+
export declare function updateRuntimeSession(sessionId: string, patch: Partial<PiRuntimeSession>): PiRuntimeSession;
|
|
114
|
+
export declare function listRuntimeSessions(): PiRuntimeSession[];
|
|
115
|
+
export declare function deleteRuntimeSession(sessionId: string): void;
|
|
116
|
+
export declare function pruneRuntimeSessions({ maxEntries, maxIdleMs, }?: {
|
|
117
|
+
maxEntries?: number;
|
|
118
|
+
maxIdleMs?: number;
|
|
119
|
+
}): void;
|
|
120
|
+
export declare function persistSessionImmediately(sessionManager: any): void;
|
|
121
|
+
export declare function resolveModelFromRegistry(modelRegistry: any, modelName: any): any;
|
|
122
|
+
export declare function inheritedModelForPolicy(policy: any, inheritedModel: any): {
|
|
123
|
+
provider: any;
|
|
124
|
+
id: any;
|
|
125
|
+
};
|
|
126
|
+
export declare function applyInheritedModel(session: any, policy: any, inheritedModel: any): Promise<any>;
|
|
127
|
+
export {};
|