@jmfederico/pi-web 1.202610.2 → 1.202610.3
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/dist/client/assets/{index-DUsqiytv.js → index-BAl5IbYx.js} +378 -357
- package/dist/client/index.html +1 -1
- package/dist/pi-packages/captains-log/dist/companion.js +1 -1
- package/dist/pi-web-plugins/git/server-plugin.js +3 -3
- package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +8 -2
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +391 -389
- package/dist/server/sessions/piSessionService.js +178 -14
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/queuedMessageMutation.js +168 -0
- package/dist/server/sessions/queuedMessageMutation.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +10 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +1 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/config.md +12 -0
- package/docs/plugins.md +2 -0
- package/examples/session-bridge-plugin/src/companion.ts +1 -1
- package/package.json +8 -8
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { statSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { QueueCompatibilityGuard, mirroredQueueEntries, mutableQueueAgent, mutableTextMirror, parseQueueMessageMutation, queuedMessageAttachmentCounts, queuedMessageAttachmentCount, snapshotAgentQueues } from "./queuedMessageMutation.js";
|
|
6
7
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEventBus, createEditToolDefinition, defineTool, hasTrustRequiringProjectResources, ProjectTrustStore, readStoredCredential, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
import { projectBrowserMessage } from "../browserMessageProjection.js";
|
|
8
9
|
import { isSessionMediaId } from "../../shared/sessionMedia.js";
|
|
@@ -600,6 +601,9 @@ export class PiSessionService {
|
|
|
600
601
|
* one (not for standalone commands or compaction); completion disarms it.
|
|
601
602
|
*/
|
|
602
603
|
this.subsessionNotifyArmed = new Map();
|
|
604
|
+
this.queueCompatibility = new QueueCompatibilityGuard((session, err) => {
|
|
605
|
+
this.logger.info({ sessionId: session.sessionId, err }, "Disabled SDK-dependent queue controls after an incompatible queue interface");
|
|
606
|
+
});
|
|
603
607
|
this.notificationGenerationBySession = new WeakMap();
|
|
604
608
|
/** Last idle-poll transcript file resolution per runtime, throttled; entries die with their runtime. */
|
|
605
609
|
this.idleSessionFileResolutions = new WeakMap();
|
|
@@ -2076,7 +2080,7 @@ export class PiSessionService {
|
|
|
2076
2080
|
submitPrompt(session, text, behavior, images = [], echoUserMessage = true) {
|
|
2077
2081
|
return this.beginPromptSubmission(session, text, behavior, images, echoUserMessage).catch(() => undefined);
|
|
2078
2082
|
}
|
|
2079
|
-
beginPromptSubmission(session, text, behavior, images = [], echoUserMessage = true) {
|
|
2083
|
+
beginPromptSubmission(session, text, behavior, images = [], echoUserMessage = true, expandPromptTemplates = true) {
|
|
2080
2084
|
this.publishActivity(session, behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active");
|
|
2081
2085
|
const echoes = this.pendingPromptEchoes.get(session) ?? [];
|
|
2082
2086
|
const userCount = historyMessagesFromEntries(session.sessionManager.getBranch()).filter((message) => isRecord(message) && message["role"] === "user").length;
|
|
@@ -2089,8 +2093,8 @@ export class PiSessionService {
|
|
|
2089
2093
|
this.pendingPromptEchoes.set(session, [...echoes, echo]);
|
|
2090
2094
|
this.events.publish(session.sessionId, { type: "message.append", message: this.browserTranscriptMessage(session, echo.message) }, { id: session.sessionId, cwd: session.sessionManager.getCwd() });
|
|
2091
2095
|
}
|
|
2092
|
-
const promptOptions = buildPromptOptions(behavior, images);
|
|
2093
|
-
const commandName = text.startsWith("/") ? text.slice(1).split(" ")[0] : undefined;
|
|
2096
|
+
const promptOptions = expandPromptTemplates ? buildPromptOptions(behavior, images) : { ...buildPromptOptions(behavior, images), expandPromptTemplates: false };
|
|
2097
|
+
const commandName = expandPromptTemplates && text.startsWith("/") ? text.slice(1).split(" ")[0] : undefined;
|
|
2094
2098
|
const trackEarlyFailure = behavior === undefined
|
|
2095
2099
|
&& this.subsessionLinkForActiveChild(session) !== undefined
|
|
2096
2100
|
&& !session.extensionRunner.getRegisteredCommands().some((command) => command.invocationName === commandName);
|
|
@@ -2718,6 +2722,124 @@ export class PiSessionService {
|
|
|
2718
2722
|
this.unregisterSubsession(session.sessionId);
|
|
2719
2723
|
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }]);
|
|
2720
2724
|
}
|
|
2725
|
+
async mutateQueueMessage(ref, input) {
|
|
2726
|
+
const mutation = parseQueueMessageMutation(input);
|
|
2727
|
+
await this.assertWritable(ref);
|
|
2728
|
+
const session = await this.getOrOpen(ref);
|
|
2729
|
+
// Compare the entire execution-order queue after all awaits, then mutate
|
|
2730
|
+
// synchronously so neither another request nor the agent loop can interleave.
|
|
2731
|
+
const current = this.queuedMessages(session);
|
|
2732
|
+
if (this.queueCompatibility.reason(session) !== undefined) {
|
|
2733
|
+
this.publishStatus(session);
|
|
2734
|
+
this.queueCompatibility.assertAvailable(session);
|
|
2735
|
+
}
|
|
2736
|
+
if (current.length !== mutation.expectedQueue.length || current.some((entry, index) => {
|
|
2737
|
+
const expected = mutation.expectedQueue[index];
|
|
2738
|
+
if (expected === undefined)
|
|
2739
|
+
return true;
|
|
2740
|
+
return entry.kind !== expected.kind || entry.text !== expected.text
|
|
2741
|
+
|| (mutation.action === "edit" && index === mutation.index && (entry.hasAttachments === true) !== (expected.hasAttachments === true));
|
|
2742
|
+
}))
|
|
2743
|
+
throw new Error("Queued messages changed; refresh the queue and try again");
|
|
2744
|
+
if (mutation.index >= current.length)
|
|
2745
|
+
throw new Error("Invalid queue index");
|
|
2746
|
+
try {
|
|
2747
|
+
this.queueCompatibility.run(session, () => { this.applyQueueMessageMutation(session, mutation); });
|
|
2748
|
+
}
|
|
2749
|
+
catch (error) {
|
|
2750
|
+
this.publishStatus(session);
|
|
2751
|
+
throw error;
|
|
2752
|
+
}
|
|
2753
|
+
this.publishStatus(session);
|
|
2754
|
+
return this.statusFromSession(session);
|
|
2755
|
+
}
|
|
2756
|
+
applyQueueMessageMutation(session, mutation) {
|
|
2757
|
+
const agent = mutableQueueAgent(session.agent);
|
|
2758
|
+
const mirrors = { steer: mutableTextMirror(session.getSteeringMessages()), followUp: mutableTextMirror(session.getFollowUpMessages()) };
|
|
2759
|
+
if (session.getSteeringMessages() !== mirrors.steer || session.getFollowUpMessages() !== mirrors.followUp) {
|
|
2760
|
+
throw new Error("SDK queue getters no longer expose stable backing arrays");
|
|
2761
|
+
}
|
|
2762
|
+
const queues = snapshotAgentQueues(agent);
|
|
2763
|
+
const originalQueues = { steer: [...queues.steer], followUp: [...queues.followUp] };
|
|
2764
|
+
const originalMirrors = { steer: [...mirrors.steer], followUp: [...mirrors.followUp] };
|
|
2765
|
+
const held = this.compactionQueuedMessages(session.sessionId).map((prompt) => ({ ...prompt }));
|
|
2766
|
+
const entries = ["steer", "followUp"].flatMap((kind) => [
|
|
2767
|
+
...mirroredQueueEntries(queues[kind], mirrors[kind]).map((entry) => ({ ...entry, kind })),
|
|
2768
|
+
...held.filter((prompt) => prompt.kind === kind).map((prompt) => ({ kind, text: prompt.text, prompt })),
|
|
2769
|
+
]);
|
|
2770
|
+
const selected = entries[mutation.index];
|
|
2771
|
+
if (selected === undefined)
|
|
2772
|
+
throw new Error("Invalid queue index");
|
|
2773
|
+
const removed = mutation.action === "edit" ? entries.slice(mutation.index) : [selected];
|
|
2774
|
+
const remaining = entries.filter((entry) => !removed.includes(entry));
|
|
2775
|
+
for (const entry of removed) {
|
|
2776
|
+
if (entry.message !== undefined) {
|
|
2777
|
+
const queue = queues[entry.kind];
|
|
2778
|
+
queue.splice(queue.indexOf(entry.message), 1);
|
|
2779
|
+
}
|
|
2780
|
+
if (entry.prompt !== undefined)
|
|
2781
|
+
held.splice(held.indexOf(entry.prompt), 1);
|
|
2782
|
+
}
|
|
2783
|
+
if (mutation.action === "toggle") {
|
|
2784
|
+
const kind = selected.kind === "steer" ? "followUp" : "steer";
|
|
2785
|
+
// Destination includes held prompts: append there when necessary to keep
|
|
2786
|
+
// status order and eventual execution order identical.
|
|
2787
|
+
if (selected.prompt !== undefined) {
|
|
2788
|
+
selected.prompt.kind = kind;
|
|
2789
|
+
held.push(selected.prompt);
|
|
2790
|
+
remaining.push({ ...selected, kind });
|
|
2791
|
+
}
|
|
2792
|
+
else if (selected.message !== undefined) {
|
|
2793
|
+
if (held.some((prompt) => prompt.kind === kind)) {
|
|
2794
|
+
const prompt = { kind, text: selected.text, message: selected.message };
|
|
2795
|
+
held.push(prompt);
|
|
2796
|
+
remaining.push({ kind, text: selected.text, prompt });
|
|
2797
|
+
}
|
|
2798
|
+
else {
|
|
2799
|
+
queues[kind].push(selected.message);
|
|
2800
|
+
remaining.push({ ...selected, kind });
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
try {
|
|
2805
|
+
agent.clearAllQueues();
|
|
2806
|
+
for (const message of queues.steer)
|
|
2807
|
+
agent.steer(message);
|
|
2808
|
+
for (const message of queues.followUp)
|
|
2809
|
+
agent.followUp(message);
|
|
2810
|
+
for (const kind of ["steer", "followUp"]) {
|
|
2811
|
+
mirrors[kind].splice(0, mirrors[kind].length, ...remaining.filter((entry) => entry.kind === kind && entry.message !== undefined).map((entry) => entry.text));
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
catch (error) {
|
|
2815
|
+
// Best-effort rollback when a callable SDK method fails during replacement.
|
|
2816
|
+
// Restoration itself can fail after an incompatible behavioral change.
|
|
2817
|
+
const failures = [error];
|
|
2818
|
+
try {
|
|
2819
|
+
agent.clearAllQueues();
|
|
2820
|
+
for (const message of originalQueues.steer)
|
|
2821
|
+
agent.steer(message);
|
|
2822
|
+
for (const message of originalQueues.followUp)
|
|
2823
|
+
agent.followUp(message);
|
|
2824
|
+
}
|
|
2825
|
+
catch (restoreError) {
|
|
2826
|
+
failures.push(restoreError);
|
|
2827
|
+
}
|
|
2828
|
+
for (const kind of ["steer", "followUp"]) {
|
|
2829
|
+
try {
|
|
2830
|
+
mirrors[kind].splice(0, mirrors[kind].length, ...originalMirrors[kind]);
|
|
2831
|
+
}
|
|
2832
|
+
catch (restoreError) {
|
|
2833
|
+
failures.push(restoreError);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
throw new AggregateError(failures, "Could not replace SDK queues; attempted to restore their original contents", { cause: error });
|
|
2837
|
+
}
|
|
2838
|
+
if (held.length === 0)
|
|
2839
|
+
this.clearCompactionPromptQueue(session.sessionId);
|
|
2840
|
+
else
|
|
2841
|
+
this.compactionPromptQueues.set(session.sessionId, held);
|
|
2842
|
+
}
|
|
2721
2843
|
async clearQueue(ref) {
|
|
2722
2844
|
await this.assertWritable(ref);
|
|
2723
2845
|
const session = await this.getOrOpen(ref);
|
|
@@ -3644,16 +3766,25 @@ export class PiSessionService {
|
|
|
3644
3766
|
return;
|
|
3645
3767
|
this.publishStatus(session);
|
|
3646
3768
|
for (const prompt of queued)
|
|
3647
|
-
void this.
|
|
3769
|
+
void this.submitHeldPrompt(session, prompt, prompt.kind);
|
|
3648
3770
|
return;
|
|
3649
3771
|
}
|
|
3650
3772
|
const prompt = this.shiftCompactionPrompt(sessionId);
|
|
3651
3773
|
if (prompt === undefined)
|
|
3652
3774
|
return;
|
|
3653
3775
|
this.publishStatus(session);
|
|
3654
|
-
const submitted = this.
|
|
3776
|
+
const submitted = this.submitHeldPrompt(session, prompt);
|
|
3655
3777
|
void submitted.finally(() => { this.scheduleCompactionQueueDrain(sessionId); });
|
|
3656
3778
|
}
|
|
3779
|
+
submitHeldPrompt(session, prompt, kind) {
|
|
3780
|
+
if (prompt.message === undefined)
|
|
3781
|
+
return this.submitPrompt(session, prompt.text, kind, prompt.images, prompt.echoUserMessage ?? true);
|
|
3782
|
+
// Use the public session API even after the queue adapter has been disabled.
|
|
3783
|
+
// Keep auth, run ownership and settlement SDK-owned; text is already expanded.
|
|
3784
|
+
const images = prompt.message.role === "user" && Array.isArray(prompt.message.content)
|
|
3785
|
+
? prompt.message.content.filter((part) => part.type === "image") : [];
|
|
3786
|
+
return this.beginPromptSubmission(session, prompt.text, kind, images, true, false).catch(() => undefined);
|
|
3787
|
+
}
|
|
3657
3788
|
takeCompactionPromptQueue(sessionId) {
|
|
3658
3789
|
const queued = this.compactionPromptQueues.get(sessionId) ?? [];
|
|
3659
3790
|
this.compactionPromptQueues.delete(sessionId);
|
|
@@ -3661,7 +3792,8 @@ export class PiSessionService {
|
|
|
3661
3792
|
}
|
|
3662
3793
|
shiftCompactionPrompt(sessionId) {
|
|
3663
3794
|
const queue = this.compactionPromptQueues.get(sessionId);
|
|
3664
|
-
const
|
|
3795
|
+
const steeringIndex = queue?.findIndex((prompt) => prompt.kind === "steer") ?? -1;
|
|
3796
|
+
const prompt = queue?.splice(steeringIndex < 0 ? 0 : steeringIndex, 1)[0];
|
|
3665
3797
|
if (queue === undefined || queue.length === 0)
|
|
3666
3798
|
this.compactionPromptQueues.delete(sessionId);
|
|
3667
3799
|
return prompt;
|
|
@@ -4038,6 +4170,8 @@ export class PiSessionService {
|
|
|
4038
4170
|
const warnings = this.warningsForSession(session);
|
|
4039
4171
|
const pendingDialogs = this.pendingExtensionDialogStore.pendingDialogs(session.sessionId);
|
|
4040
4172
|
const suggestedInput = this.suggestedInputs.get(session.sessionId);
|
|
4173
|
+
const queuedMessages = this.queuedMessages(session);
|
|
4174
|
+
const queueMessageActionsUnavailable = this.queueCompatibility.reason(session);
|
|
4041
4175
|
return {
|
|
4042
4176
|
sessionId: session.sessionId,
|
|
4043
4177
|
persisted: sessionFileExists(session.sessionFile),
|
|
@@ -4048,7 +4182,8 @@ export class PiSessionService {
|
|
|
4048
4182
|
isCompacting: session.isCompacting,
|
|
4049
4183
|
isBashRunning: session.isBashRunning,
|
|
4050
4184
|
pendingMessageCount: this.pendingMessageCount(session),
|
|
4051
|
-
queuedMessages
|
|
4185
|
+
queuedMessages,
|
|
4186
|
+
...(queueMessageActionsUnavailable === undefined ? {} : { queueMessageActionsUnavailable }),
|
|
4052
4187
|
messageCount,
|
|
4053
4188
|
tokens: stats.tokens,
|
|
4054
4189
|
cost: stats.cost,
|
|
@@ -4078,8 +4213,12 @@ export class PiSessionService {
|
|
|
4078
4213
|
compactionQueuedMessages(sessionId) {
|
|
4079
4214
|
return this.compactionPromptQueues.get(sessionId) ?? [];
|
|
4080
4215
|
}
|
|
4216
|
+
queuedMessages(session) {
|
|
4217
|
+
const held = this.compactionQueuedMessages(session.sessionId);
|
|
4218
|
+
return this.queueCompatibility.inspect(session, () => queuedMessagesFromSession(session, held), () => queuedMessagesFromSession(session, held, false));
|
|
4219
|
+
}
|
|
4081
4220
|
hasQueuedMessageText(session, text) {
|
|
4082
|
-
return
|
|
4221
|
+
return this.queuedMessages(session).some((message) => message.text === text);
|
|
4083
4222
|
}
|
|
4084
4223
|
}
|
|
4085
4224
|
function previewResponseFromPlan(plan) {
|
|
@@ -4462,12 +4601,37 @@ function clearParentSessionHeader(sessionManager) {
|
|
|
4462
4601
|
function clearSessionQueue(session) {
|
|
4463
4602
|
session.clearQueue();
|
|
4464
4603
|
}
|
|
4465
|
-
function queuedMessagesFromSession(session, extraQueuedMessages = []) {
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4604
|
+
function queuedMessagesFromSession(session, extraQueuedMessages = [], inspectSdk = true) {
|
|
4605
|
+
const texts = inspectSdk
|
|
4606
|
+
? { steer: mutableTextMirror(session.getSteeringMessages()), followUp: mutableTextMirror(session.getFollowUpMessages()) }
|
|
4607
|
+
: { steer: publicQueueTexts(() => session.getSteeringMessages()), followUp: publicQueueTexts(() => session.getFollowUpMessages()) };
|
|
4608
|
+
const attachmentCounts = { steer: [], followUp: [] };
|
|
4609
|
+
if (inspectSdk && (texts.steer.length > 0 || texts.followUp.length > 0 || extraQueuedMessages.length > 0)) {
|
|
4610
|
+
const queues = snapshotAgentQueues(mutableQueueAgent(session.agent));
|
|
4611
|
+
for (const kind of ["steer", "followUp"]) {
|
|
4612
|
+
attachmentCounts[kind] = queuedMessageAttachmentCounts(queues[kind], texts[kind]);
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
return ["steer", "followUp"].flatMap((kind) => [
|
|
4616
|
+
...texts[kind].map((text, index) => ({ kind, text, ...queuedAttachmentMetadata(attachmentCounts[kind][index] ?? 0) })),
|
|
4617
|
+
...extraQueuedMessages.filter((message) => message.kind === kind).map((prompt) => ({
|
|
4618
|
+
kind, text: prompt.text,
|
|
4619
|
+
...queuedAttachmentMetadata(prompt.message === undefined ? prompt.images?.length ?? 0 : queuedMessageAttachmentCount(prompt.message)),
|
|
4620
|
+
})),
|
|
4621
|
+
]);
|
|
4622
|
+
}
|
|
4623
|
+
/** Degraded status still uses public text getters where their values are usable. */
|
|
4624
|
+
function publicQueueTexts(read) {
|
|
4625
|
+
try {
|
|
4626
|
+
const value = read();
|
|
4627
|
+
return Array.isArray(value) ? value.filter((text) => typeof text === "string") : [];
|
|
4628
|
+
}
|
|
4629
|
+
catch {
|
|
4630
|
+
return [];
|
|
4631
|
+
} // The compatibility guard already records the original failure.
|
|
4632
|
+
}
|
|
4633
|
+
function queuedAttachmentMetadata(attachmentCount) {
|
|
4634
|
+
return attachmentCount > 0 ? { hasAttachments: true, attachmentCount } : {};
|
|
4471
4635
|
}
|
|
4472
4636
|
function userTextMessage(text) {
|
|
4473
4637
|
return { role: "user", content: text };
|