@jmfederico/pi-web 1.202608.2 → 1.202609.1
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 +3 -3
- package/dist/.plugins-ready +1 -0
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/apple-touch-icon-dev.png +0 -0
- package/dist/client/assets/index-AroYaCLx.js +4130 -0
- package/dist/client/assets/vendor-editor-core-YWog6mAR.js +10 -0
- package/dist/client/assets/vendor-editor-languages-DOCw3vc9.js +28 -0
- package/dist/client/favicon-dev.svg +8 -0
- package/dist/client/index.html +5 -5
- package/dist/client/pwa-icon-dev-192.png +0 -0
- package/dist/client/pwa-icon-dev-512.png +0 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -1
- package/dist/docker/piWebDockerCommandPlan.js +9 -1
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/nativeServices/installedServiceDefinitions.js +25 -19
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -1
- package/dist/nativeServices/serviceAction.js +38 -26
- package/dist/nativeServices/serviceAction.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +3 -3
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/pi-packages/captains-log/README.md +11 -0
- package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
- package/dist/pi-packages/captains-log/dist/browser/index.js +144 -0
- package/dist/pi-packages/captains-log/dist/browser/markdown.js +67 -0
- package/dist/pi-packages/captains-log/dist/browser/panel.js +105 -0
- package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
- package/dist/pi-packages/captains-log/dist/companion.js +126 -0
- package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
- package/dist/pi-packages/captains-log/dist/server.js +210 -0
- package/dist/pi-packages/captains-log/dist/store.js +75 -0
- package/dist/pi-packages/captains-log/docs/usage.md +30 -0
- package/dist/pi-packages/captains-log/package.json +35 -0
- package/dist/pi-packages/captains-log/tsconfig.json +21 -0
- package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
- package/dist/pi-packages/relays/package.json +1 -1
- package/dist/pi-packages/relays/pi-web-plugin.js +1 -1
- package/dist/pi-packages/relays/prompts/relay-worktree.md +13 -142
- package/dist/pi-packages/relays/prompts/relay.md +25 -132
- package/dist/pi-packages/relays/relayDiscovery.js +2 -2
- package/dist/pi-packages/relays/relaysPanelElement.js +1 -1
- package/dist/pi-packages/relays/skills/relay/SKILL.md +59 -88
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +253 -0
- package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +439 -0
- package/dist/pi-web-plugins/files/package.json +15 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +54 -24
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/git/git-backend.js +5 -5
- package/dist/pi-web-plugins/git/server-plugin.js +5 -3
- package/dist/pi-web-plugins/info/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +3590 -0
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +6 -0
- package/dist/pi-web-plugins/mermaid/package.json +13 -0
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +210 -0
- package/dist/pi-web-plugins/terminal/package.json +16 -0
- package/dist/pi-web-plugins/terminal/server-plugin.js +365 -0
- package/dist/{server/terminals → pi-web-plugins/terminal}/terminalService.js +162 -65
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +45 -35
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
- package/dist/piWebVersionReport.js +12 -4
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +222 -23
- package/dist/plugin-api.js +1 -0
- package/dist/pluginRecoveryCli.js +3 -2
- package/dist/pluginRecoveryCli.js.map +1 -1
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +33 -12
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +6 -1
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/deploymentIdentity.js +67 -0
- package/dist/server/deploymentIdentity.js.map +1 -0
- package/dist/server/deploymentIdentityRoutes.js +22 -0
- package/dist/server/deploymentIdentityRoutes.js.map +1 -0
- package/dist/server/knownAutoInstallPiPackages.js +1 -1
- package/dist/server/knownAutoInstallPiPackages.js.map +1 -1
- package/dist/server/knownPiPackages.js +12 -0
- package/dist/server/knownPiPackages.js.map +1 -0
- package/dist/server/machines/machineClient.js +2 -2
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +53 -11
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +70 -7
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/notices/serverNoticeRoutes.js +34 -0
- package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
- package/dist/server/notices/serverNoticeService.js +26 -0
- package/dist/server/notices/serverNoticeService.js.map +1 -0
- package/dist/server/notices/serverNoticeStore.js +119 -0
- package/dist/server/notices/serverNoticeStore.js.map +1 -0
- package/dist/server/piPackageService.js +5 -5
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +46 -12
- package/dist/server/piWebPluginCatalog.js.map +1 -1
- package/dist/server/piWebPluginLifecycle.js +26 -3
- package/dist/server/piWebPluginLifecycle.js.map +1 -1
- package/dist/server/piWebPluginService.js +37 -2
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +1 -1
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/pluginCallbackDrain.js +35 -0
- package/dist/server/pluginCallbackDrain.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +52 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +38 -31
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +740 -0
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +1028 -107
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
- package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +393 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +10 -7
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionDaemonShutdown.js +9 -2
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond.js +82 -21
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/attachmentService.js +1 -5
- package/dist/server/sessions/attachmentService.js.map +1 -1
- package/dist/server/sessions/builtinCommands.js +2 -2
- package/dist/server/sessions/builtinCommands.js.map +1 -1
- package/dist/server/sessions/clientSessionPreview.js +14 -0
- package/dist/server/sessions/clientSessionPreview.js.map +1 -0
- package/dist/server/sessions/piSessionEventConnections.js +65 -0
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +171 -1
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +442 -90
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionActivityMarker.js +105 -0
- package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +3 -2
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +18 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +1 -1
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/transcriptBranchCache.js +81 -0
- package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
- package/dist/server/sessions/transcriptMessages.js +54 -0
- package/dist/server/sessions/transcriptMessages.js.map +1 -0
- package/dist/server/terminals/requiredTerminalService.js +103 -0
- package/dist/server/terminals/requiredTerminalService.js.map +1 -0
- package/dist/server/webSocketBridge.js +339 -0
- package/dist/server/webSocketBridge.js.map +1 -1
- package/dist/server/workspaces/projectPiWebConfig.js +6 -1
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +23 -3
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceCatalog.js +3 -1
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceProviderRegistry.js +64 -149
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
- package/dist/server/workspaces/workspaceRemovalService.js +38 -5
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
- package/dist/server-plugin-api.d.ts +216 -22
- package/dist/server-plugin-api.js +319 -2
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/serverPluginRecovery.js +4 -0
- package/dist/serverPluginRecovery.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +3 -3
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/apiTypes.js +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +47 -17
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machinePluginIds.js +28 -7
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +21 -1
- package/dist/shared/pluginApiTypes.js +0 -1
- package/dist/shared/pluginBackendProtocol.js +163 -2
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginIds.js +8 -1
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/requiredTerminalPlugin.js +6 -0
- package/dist/shared/requiredTerminalPlugin.js.map +1 -0
- package/dist/shared/serverNoticeContract.js +88 -0
- package/dist/shared/serverNoticeContract.js.map +1 -0
- package/dist/shared/sessionDefaults.js +41 -0
- package/dist/shared/sessionDefaults.js.map +1 -0
- package/docs/config.md +77 -37
- package/docs/plugins.md +146 -1213
- package/examples/session-bridge-plugin/README.md +20 -0
- package/examples/session-bridge-plugin/docs/usage.md +54 -0
- package/examples/session-bridge-plugin/package.json +26 -0
- package/examples/session-bridge-plugin/src/browser/index.ts +84 -0
- package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
- package/examples/session-bridge-plugin/src/companion.ts +65 -0
- package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
- package/examples/session-bridge-plugin/src/server.ts +85 -0
- package/examples/session-bridge-plugin/src/store.ts +57 -0
- package/examples/session-bridge-plugin/tsconfig.json +21 -0
- package/examples/workspace-provider-plugin/README.md +4 -4
- package/examples/workspace-provider-plugin/package.json +1 -1
- package/examples/workspace-provider-plugin/src/browser/index.ts +9 -9
- package/examples/workspace-provider-plugin/src/server.ts +15 -11
- package/package.json +17 -11
- package/dist/client/assets/CodeViewer-BMWwxG7q.js +0 -4
- package/dist/client/assets/TerminalPanel-CacQDIYn.js +0 -187
- package/dist/client/assets/index-DUW2xnoV.js +0 -4279
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +0 -12
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +0 -46
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
- package/dist/server/terminalProxyRoutes.js +0 -141
- package/dist/server/terminalProxyRoutes.js.map +0 -1
- package/dist/server/terminals/terminalRoutes.js +0 -155
- package/dist/server/terminals/terminalRoutes.js.map +0 -1
- package/dist/server/terminals/terminalService.js.map +0 -1
- package/dist/server/terminals/terminalSize.js +0 -17
- package/dist/server/terminals/terminalSize.js.map +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Generated from pi-packages/captains-log/src/companion.ts. Do not edit directly.
|
|
2
|
+
import { MAX_FINDINGS, UUID, CAPTAIN_REPLY, CAPTAIN_REQUEST, SOURCE_REQUEST, SOURCE_REPLY, PIRATE_INTRO, PIRATE_MARKER, captainPrompt } from "./browser/protocol.js";
|
|
3
|
+
export default function companion(pi) {
|
|
4
|
+
let context;
|
|
5
|
+
let active;
|
|
6
|
+
const reply = (requestId, result) => { pi.events.emit(CAPTAIN_REPLY, { requestId, ...result }); };
|
|
7
|
+
pi.on("session_start", (_event, ctx) => { context = ctx; });
|
|
8
|
+
pi.on("session_shutdown", () => {
|
|
9
|
+
if (active)
|
|
10
|
+
reply(active.requestId, { status: "failed", error: "Captain request interrupted by session shutdown or reload" });
|
|
11
|
+
active = undefined;
|
|
12
|
+
context = undefined;
|
|
13
|
+
});
|
|
14
|
+
pi.on("before_agent_start", (event) => {
|
|
15
|
+
if (!active)
|
|
16
|
+
return;
|
|
17
|
+
if (!active.started && event.prompt === active.prompt) {
|
|
18
|
+
active.started = true;
|
|
19
|
+
if (event.prompt === PIRATE_INTRO)
|
|
20
|
+
pi.appendEntry(PIRATE_MARKER, {});
|
|
21
|
+
reply(active.requestId, { status: "running" });
|
|
22
|
+
}
|
|
23
|
+
else
|
|
24
|
+
active.error = "Another prompt interfered with this Captain request; translation was not saved";
|
|
25
|
+
});
|
|
26
|
+
pi.on("message_start", (event) => {
|
|
27
|
+
if (!active || event.message.role !== "user")
|
|
28
|
+
return;
|
|
29
|
+
const content = event.message.content;
|
|
30
|
+
const text = typeof content === "string" ? content : content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
31
|
+
if (text !== active.prompt)
|
|
32
|
+
active.error = "Another user message interfered with this Captain request";
|
|
33
|
+
});
|
|
34
|
+
pi.on("tool_result", (event) => {
|
|
35
|
+
if (active?.started === true && event.isError)
|
|
36
|
+
active.error = `Captain tool failed: ${event.toolName}. Inspect the conversation and retry explicitly.`;
|
|
37
|
+
});
|
|
38
|
+
pi.on("message_end", (event) => {
|
|
39
|
+
if (active?.started !== true || event.message.role !== "assistant")
|
|
40
|
+
return;
|
|
41
|
+
active.text = event.message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
42
|
+
active.stopReason = event.message.stopReason;
|
|
43
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
44
|
+
active.error = event.message.errorMessage ?? `Captain request ${event.message.stopReason}`;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// agent_end can precede automatic retries/compaction. Settlement is the final boundary.
|
|
48
|
+
pi.on("agent_settled", () => {
|
|
49
|
+
if (active?.started !== true)
|
|
50
|
+
return;
|
|
51
|
+
const result = active;
|
|
52
|
+
active = undefined;
|
|
53
|
+
const error = result.error ?? (result.stopReason !== "stop" || !result.text.trim()
|
|
54
|
+
? "Captain request ended without a successful final text response" : result.text.length > MAX_FINDINGS
|
|
55
|
+
? "Captain reply exceeded the storage limit" : undefined);
|
|
56
|
+
if (error === undefined && result.nextPrompt !== undefined) {
|
|
57
|
+
active = { requestId: result.requestId, prompt: result.nextPrompt, started: false, text: "", stopReason: "" };
|
|
58
|
+
try {
|
|
59
|
+
pi.sendUserMessage(active.prompt);
|
|
60
|
+
}
|
|
61
|
+
catch (failure) {
|
|
62
|
+
active = undefined;
|
|
63
|
+
reply(result.requestId, { status: "failed", error: String(failure) });
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
reply(result.requestId, error !== undefined ? { status: "failed", error } : { status: "completed", text: result.text });
|
|
68
|
+
});
|
|
69
|
+
// Read only this companion's active branch; never prompt, rename, or append to the source.
|
|
70
|
+
pi.events.on(SOURCE_REQUEST, (data) => {
|
|
71
|
+
if (typeof data !== "object" || data === null || !("requestId" in data)
|
|
72
|
+
|| typeof data.requestId !== "string" || !UUID.test(data.requestId))
|
|
73
|
+
return;
|
|
74
|
+
const respond = (result) => { pi.events.emit(SOURCE_REPLY, { requestId: data.requestId, ...result }); };
|
|
75
|
+
try {
|
|
76
|
+
if (!context || active || !context.isIdle() || context.hasPendingMessages())
|
|
77
|
+
throw new Error("Source session must be idle with no pending messages. Wait for it to finish, then translate again.");
|
|
78
|
+
if (context.sessionManager.getEntries().some((entry) => entry.type === "custom" && (entry.customType === PIRATE_MARKER || entry.customType === "captains-log:request")))
|
|
79
|
+
throw new Error("Select a source session other than the pirate conversation.");
|
|
80
|
+
for (const entry of [...context.sessionManager.getBranch()].reverse()) {
|
|
81
|
+
if (entry.type !== "message" || entry.message.role !== "assistant" || entry.message.stopReason !== "stop")
|
|
82
|
+
continue;
|
|
83
|
+
const text = entry.message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
84
|
+
if (!text.trim())
|
|
85
|
+
continue;
|
|
86
|
+
if (text.length > MAX_FINDINGS)
|
|
87
|
+
throw new Error("Source reply exceeds the translation limit (48000 characters).");
|
|
88
|
+
respond({ status: "completed", text });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
throw new Error("Source session has no completed assistant text reply on its active branch.");
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
respond({ status: "failed", error: error instanceof Error ? error.message : String(error) });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// Pi owns listeners and replaces them on /reload. No background resources at load time.
|
|
98
|
+
pi.events.on(CAPTAIN_REQUEST, (data) => {
|
|
99
|
+
if (typeof data !== "object" || data === null || !("requestId" in data)
|
|
100
|
+
|| typeof data.requestId !== "string" || !UUID.test(data.requestId))
|
|
101
|
+
return;
|
|
102
|
+
if (!("text" in data) || typeof data.text !== "string" || !data.text.trim() || data.text.length > MAX_FINDINGS) {
|
|
103
|
+
reply(data.requestId, { status: "failed", error: "Invalid translation text" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const { requestId } = data;
|
|
107
|
+
if (!context || active || !context.isIdle() || context.hasPendingMessages()) {
|
|
108
|
+
reply(requestId, { status: "failed", error: "Companion not ready or session busy; open Captain's Log in Sessions, wait for idle, then retry" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const introduced = context.sessionManager.getEntries().some((entry) => entry.type === "custom" && entry.customType === PIRATE_MARKER);
|
|
112
|
+
const translation = captainPrompt(data.text);
|
|
113
|
+
const prompt = introduced ? translation : PIRATE_INTRO;
|
|
114
|
+
active = { requestId, prompt, ...(introduced ? {} : { nextPrompt: translation }), started: false, text: "", stopReason: "" };
|
|
115
|
+
try {
|
|
116
|
+
pi.setSessionName("Captain's Log");
|
|
117
|
+
pi.appendEntry("captains-log:request", { requestId });
|
|
118
|
+
reply(requestId, { status: "accepted" });
|
|
119
|
+
pi.sendUserMessage(prompt);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
active = undefined;
|
|
123
|
+
reply(requestId, { status: "failed", error: error instanceof Error ? error.message : String(error) });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated from pi-packages/captains-log/src/roundtrip.ts. Do not edit directly.
|
|
2
|
+
import { MAX_FINDINGS, CAPTAIN_REPLY, CAPTAIN_REQUEST, SOURCE_REPLY, SOURCE_REQUEST } from "./browser/protocol.js";
|
|
3
|
+
export function collectReply(connection, requestId, text, lifetime, progress) {
|
|
4
|
+
return collect(connection, requestId, { requestId, text }, CAPTAIN_REQUEST, CAPTAIN_REPLY, lifetime, progress);
|
|
5
|
+
}
|
|
6
|
+
export function collectSourceReply(connection, requestId, lifetime, progress) {
|
|
7
|
+
return collect(connection, requestId, { requestId }, SOURCE_REQUEST, SOURCE_REPLY, lifetime, progress);
|
|
8
|
+
}
|
|
9
|
+
/** Subscribe before sending. Never retry an ambiguous send or cancel Pi on timeout. */
|
|
10
|
+
async function collect(connection, requestId, payload, requestEvent, replyEvent, lifetime, progress) {
|
|
11
|
+
const signal = AbortSignal.any([lifetime, connection.signal]);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
let unsubscribe;
|
|
14
|
+
let onAbort;
|
|
15
|
+
let receiptTimer;
|
|
16
|
+
let completionTimer;
|
|
17
|
+
try {
|
|
18
|
+
return await new Promise((resolve, reject) => {
|
|
19
|
+
onAbort = () => { reject(new Error("Captain connection interrupted; agent work may continue in Sessions")); };
|
|
20
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
21
|
+
receiptTimer = setTimeout(() => { reject(new Error("No companion receipt within 5 seconds. Check package enablement/trust; inspect Sessions before retrying.")); }, 5_000);
|
|
22
|
+
completionTimer = setTimeout(() => { reject(new Error("Captain request exceeded 10 minutes; agent work may continue in Sessions. No translation saved.")); }, 600_000);
|
|
23
|
+
unsubscribe = connection.on(replyEvent, (data) => {
|
|
24
|
+
if (typeof data !== "object" || data === null || !("requestId" in data) || data.requestId !== requestId)
|
|
25
|
+
return;
|
|
26
|
+
if (!("status" in data))
|
|
27
|
+
return;
|
|
28
|
+
if (data.status === "accepted") {
|
|
29
|
+
clearTimeout(receiptTimer);
|
|
30
|
+
progress("Native companion received request");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (data.status === "running") {
|
|
34
|
+
progress("Native Pi agent running");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (data.status === "failed") {
|
|
38
|
+
reject(new Error("error" in data && typeof data.error === "string" ? data.error.slice(0, 4000) : "Captain request failed"));
|
|
39
|
+
}
|
|
40
|
+
else if (data.status === "completed") {
|
|
41
|
+
if (!("text" in data) || typeof data.text !== "string" || !data.text.trim() || data.text.length > MAX_FINDINGS) {
|
|
42
|
+
reject(new Error("Invalid companion reply"));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
progress("Backend received correlated final reply");
|
|
46
|
+
resolve(data.text);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// Native listeners can reply synchronously.
|
|
51
|
+
progress("Backend sent request to native companion");
|
|
52
|
+
connection.emit(requestEvent, payload);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(receiptTimer);
|
|
57
|
+
clearTimeout(completionTimer);
|
|
58
|
+
if (onAbort)
|
|
59
|
+
signal.removeEventListener("abort", onAbort);
|
|
60
|
+
unsubscribe?.();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Generated from pi-packages/captains-log/src/server.ts. Do not edit directly.
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { PI_WEB_HOST_PI_SESSIONS_CAPABILITY, PI_WEB_HOST_PI_SESSION_EVENTS_CAPABILITY } from "@jmfederico/pi-web/server-plugin-api";
|
|
4
|
+
import {} from "./browser/protocol.js";
|
|
5
|
+
import { LOG_CHANNEL, entryFrames, isCaptainClientFrame } from "./browser/channelProtocol.js";
|
|
6
|
+
import { collectReply, collectSourceReply } from "./roundtrip.js";
|
|
7
|
+
import { LogStore, logScope } from "./store.js";
|
|
8
|
+
const plugin = {
|
|
9
|
+
apiVersion: 3,
|
|
10
|
+
name: "Captain's Log",
|
|
11
|
+
requires: [PI_WEB_HOST_PI_SESSIONS_CAPABILITY, PI_WEB_HOST_PI_SESSION_EVENTS_CAPABILITY],
|
|
12
|
+
activate(host) {
|
|
13
|
+
const store = new LogStore(host.dataDirectory);
|
|
14
|
+
const active = new Map();
|
|
15
|
+
const unsaved = new Map();
|
|
16
|
+
const currentSessions = new Map();
|
|
17
|
+
const subscribers = new Map();
|
|
18
|
+
const channelCleanups = new Set();
|
|
19
|
+
function publish(scope, entry, saved) {
|
|
20
|
+
for (const frame of entryFrames(entry, saved))
|
|
21
|
+
for (const send of subscribers.get(scope) ?? [])
|
|
22
|
+
send(frame);
|
|
23
|
+
}
|
|
24
|
+
const tasks = new Set();
|
|
25
|
+
let sessions;
|
|
26
|
+
let events;
|
|
27
|
+
function visible(scope, entry) {
|
|
28
|
+
const live = active.get(scope);
|
|
29
|
+
if (live?.id === entry.id)
|
|
30
|
+
return live;
|
|
31
|
+
return unsaved.get(entry.id) ?? (entry.status === "running"
|
|
32
|
+
? { ...entry, status: "interrupted", failureCode: "interrupted", text: "Captain's Log stopped before recording completion. Inspect the previous session before retrying. Nothing was resent automatically." }
|
|
33
|
+
: entry);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
start({ capabilities }) {
|
|
37
|
+
sessions = capabilities.resolve(PI_WEB_HOST_PI_SESSIONS_CAPABILITY);
|
|
38
|
+
events = capabilities.resolve(PI_WEB_HOST_PI_SESSION_EVENTS_CAPABILITY);
|
|
39
|
+
},
|
|
40
|
+
peer: {
|
|
41
|
+
async request(context) {
|
|
42
|
+
context.signal.throwIfAborted();
|
|
43
|
+
host.lifetimeSignal.throwIfAborted();
|
|
44
|
+
const selection = { projectId: context.project.id, workspaceId: context.workspace.id };
|
|
45
|
+
const scope = logScope(selection.projectId, selection.workspaceId);
|
|
46
|
+
if (context.operation === "list") {
|
|
47
|
+
const entries = (await store.list(scope)).map((entry) => visible(scope, entry));
|
|
48
|
+
const live = active.get(scope);
|
|
49
|
+
if (live && !entries.some((entry) => entry.id === live.id))
|
|
50
|
+
entries.unshift(live);
|
|
51
|
+
return entries.map((entry) => ({ ...entry, text: "" }));
|
|
52
|
+
}
|
|
53
|
+
if (context.operation === "read") {
|
|
54
|
+
const live = active.get(scope);
|
|
55
|
+
return { ...(live?.id === context.input ? live : visible(scope, await store.read(scope, context.input))) };
|
|
56
|
+
}
|
|
57
|
+
throw new Error("Unknown Captain operation (translations require the log channel)");
|
|
58
|
+
},
|
|
59
|
+
openChannel(context) {
|
|
60
|
+
context.signal.throwIfAborted();
|
|
61
|
+
host.lifetimeSignal.throwIfAborted();
|
|
62
|
+
if (context.operation !== LOG_CHANNEL || context.input !== null)
|
|
63
|
+
throw new Error("Invalid Captain channel open");
|
|
64
|
+
const selection = { projectId: context.project.id, workspaceId: context.workspace.id };
|
|
65
|
+
const scope = logScope(selection.projectId, selection.workspaceId);
|
|
66
|
+
let listening = true;
|
|
67
|
+
const send = (frame) => {
|
|
68
|
+
if (!listening)
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
context.send(frame.type === "entry" ? { ...frame, entry: { ...frame.entry } } : frame);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
cleanup();
|
|
75
|
+
host.logger.warn(`Captain subscriber disconnected: ${String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const cleanup = () => {
|
|
79
|
+
listening = false;
|
|
80
|
+
subscribers.get(scope)?.delete(send);
|
|
81
|
+
if ((subscribers.get(scope)?.size ?? 0) === 0)
|
|
82
|
+
subscribers.delete(scope);
|
|
83
|
+
context.signal.removeEventListener("abort", cleanup);
|
|
84
|
+
host.lifetimeSignal.removeEventListener("abort", cleanup);
|
|
85
|
+
channelCleanups.delete(cleanup);
|
|
86
|
+
};
|
|
87
|
+
if (!subscribers.has(scope))
|
|
88
|
+
subscribers.set(scope, new Set());
|
|
89
|
+
subscribers.get(scope)?.add(send);
|
|
90
|
+
context.signal.addEventListener("abort", cleanup, { once: true });
|
|
91
|
+
host.lifetimeSignal.addEventListener("abort", cleanup, { once: true });
|
|
92
|
+
channelCleanups.add(cleanup);
|
|
93
|
+
return {
|
|
94
|
+
close: cleanup,
|
|
95
|
+
async receive(data, signal) {
|
|
96
|
+
signal.throwIfAborted();
|
|
97
|
+
host.lifetimeSignal.throwIfAborted();
|
|
98
|
+
if (!isCaptainClientFrame(data))
|
|
99
|
+
throw new Error("Invalid Captain translation frame");
|
|
100
|
+
const reject = (message) => { send({ type: "rejected", requestId: data.requestId, message: message.slice(0, 2000) }); };
|
|
101
|
+
if (active.has(scope)) {
|
|
102
|
+
reject("Captain is already working.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const entry = { id: randomUUID(), createdAt: new Date().toISOString(), sessionId: "", question: "Translate last reply", sourceSessionId: data.sourceSessionId, status: "running", stages: ["Backend admitted translation"], text: "Preparing Captain's Log…" };
|
|
106
|
+
// Lock before any I/O so concurrent tabs cannot create two dedicated sessions.
|
|
107
|
+
active.set(scope, entry);
|
|
108
|
+
try {
|
|
109
|
+
const previous = await store.list(scope);
|
|
110
|
+
// Strictly increasing admission times make persisted session selection independent of UUID ties.
|
|
111
|
+
const latestTime = Math.max(0, ...previous.map((record) => Date.parse(record.createdAt)).filter(Number.isFinite));
|
|
112
|
+
entry.createdAt = new Date(Math.max(Date.now(), latestTime + 1)).toISOString();
|
|
113
|
+
entry.sessionId = currentSessions.get(scope) ?? previous.find((record) => record.sessionId)?.sessionId ?? "";
|
|
114
|
+
if (entry.sessionId === data.sourceSessionId || previous.some((record) => record.sessionId === data.sourceSessionId))
|
|
115
|
+
throw new Error("Select a source session other than the pirate conversation.");
|
|
116
|
+
await store.save(scope, entry);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
active.delete(scope);
|
|
120
|
+
reject(String(error));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
send({ type: "admitted", requestId: data.requestId, id: entry.id });
|
|
124
|
+
publish(scope, entry, true);
|
|
125
|
+
const progress = (stage) => {
|
|
126
|
+
if (!entry.stages.includes(stage) && entry.stages.length < 16)
|
|
127
|
+
entry.stages.push(stage);
|
|
128
|
+
publish(scope, entry, false);
|
|
129
|
+
};
|
|
130
|
+
const task = (async () => {
|
|
131
|
+
try {
|
|
132
|
+
host.lifetimeSignal.throwIfAborted();
|
|
133
|
+
const source = await events.connect({ ...selection, sessionId: data.sourceSessionId });
|
|
134
|
+
let sourceText;
|
|
135
|
+
try {
|
|
136
|
+
sourceText = await collectSourceReply(source, entry.id, host.lifetimeSignal, progress);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
source.close();
|
|
140
|
+
}
|
|
141
|
+
progress("Source reply read without changing source");
|
|
142
|
+
const createPirate = async () => {
|
|
143
|
+
({ sessionId: entry.sessionId } = await sessions.create(selection));
|
|
144
|
+
currentSessions.set(scope, entry.sessionId);
|
|
145
|
+
// Persist identity before any potentially ambiguous send.
|
|
146
|
+
await store.save(scope, entry);
|
|
147
|
+
return events.connect({ ...selection, sessionId: entry.sessionId });
|
|
148
|
+
};
|
|
149
|
+
const previousSessionId = entry.sessionId;
|
|
150
|
+
const connection = previousSessionId
|
|
151
|
+
? await events.connect({ ...selection, sessionId: previousSessionId }).catch((error) => {
|
|
152
|
+
// Only a definitive rejection before sending permits automatic replacement.
|
|
153
|
+
if (error instanceof Error && error.message === "Selected session is not hosted in this workspace on this machine")
|
|
154
|
+
return createPirate();
|
|
155
|
+
throw error;
|
|
156
|
+
})
|
|
157
|
+
: await createPirate();
|
|
158
|
+
progress("Pirate conversation selected");
|
|
159
|
+
try {
|
|
160
|
+
entry.text = await collectReply(connection, entry.id, sourceText, host.lifetimeSignal, progress);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
connection.close();
|
|
164
|
+
}
|
|
165
|
+
entry.status = "completed";
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
entry.status = "failed";
|
|
169
|
+
entry.failureCode ??= "request-failed";
|
|
170
|
+
entry.text = `Captain could not finish this request. Inspect the session before retrying; nothing was resent automatically.\n${error instanceof Error ? error.message : String(error)}`.slice(0, 4000);
|
|
171
|
+
progress("Backend recorded failure (not success)");
|
|
172
|
+
}
|
|
173
|
+
let saved = false;
|
|
174
|
+
try {
|
|
175
|
+
if (entry.status === "completed")
|
|
176
|
+
entry.stages.push("Backend saved completed roundtrip");
|
|
177
|
+
await store.save(scope, entry);
|
|
178
|
+
saved = true;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
entry.status = "failed";
|
|
182
|
+
entry.failureCode = "request-failed";
|
|
183
|
+
entry.stages = entry.stages.filter((stage) => stage !== "Backend saved completed roundtrip");
|
|
184
|
+
entry.text = `Could not save Captain's Log: ${String(error)}. Inspect session ${entry.sessionId}.`.slice(0, 4000);
|
|
185
|
+
unsaved.set(entry.id, { ...entry });
|
|
186
|
+
host.logger.error(entry.text);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
active.delete(scope);
|
|
190
|
+
publish(scope, entry, saved);
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
tasks.add(task);
|
|
194
|
+
void task.finally(() => { tasks.delete(task); });
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
async dispose() {
|
|
200
|
+
await Promise.all(tasks);
|
|
201
|
+
for (const cleanup of channelCleanups)
|
|
202
|
+
cleanup();
|
|
203
|
+
active.clear();
|
|
204
|
+
unsaved.clear();
|
|
205
|
+
currentSessions.clear();
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
export default plugin;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Generated from pi-packages/captains-log/src/store.ts. Do not edit directly.
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { lstat, mkdir, open, readdir, rename, rm } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { isLogEntry } from "./browser/protocol.js";
|
|
7
|
+
/** Only host-resolved ids enter the scope hash; peer input is never a filesystem path. */
|
|
8
|
+
export function logScope(projectId, workspaceId) {
|
|
9
|
+
return createHash("sha256").update(JSON.stringify([projectId, workspaceId])).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
export class LogStore {
|
|
12
|
+
dataDirectory;
|
|
13
|
+
constructor(dataDirectory) {
|
|
14
|
+
this.dataDirectory = dataDirectory;
|
|
15
|
+
}
|
|
16
|
+
async directory(scope) {
|
|
17
|
+
if (!/^[a-f0-9]{64}$/.test(scope))
|
|
18
|
+
throw new Error("Invalid captain scope");
|
|
19
|
+
const directory = join(this.dataDirectory, scope);
|
|
20
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
21
|
+
if (!(await lstat(directory)).isDirectory())
|
|
22
|
+
throw new Error("LogEntry directory must not be a symlink");
|
|
23
|
+
return directory;
|
|
24
|
+
}
|
|
25
|
+
async save(scope, captain) {
|
|
26
|
+
if (!isLogEntry(captain))
|
|
27
|
+
throw new Error("Invalid captain record");
|
|
28
|
+
const directory = await this.directory(scope);
|
|
29
|
+
const temporary = join(directory, `.${randomUUID()}.tmp`);
|
|
30
|
+
try {
|
|
31
|
+
const file = await open(temporary, "wx", 0o600);
|
|
32
|
+
try {
|
|
33
|
+
await file.writeFile(JSON.stringify(captain));
|
|
34
|
+
await file.sync();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await file.close();
|
|
38
|
+
}
|
|
39
|
+
await rename(temporary, join(directory, `${captain.id}.json`));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await rm(temporary, { force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async read(scope, id) {
|
|
46
|
+
if (typeof id !== "string" || !/^[a-f0-9-]{36}$/.test(id))
|
|
47
|
+
throw new Error("Invalid captain id");
|
|
48
|
+
const path = join(await this.directory(scope), `${id}.json`);
|
|
49
|
+
// O_NOFOLLOW is not enforced on Windows; reject existing links there too.
|
|
50
|
+
if (!(await lstat(path)).isFile())
|
|
51
|
+
throw new Error("Invalid captain file");
|
|
52
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
53
|
+
try {
|
|
54
|
+
const stat = await file.stat();
|
|
55
|
+
if (!stat.isFile() || stat.size > 300_000)
|
|
56
|
+
throw new Error("Invalid captain file");
|
|
57
|
+
const value = JSON.parse(await file.readFile("utf8"));
|
|
58
|
+
if (!isLogEntry(value) || value.id !== id)
|
|
59
|
+
throw new Error("Invalid saved captain");
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await file.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async list(scope) {
|
|
67
|
+
const names = await readdir(await this.directory(scope));
|
|
68
|
+
const captains = [];
|
|
69
|
+
for (const name of names) {
|
|
70
|
+
if (/^[a-f0-9-]{36}\.json$/.test(name))
|
|
71
|
+
captains.push(await this.read(scope, name.slice(0, -5)));
|
|
72
|
+
}
|
|
73
|
+
return captains.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Using Captain's Log
|
|
2
|
+
|
|
3
|
+
Select a conversation in the current workspace, open **Captain's Log**, and click **Let the Captain tell it**. The companion reads that session's latest completed assistant reply without sending it a prompt or changing its transcript. A separate pirate conversation retells the text using your configured model. The prompt asks for a fresh, theatrical crew briefing with nautical metaphors and humor—not just a few pirate words. It asks the model to preserve important facts, warnings, and next steps, and keep code and commands exact.
|
|
4
|
+
|
|
5
|
+
The pirate gets a short introduction on its first prompt and keeps speaking pirate. Further translations use the same conversation while it is loaded in the session daemon. If it is no longer loaded, the next translation automatically creates a new pirate conversation. There are no confirmation dialogs, workspace reviews, or plain-English mode.
|
|
6
|
+
|
|
7
|
+
To keep the previous pirate's context after a daemon restart, open the most recent **Captain's Log** conversation in Sessions before translating. Once loading finishes, the plugin can reuse it; it need not remain selected. Then select the conversation whose reply you want translated. The plugin does not reopen saved sessions itself or stop/delete old sessions.
|
|
8
|
+
|
|
9
|
+
## Results and connections
|
|
10
|
+
|
|
11
|
+
- The translation is rendered as Markdown, including headings, lists, emphasis, links, tables, and code blocks. Embedded HTML stays literal, unsafe links are inert, and images are not loaded automatically. **Previous translations** holds earlier results; **Diagnostics** holds session/request IDs and the message trail. Both start collapsed. Existing review-era logs remain on disk but are not shown as translations.
|
|
12
|
+
- The panel inherits PI WEB's workspace styles and themes. The action-palette **Open pirate translator** opens it without spending model tokens.
|
|
13
|
+
- Browser requests and backend progress use a bidirectional peer channel, with no polling. Opening or reconnecting loads a snapshot. Progress is not token-by-token model streaming; the completed translation arrives when the pirate finishes.
|
|
14
|
+
- Closing the panel or browser does not cancel admitted work. Reconnect to recover the result. Requests with uncertain delivery are never automatically resent. Automatic pirate creation happens only before sending a translation, when the previous pirate is confirmed unhosted—not after a timeout or arbitrary connection failure.
|
|
15
|
+
|
|
16
|
+
## When a translation cannot run
|
|
17
|
+
|
|
18
|
+
Select an ordinary, unarchived conversation in the same workspace and machine, not the pirate itself. Wait for it to finish responding. It needs a completed assistant reply with text; partial, aborted, failed, and tool-call-only replies are not translations. If the native companion is missing from an existing session, use `/reload` in that session and try again.
|
|
19
|
+
|
|
20
|
+
Only one panel translation per workspace runs at a time. Busy pirates, direct user messages during a translation, provider errors, aborted or truncated replies, and runtime replacement are reported as failures rather than successful translations. A receipt timeout or completion timeout detaches listeners but does not stop Pi. Inspect the pirate conversation before retrying an uncertain request. Very large source replies or results are rejected rather than silently truncated; see Diagnostics for the reason.
|
|
21
|
+
|
|
22
|
+
Translations and metadata are stored under the host-provided plugin data directory, scoped to the project/workspace. Pi owns the conversation transcripts. Interrupted records remain visible; there is no durable job resumption or automatic retention policy. The source text is sent to the pirate's model provider. This is trusted agent code, not a tool sandbox.
|
|
23
|
+
|
|
24
|
+
## Installation and activation
|
|
25
|
+
|
|
26
|
+
Install from **Settings → Pi packages → Available packages** on the target machine, then explicitly enable **Captain's Log** in **Settings → PI WEB plugins**. The package ships compiled. Manual installation can use `pi install /absolute/path/to/dist/pi-packages/captains-log` with the target machine's Pi profile.
|
|
27
|
+
|
|
28
|
+
Restart the target session daemon manually when safe, **from outside its hosted sessions**, then reload the browser. A web/API restart alone cannot activate the backend. Existing sessions need `/reload` to load an updated native companion. Project-scoped packages require normal project trust.
|
|
29
|
+
|
|
30
|
+
Disabling the PI WEB plugin controls its panel/backend, not its native extension. Remove the Pi package to remove both resource declarations; saved conversations and plugin data are retained. The older Workspace Reviews example has a separate plugin ID and must be disabled or removed separately.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jmfederico/pi-captains-log",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opt-in Captain's Log: translate the selected session's last reply into pirate through a native Pi companion and PI WEB panel",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": ["pi-package"],
|
|
8
|
+
"files": ["dist", "src", "docs", "test", "tsconfig.json", "vite.config.mjs"],
|
|
9
|
+
"scripts": { "build": "tsc -p tsconfig.json && vite build", "test": "node --test test/*.test.mjs" },
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"marked": "^18.0.6"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@jmfederico/pi-web": "^1.202609.1",
|
|
15
|
+
"@earendil-works/pi-coding-agent": "^0.87.0",
|
|
16
|
+
"@types/node": "^24.13.3",
|
|
17
|
+
"typescript": "^6.0.3",
|
|
18
|
+
"vite": "^8.1.4"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
22
|
+
"@jmfederico/pi-web": "^1.202609.1"
|
|
23
|
+
},
|
|
24
|
+
"pi": { "extensions": ["./dist/companion.js"] },
|
|
25
|
+
"piWeb": {
|
|
26
|
+
"plugins": [{
|
|
27
|
+
"id": "captains-log",
|
|
28
|
+
"defaultEnabled": false,
|
|
29
|
+
"browserRoot": "dist/browser",
|
|
30
|
+
"module": "dist/browser/index.js",
|
|
31
|
+
"serverModule": "dist/server.js",
|
|
32
|
+
"machineSpecific": true
|
|
33
|
+
}]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"exactOptionalPropertyTypes": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"noImplicitReturns": true,
|
|
14
|
+
"noFallthroughCasesInSwitch": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"noEmitOnError": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*.ts"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
const packageDir = fileURLToPath(new URL(".", import.meta.url));
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
root: packageDir,
|
|
8
|
+
base: "./",
|
|
9
|
+
publicDir: false,
|
|
10
|
+
build: {
|
|
11
|
+
outDir: "dist/browser",
|
|
12
|
+
// Retain the transpiled filenames for existing package/install consumers.
|
|
13
|
+
emptyOutDir: false,
|
|
14
|
+
copyPublicDir: false,
|
|
15
|
+
target: "es2022",
|
|
16
|
+
sourcemap: false,
|
|
17
|
+
rollupOptions: {
|
|
18
|
+
input: fileURLToPath(new URL("src/browser/index.ts", import.meta.url)),
|
|
19
|
+
preserveEntrySignatures: "strict",
|
|
20
|
+
output: {
|
|
21
|
+
format: "es",
|
|
22
|
+
entryFileNames: "index.js",
|
|
23
|
+
chunkFileNames: "assets/[name]-[hash].js",
|
|
24
|
+
assetFileNames: "assets/[name]-[hash][extname]",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@jmfederico/pi-relay",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Tool-agnostic Relay method, opinionated Relay Runner profile, and human-gated preparation prompts for independent Pi session chains.",
|
|
6
6
|
"piWeb": {
|
|
7
7
|
"plugins": [
|
|
8
8
|
{ "id": "relays", "browserRoot": ".", "module": "pi-web-plugin.js" }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { RELAYS_ROOT } from "./relayDiscovery.js";
|
|
3
3
|
import { defineRelaysPanelElement } from "./relaysPanelElement.js";
|
|
4
4
|
const plugin = {
|
|
5
|
-
apiVersion:
|
|
5
|
+
apiVersion: 4,
|
|
6
6
|
name: "Relays",
|
|
7
7
|
activate: ({ runtimePluginId, html, svg }) => {
|
|
8
8
|
defineRelaysPanelElement();
|