my-durable 0.0.0-stage → 0.0.2
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/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/ai_docs/durable_pi/announcement.md +860 -0
- package/ai_docs/durable_pi/changelog.md +51 -0
- package/ai_docs/durable_pi/chord-delta-findings.md +362 -0
- package/ai_docs/durable_pi/coding-agent-integration.md +70 -0
- package/ai_docs/durable_pi/pico-v5-chord-usage.md +391 -0
- package/ai_docs/durable_pi/pico-v5-handoff.md +921 -0
- package/ai_docs/durable_pi/readme.md +617 -0
- package/ai_docs/durable_pi/sources.md +22 -0
- package/ai_docs/durable_pi/spec.md +4696 -0
- package/ai_docs/durable_pi/vacation-planner.md +57 -0
- package/ai_docs/pi/chord.md +268 -0
- package/ai_docs/pi/cli-integration.md +110 -0
- package/ai_docs/pi/cli.md +325 -0
- package/ai_docs/pi/codemode.md +211 -0
- package/ai_docs/pi/compaction.md +467 -0
- package/ai_docs/pi/configuration.md +51 -0
- package/ai_docs/pi/containerization.md +187 -0
- package/ai_docs/pi/custom-provider.md +177 -0
- package/ai_docs/pi/environment-variables.md +102 -0
- package/ai_docs/pi/extensions.md +278 -0
- package/ai_docs/pi/how-pi-works.md +53 -0
- package/ai_docs/pi/index.md +43 -0
- package/ai_docs/pi/json.md +230 -0
- package/ai_docs/pi/keybindings.md +198 -0
- package/ai_docs/pi/llama-cpp.md +118 -0
- package/ai_docs/pi/mcp.md +274 -0
- package/ai_docs/pi/message-types.md +265 -0
- package/ai_docs/pi/models.md +209 -0
- package/ai_docs/pi/overview.md +166 -0
- package/ai_docs/pi/packages.md +133 -0
- package/ai_docs/pi/pi-agent-core.md +568 -0
- package/ai_docs/pi/pi-ai.md +1942 -0
- package/ai_docs/pi/prompt-templates.md +63 -0
- package/ai_docs/pi/providers.md +204 -0
- package/ai_docs/pi/quickstart.md +140 -0
- package/ai_docs/pi/readme.md +99 -0
- package/ai_docs/pi/rpc-commands.md +864 -0
- package/ai_docs/pi/rpc-extension-ui.md +204 -0
- package/ai_docs/pi/rpc.md +197 -0
- package/ai_docs/pi/sdk.md +151 -0
- package/ai_docs/pi/security.md +103 -0
- package/ai_docs/pi/session-format.md +297 -0
- package/ai_docs/pi/sessions.md +70 -0
- package/ai_docs/pi/settings.md +173 -0
- package/ai_docs/pi/shell-aliases.md +97 -0
- package/ai_docs/pi/skills.md +97 -0
- package/ai_docs/pi/slash-commands.md +64 -0
- package/ai_docs/pi/sources.md +57 -0
- package/ai_docs/pi/terminal-setup.md +223 -0
- package/ai_docs/pi/termux.md +121 -0
- package/ai_docs/pi/themes.md +140 -0
- package/ai_docs/pi/tmux.md +59 -0
- package/ai_docs/pi/toolkit.md +168 -0
- package/ai_docs/pi/tui.md +129 -0
- package/ai_docs/pi/usage.md +98 -0
- package/ai_docs/pi/virtual-models.md +118 -0
- package/ai_docs/pi/windows.md +71 -0
- package/apps/dpi-backend/.nvmrc +1 -0
- package/apps/dpi-backend/API.md +99 -0
- package/apps/dpi-backend/README.md +119 -0
- package/apps/dpi-backend/deploy/backend.env.example +9 -0
- package/apps/dpi-backend/deploy/dpi-backend.plist +26 -0
- package/apps/dpi-backend/deploy/dpi-backend.service +22 -0
- package/apps/dpi-backend/package.json +33 -0
- package/apps/dpi-backend/prompts/one_sentence.md +3 -0
- package/apps/dpi-backend/prompts/smart_ass_opus_fix.md +169 -0
- package/apps/dpi-backend/scripts/check-contract.mjs +151 -0
- package/apps/dpi-backend/scripts/migrate-playground.ts +66 -0
- package/apps/dpi-backend/scripts/record-nested-fixture.mts +154 -0
- package/apps/dpi-backend/scripts/token.ts +57 -0
- package/apps/dpi-backend/src/addons.ts +139 -0
- package/apps/dpi-backend/src/auth.ts +20 -0
- package/apps/dpi-backend/src/config.ts +54 -0
- package/apps/dpi-backend/src/contract-check.ts +118 -0
- package/apps/dpi-backend/src/errors.ts +50 -0
- package/apps/dpi-backend/src/extensions.ts +510 -0
- package/apps/dpi-backend/src/faux.ts +80 -0
- package/apps/dpi-backend/src/history.ts +165 -0
- package/apps/dpi-backend/src/idempotency.ts +20 -0
- package/apps/dpi-backend/src/jev.ts +43 -0
- package/apps/dpi-backend/src/jobs.ts +790 -0
- package/apps/dpi-backend/src/main.ts +76 -0
- package/apps/dpi-backend/src/model-catalog.ts +180 -0
- package/apps/dpi-backend/src/pairing-files.ts +142 -0
- package/apps/dpi-backend/src/pairing.ts +459 -0
- package/apps/dpi-backend/src/routes.ts +217 -0
- package/apps/dpi-backend/src/shared-state.ts +85 -0
- package/apps/dpi-backend/src/store.ts +1240 -0
- package/apps/dpi-backend/src/streams.ts +256 -0
- package/apps/dpi-backend/src/tunnel.ts +955 -0
- package/apps/dpi-backend/tsconfig.json +13 -0
- package/apps/dpi-playground/README.md +83 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-CFGc7lHT.js +77 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DFdKpwm_.css +1 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/index.html +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/index.html +15 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/App.vue +180 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/commands.ts +119 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/composables.ts +129 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/hub.ts +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/icon-path.ts +7 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/ids.ts +2 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/jobs.ts +80 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/link-policy.ts +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown-policy.ts +115 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-close.ts +23 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-handoff.ts +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair.ts +166 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/qrcode.ts +323 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/signature.ts +146 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/store.ts +373 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/stream.ts +183 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/AgentView.vue +342 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Composer.vue +198 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ContextBar.vue +254 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceCredentialsDialog.vue +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceIcon.vue +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DirBrowser.vue +95 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ExtensionsPanel.vue +96 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/HelpDialog.vue +69 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Inspector.vue +425 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/JobCard.vue +221 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/MobileNav.vue +39 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ModelPicker.vue +71 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NetworkSidebar.vue +236 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NewSessionDialog.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/OfflinePanel.vue +128 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PairDeviceWizard.vue +433 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PromptDialog.vue +89 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/StateEditor.vue +179 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TasksPanel.vue +280 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Toast.vue +11 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TokenManager.vue +175 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TopBar.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TranscriptItem.vue +152 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TreeRow.vue +195 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TunnelStatusBar.vue +107 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/extensions.css +105 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/jobs.css +655 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/main.ts +22 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/network.css +675 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/style.css +3203 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/tsconfig.json +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/vite.config.ts +14 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/audit.ts +406 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/client-ip.ts +72 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/credentials.ts +265 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/guard.ts +43 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/http-errors.ts +156 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/identity.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/limiter.ts +165 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/main.ts +460 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/monitor.ts +320 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/mux.ts +277 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/pairing.ts +816 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/proxy.ts +128 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/rotation.ts +583 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/seed.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/transport.ts +108 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-token.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-transport.ts +560 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel.ts +873 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/tsconfig.json +13 -0
- package/apps/dpi-playground/package.json +37 -0
- package/apps/dpi-sdk/README.md +74 -0
- package/apps/dpi-sdk/package.json +20 -0
- package/apps/dpi-sdk/scripts/net.ts +73 -0
- package/apps/dpi-sdk/src/api.ts +631 -0
- package/apps/dpi-sdk/src/app-state.ts +111 -0
- package/apps/dpi-sdk/src/client.ts +330 -0
- package/apps/dpi-sdk/src/errors.ts +16 -0
- package/apps/dpi-sdk/src/jobs.ts +99 -0
- package/apps/dpi-sdk/src/models.ts +472 -0
- package/apps/dpi-sdk/src/node/chunk.ts +488 -0
- package/apps/dpi-sdk/src/node/config.ts +175 -0
- package/apps/dpi-sdk/src/node/drivers.ts +402 -0
- package/apps/dpi-sdk/src/node/ops.ts +114 -0
- package/apps/dpi-sdk/src/node/private-dir.ts +48 -0
- package/apps/dpi-sdk/src/node/registry-migration.ts +549 -0
- package/apps/dpi-sdk/src/node/registry.ts +390 -0
- package/apps/dpi-sdk/src/node/sse-writer.ts +151 -0
- package/apps/dpi-sdk/src/protocol/backoff.ts +31 -0
- package/apps/dpi-sdk/src/protocol/limits.ts +199 -0
- package/apps/dpi-sdk/src/protocol/pairing.ts +311 -0
- package/apps/dpi-sdk/src/protocol/rest-routes.ts +105 -0
- package/apps/dpi-sdk/src/protocol/tunnel.ts +331 -0
- package/apps/dpi-sdk/src/sse.ts +117 -0
- package/apps/dpi-sdk/src/transcript.ts +291 -0
- package/apps/dpi-sdk/tsconfig.json +13 -0
- package/apps/dpi-sdk/w +20 -0
- package/apps/dpi-tui/README.md +54 -0
- package/apps/dpi-tui/bin/dpi-tui.mjs +5 -0
- package/apps/dpi-tui/package.json +20 -0
- package/apps/dpi-tui/src/app.ts +433 -0
- package/apps/dpi-tui/src/commands.ts +151 -0
- package/apps/dpi-tui/src/main.ts +122 -0
- package/apps/dpi-tui/src/pairing.ts +287 -0
- package/apps/dpi-tui/src/pickers.ts +238 -0
- package/apps/dpi-tui/src/render.ts +164 -0
- package/apps/dpi-tui/src/theme.ts +57 -0
- package/apps/dpi-tui/tsconfig.json +14 -0
- package/bin/device.mjs +5 -0
- package/bin/hub.mjs +5 -0
- package/bin/tui.mjs +5 -0
- package/deploy/Caddyfile +120 -0
- package/docs/verification.md +102 -0
- package/dpi-network.example.yaml +62 -0
- package/images/01_durable_agent_network.svg +185 -0
- package/images/02_pi_made_durable.svg +204 -0
- package/images/03_restart_mid_tool.svg +168 -0
- package/images/07_one_api_every_client.svg +123 -0
- package/images/08_device_session_agent.svg +123 -0
- package/images/09_compact_keep_history.svg +143 -0
- package/images/10_shared_app_state.svg +177 -0
- package/images/11_durable_jobs_wait.svg +141 -0
- package/images/12_tools_change_mid_run.svg +231 -0
- package/images/13_hook_blocks_tool.svg +122 -0
- package/images/14_fork_at_any_entry.svg +119 -0
- package/images/15_web_client.png +0 -0
- package/images/16_terminal_client.svg +43 -0
- package/images/17_devices_are_data.svg +182 -0
- package/images/18_should_every_agent_be_durable.svg +118 -0
- package/images/19_sqlite_on_every_device.svg +155 -0
- package/images/20_admission_and_effects.svg +134 -0
- package/justfile +33 -0
- package/package.json +41 -4
- package/scripts/verify.mjs +210 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Normalize pi-durable entries + live generation state into renderable transcript items.
|
|
2
|
+
// The web UI, the TUI and the backend's /transcript endpoint all use this, so every
|
|
3
|
+
// interface stitches tool calls, results and live output the same way.
|
|
4
|
+
import type {
|
|
5
|
+
WireAssistantMessage,
|
|
6
|
+
WireEntry,
|
|
7
|
+
WireMessage,
|
|
8
|
+
WireToolResultMessage,
|
|
9
|
+
WireToolSlot,
|
|
10
|
+
WireUsage,
|
|
11
|
+
WireUsageState,
|
|
12
|
+
} from "./api.js";
|
|
13
|
+
|
|
14
|
+
export type ToolStatus = "pending" | "running" | "done" | "error";
|
|
15
|
+
|
|
16
|
+
export interface ToolCallView {
|
|
17
|
+
callId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
args: unknown;
|
|
20
|
+
status: ToolStatus;
|
|
21
|
+
/** Live output while running. */
|
|
22
|
+
output?: string;
|
|
23
|
+
/** Result text once the result entry exists, or once a nested call returns its result. */
|
|
24
|
+
result?: string;
|
|
25
|
+
resultEntryId?: number;
|
|
26
|
+
/** Nested call metadata: the parent call that made it, and the call's own task. */
|
|
27
|
+
nested?: { parentCallId: string; parentTaskId: number; taskId: number };
|
|
28
|
+
/** One-line summary of how a nested call ended (duration, spend, error). */
|
|
29
|
+
summary?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AssistantBlockView =
|
|
33
|
+
| { type: "text"; text: string }
|
|
34
|
+
| { type: "thinking"; text: string }
|
|
35
|
+
| { type: "tool"; call: ToolCallView };
|
|
36
|
+
|
|
37
|
+
export type TranscriptItem =
|
|
38
|
+
| { kind: "user"; id: number; text: string }
|
|
39
|
+
| {
|
|
40
|
+
kind: "assistant";
|
|
41
|
+
id: number | "live";
|
|
42
|
+
streaming: boolean;
|
|
43
|
+
blocks: AssistantBlockView[];
|
|
44
|
+
error?: string;
|
|
45
|
+
model?: string;
|
|
46
|
+
}
|
|
47
|
+
| { kind: "tool-result"; id: number; name: string; text: string; isError: boolean }
|
|
48
|
+
| { kind: "tool"; id: string; call: ToolCallView }
|
|
49
|
+
| { kind: "event"; id: number; entryKind: string; text: string; note?: string; data?: unknown };
|
|
50
|
+
|
|
51
|
+
export interface LiveTool {
|
|
52
|
+
name: string;
|
|
53
|
+
status: "pending" | "running" | "done";
|
|
54
|
+
output: string;
|
|
55
|
+
args?: unknown;
|
|
56
|
+
/** Set for a nested call (one a running tool made through `executeTool()`). */
|
|
57
|
+
nested?: { parentCallId: string; parentTaskId: number; taskId: number };
|
|
58
|
+
/** A nested result kept for display when no transcript entry holds it. */
|
|
59
|
+
result?: { text: string; isError: boolean };
|
|
60
|
+
/** How a nested call ended, for a status line (`NestedToolSummary`). */
|
|
61
|
+
summary?: { isError: boolean; durationMs?: number; usage?: unknown; error?: string };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isAssistant = (m: WireMessage): m is WireAssistantMessage => m.role === "assistant";
|
|
65
|
+
const isToolResult = (m: WireMessage): m is WireToolResultMessage => m.role === "toolResult";
|
|
66
|
+
|
|
67
|
+
export function messageText(message: WireMessage): string {
|
|
68
|
+
const content = (message as { content?: unknown }).content;
|
|
69
|
+
if (typeof content === "string") return content;
|
|
70
|
+
if (!Array.isArray(content)) return "";
|
|
71
|
+
return content
|
|
72
|
+
.map((block: { type?: string; text?: string }) => (block.type === "text" ? (block.text ?? "") : ""))
|
|
73
|
+
.join("");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function assistantError(message: WireAssistantMessage): string | undefined {
|
|
77
|
+
if (message.errorMessage) return message.errorMessage;
|
|
78
|
+
if (message.stopReason === "aborted") return "Interrupted. Partial response preserved.";
|
|
79
|
+
if (message.stopReason === "error") return "Provider request failed.";
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function eventText(entry: WireEntry): string {
|
|
84
|
+
const data = entry.data as { text?: unknown } | undefined;
|
|
85
|
+
if (typeof data?.text === "string") return data.text;
|
|
86
|
+
if (entry.kind === "pi.reset") return "New context started. Earlier entries remain stored.";
|
|
87
|
+
if (entry.kind === "pi.compaction") return "Context compacted. Earlier entries remain stored.";
|
|
88
|
+
return entry.kind;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function toTranscript(
|
|
92
|
+
entries: readonly WireEntry[],
|
|
93
|
+
live?: WireAssistantMessage,
|
|
94
|
+
tools: ReadonlyMap<string, LiveTool> = new Map(),
|
|
95
|
+
): TranscriptItem[] {
|
|
96
|
+
const results = new Map<string, { entryId: number; message: WireToolResultMessage }>();
|
|
97
|
+
const callIds = new Set<string>();
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
for (const message of entry.model ?? []) {
|
|
100
|
+
if (entry.kind === "pi.tool-result" && isToolResult(message))
|
|
101
|
+
results.set(message.toolCallId, { entryId: entry.id, message });
|
|
102
|
+
if (entry.kind === "pi.assistant" && isAssistant(message))
|
|
103
|
+
for (const block of message.content) if (block.type === "toolCall") callIds.add(block.id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (live) for (const block of live.content) if (block.type === "toolCall") callIds.add(block.id);
|
|
107
|
+
|
|
108
|
+
const toolView = (id: string, name: string, args: unknown): ToolCallView => {
|
|
109
|
+
const result = results.get(id);
|
|
110
|
+
const running = tools.get(id);
|
|
111
|
+
const nested = running?.nested;
|
|
112
|
+
if (result)
|
|
113
|
+
return {
|
|
114
|
+
callId: id,
|
|
115
|
+
name,
|
|
116
|
+
args,
|
|
117
|
+
status: result.message.isError ? "error" : "done",
|
|
118
|
+
result: messageText(result.message),
|
|
119
|
+
resultEntryId: result.entryId,
|
|
120
|
+
...(nested ? { nested } : {}),
|
|
121
|
+
};
|
|
122
|
+
// A nested call's result lives in its caller's document, not the transcript: render it from the live slot.
|
|
123
|
+
if (running?.result)
|
|
124
|
+
return {
|
|
125
|
+
callId: id,
|
|
126
|
+
name,
|
|
127
|
+
args,
|
|
128
|
+
status: running.result.isError ? "error" : "done",
|
|
129
|
+
result: running.result.text,
|
|
130
|
+
...(nested ? { nested } : {}),
|
|
131
|
+
...(running.summary ? { summary: summaryLine(running.summary) } : {}),
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
callId: id,
|
|
135
|
+
name,
|
|
136
|
+
args,
|
|
137
|
+
status: running?.status ?? "pending",
|
|
138
|
+
output: running?.output || undefined,
|
|
139
|
+
...(nested ? { nested } : {}),
|
|
140
|
+
...(running?.summary ? { summary: summaryLine(running.summary) } : {}),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const assistantBlocks = (message: WireAssistantMessage): AssistantBlockView[] =>
|
|
145
|
+
message.content.flatMap((block): AssistantBlockView[] => {
|
|
146
|
+
if (block.type === "text") return block.text ? [{ type: "text", text: block.text }] : [];
|
|
147
|
+
if (block.type === "thinking")
|
|
148
|
+
return block.thinking ? [{ type: "thinking", text: block.thinking }] : [];
|
|
149
|
+
return [{ type: "tool", call: toolView(block.id, block.name, block.arguments) }];
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const items: TranscriptItem[] = [];
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (entry.kind === "pi.system") continue;
|
|
155
|
+
if (entry.kind === "pi.user") {
|
|
156
|
+
items.push({
|
|
157
|
+
kind: "user",
|
|
158
|
+
id: entry.id,
|
|
159
|
+
text: (entry.model ?? []).map(messageText).join("\n"),
|
|
160
|
+
});
|
|
161
|
+
} else if (entry.kind === "pi.assistant") {
|
|
162
|
+
const messages = (entry.model ?? []).filter(isAssistant);
|
|
163
|
+
items.push({
|
|
164
|
+
kind: "assistant",
|
|
165
|
+
id: entry.id,
|
|
166
|
+
streaming: false,
|
|
167
|
+
blocks: messages.flatMap(assistantBlocks),
|
|
168
|
+
error: messages.map(assistantError).find(Boolean),
|
|
169
|
+
model: messages[0]?.model,
|
|
170
|
+
});
|
|
171
|
+
} else if (entry.kind === "pi.tool-result") {
|
|
172
|
+
for (const message of (entry.model ?? []).filter(isToolResult)) {
|
|
173
|
+
if (callIds.has(message.toolCallId)) continue;
|
|
174
|
+
items.push({
|
|
175
|
+
kind: "tool-result",
|
|
176
|
+
id: entry.id,
|
|
177
|
+
name: message.toolName,
|
|
178
|
+
text: messageText(message),
|
|
179
|
+
isError: message.isError,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
items.push({
|
|
184
|
+
kind: "event",
|
|
185
|
+
id: entry.id,
|
|
186
|
+
entryKind: entry.kind,
|
|
187
|
+
text: eventText(entry),
|
|
188
|
+
note: entry.model?.length ? (entry.model ?? []).map(messageText).join("\n") : undefined,
|
|
189
|
+
...(entry.data !== undefined ? { data: entry.data } : {}),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (live)
|
|
194
|
+
items.push({
|
|
195
|
+
kind: "assistant",
|
|
196
|
+
id: "live",
|
|
197
|
+
streaming: true,
|
|
198
|
+
blocks: assistantBlocks(live),
|
|
199
|
+
model: live.model,
|
|
200
|
+
});
|
|
201
|
+
for (const [callId, tool] of tools)
|
|
202
|
+
// A nested call is presentation of the running tool round: render it even once done (its result is not in the
|
|
203
|
+
// transcript). A model-issued call renders from its result entry instead, so its stale slot is dropped.
|
|
204
|
+
if ((tool.status !== "done" || tool.result || tool.nested) && !callIds.has(callId))
|
|
205
|
+
items.push({ kind: "tool", id: callId, call: toolView(callId, tool.name, tool.args) });
|
|
206
|
+
return items;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** "1.2s · error text" style one-liner from a nested call's summary. */
|
|
210
|
+
function summaryLine(summary: { isError: boolean; durationMs?: number; error?: string }): string {
|
|
211
|
+
const parts: string[] = [];
|
|
212
|
+
if (summary.durationMs !== undefined) parts.push(`${(summary.durationMs / 1000).toFixed(1)}s`);
|
|
213
|
+
if (summary.error) parts.push(summary.error);
|
|
214
|
+
return parts.join(" · ");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function toMarkdown(items: readonly TranscriptItem[]): string {
|
|
218
|
+
const out: string[] = [];
|
|
219
|
+
for (const item of items) {
|
|
220
|
+
if (item.kind === "user") out.push(`### You (#${item.id})\n\n${item.text}`);
|
|
221
|
+
else if (item.kind === "assistant") {
|
|
222
|
+
const parts = item.blocks.map((block) =>
|
|
223
|
+
block.type === "text"
|
|
224
|
+
? block.text
|
|
225
|
+
: block.type === "thinking"
|
|
226
|
+
? `> _thinking:_ ${block.text.replaceAll("\n", " ").slice(0, 400)}`
|
|
227
|
+
: `- \`${block.call.name}\` ${block.call.status}${block.call.result ? `\n\n\`\`\`\n${block.call.result.slice(0, 2000)}\n\`\`\`` : ""}`,
|
|
228
|
+
);
|
|
229
|
+
if (item.error) parts.push(`> ${item.error}`);
|
|
230
|
+
out.push(`### Agent (${item.id === "live" ? "working" : `#${item.id}`})\n\n${parts.join("\n\n")}`);
|
|
231
|
+
} else if (item.kind === "tool-result")
|
|
232
|
+
out.push(`- \`${item.name}\` ${item.isError ? "error" : "result"}\n\n\`\`\`\n${item.text.slice(0, 2000)}\n\`\`\``);
|
|
233
|
+
else if (item.kind === "tool") out.push(`- \`${item.call.name}\` ${item.call.status}`);
|
|
234
|
+
else out.push(`_${item.text}_`);
|
|
235
|
+
}
|
|
236
|
+
return out.join("\n\n") + "\n";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Text of the last assistant entry, for previews and simple clients. */
|
|
240
|
+
export function lastAssistantText(entries: readonly WireEntry[]): string | undefined {
|
|
241
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
242
|
+
const entry = entries[i];
|
|
243
|
+
if (entry.kind !== "pi.assistant") continue;
|
|
244
|
+
const text = (entry.model ?? []).filter(isAssistant).map(messageText).join("").trim();
|
|
245
|
+
if (text) return text;
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface UsageSummary {
|
|
251
|
+
tokens: number;
|
|
252
|
+
cost: number;
|
|
253
|
+
input: number;
|
|
254
|
+
output: number;
|
|
255
|
+
cacheRead: number;
|
|
256
|
+
cacheWrite: number;
|
|
257
|
+
inputCost: number;
|
|
258
|
+
outputCost: number;
|
|
259
|
+
cacheReadCost: number;
|
|
260
|
+
cacheWriteCost: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function sumUsage(state?: Pick<WireUsageState, "models"> | { models: Record<string, WireUsage> }): UsageSummary {
|
|
264
|
+
const total: UsageSummary = {
|
|
265
|
+
tokens: 0,
|
|
266
|
+
cost: 0,
|
|
267
|
+
input: 0,
|
|
268
|
+
output: 0,
|
|
269
|
+
cacheRead: 0,
|
|
270
|
+
cacheWrite: 0,
|
|
271
|
+
inputCost: 0,
|
|
272
|
+
outputCost: 0,
|
|
273
|
+
cacheReadCost: 0,
|
|
274
|
+
cacheWriteCost: 0,
|
|
275
|
+
};
|
|
276
|
+
for (const u of Object.values(state?.models ?? {})) {
|
|
277
|
+
total.tokens += u.totalTokens ?? u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
278
|
+
total.cost += u.cost?.total ?? 0;
|
|
279
|
+
total.input += u.input ?? 0;
|
|
280
|
+
total.output += u.output ?? 0;
|
|
281
|
+
total.cacheRead += u.cacheRead ?? 0;
|
|
282
|
+
total.cacheWrite += u.cacheWrite ?? 0;
|
|
283
|
+
total.inputCost += u.cost?.input ?? 0;
|
|
284
|
+
total.outputCost += u.cost?.output ?? 0;
|
|
285
|
+
total.cacheReadCost += u.cost?.cacheRead ?? 0;
|
|
286
|
+
total.cacheWriteCost += u.cost?.cacheWrite ?? 0;
|
|
287
|
+
}
|
|
288
|
+
return total;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export type { WireToolSlot };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts"]
|
|
13
|
+
}
|
package/apps/dpi-sdk/w
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dpi-sdk",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Durable Agent Network contract, client, models and device registry. Browser-safe core; src/node needs yaml; src/protocol needs @sinclair/typebox (browser-safe, no Node builtins).",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check": "tsc --noEmit -p .",
|
|
8
|
+
"test": "tsx --test tests/*.test.ts",
|
|
9
|
+
"net": "tsx scripts/net.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@sinclair/typebox": "0.34.52",
|
|
13
|
+
"yaml": "^2.8.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^24.0.0",
|
|
17
|
+
"tsx": "^4.20.0",
|
|
18
|
+
"typescript": "5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# dpi-tui
|
|
2
|
+
|
|
3
|
+
A pi-like terminal client for one Durable Agent Network device. It talks straight to a device's `dpi-backend` over the [v1 API](../dpi-backend/API.md) with the device's bearer token, and renders the same transcript items as the web client (`AgentModel` + `toTranscript` from [`dpi-sdk`](../dpi-sdk)). Closing it never stops the agent.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
just tui attach mac-mini # pick a session on mac-mini (or create one in any directory)
|
|
7
|
+
just tui attach mac-mini:triage # session by name or id; its root agent
|
|
8
|
+
just tui attach mac-mini:triage:alt-path # a specific agent (id, "main" or name)
|
|
9
|
+
just tui new vm-1 review # in the device's first start dir; --cwd takes a path on the device
|
|
10
|
+
cd apps/dpi-tui && npm start -- --url https://my-vm.exe.xyz --token $DPI_TOKEN --save my-vm
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Device ids come from `~/.dpi/network.sqlite`, seeded from the repo's `dpi-network.yaml` (created from `dpi-network.example.yaml`). Tokens come from `just net register <id>`. `--url/--token` connects to any backend, and `--save <id>` stores it.
|
|
14
|
+
|
|
15
|
+
`-s/--session` and `-a/--agent` pick a session and agent. `--new <name>` creates a session, with `--cwd <dir>` on the device and `--model <id>`. `DPI_URL` and `DPI_TOKEN` stand in for `--url` and `--token`, and `DPI_DEVICE` names the default device. `--help` lists every option.
|
|
16
|
+
|
|
17
|
+
## Keys
|
|
18
|
+
|
|
19
|
+
| Key | Action |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Enter | send; while the agent works it steers (joins after the current tool round) |
|
|
22
|
+
| Alt+Enter | queue as a follow-up |
|
|
23
|
+
| Esc | interrupt foreground work (background jobs continue) |
|
|
24
|
+
| Shift+Tab | cycle thinking level |
|
|
25
|
+
| Ctrl+L | model picker |
|
|
26
|
+
| Ctrl+O | expand tool output and thinking |
|
|
27
|
+
| Ctrl+C | close an open picker or panel, else clear the input; on an empty input, exit and print the reattach command (session and agent by name when unique, else ids) |
|
|
28
|
+
| Ctrl+D | exit (on an empty input) |
|
|
29
|
+
|
|
30
|
+
Commands: `/sessions`, `/agents`, `/model`, `/devices`, `/new [name]`, `/fork [entryId] [name]`, `/thinking <level>`, `/compact [instructions]`, `/reset [note]`, `/name <name>`, `/tasks`, `/help`, `/quit`. Any other text starting with `/` goes to the agent.
|
|
31
|
+
|
|
32
|
+
A send that fails on the network retries with the same `requestId`, so the device admits it once. A failed send keeps the editor text.
|
|
33
|
+
|
|
34
|
+
## Layout
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
src/main.ts args, device resolution, --new
|
|
38
|
+
src/app.ts TuiAltScreen layout, agent + session streams (SDK follow), keys, submit
|
|
39
|
+
src/render.ts transcript items → components, cached by item id
|
|
40
|
+
src/pickers.ts sessions (+ new session with a remote directory walk), agents, models, devices
|
|
41
|
+
src/commands.ts slash commands
|
|
42
|
+
src/theme.ts colors and pi-tui themes
|
|
43
|
+
bin/dpi-tui.mjs runs src/main.ts through tsx, for npm link or npx dpi-tui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Tests
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run check && npm test # types; every item kind fits widths 40/80/200 (CJK, emoji, long args)
|
|
50
|
+
tests/smoke.sh faux # tmux end to end against a faux-provider backend: streaming, steer, Esc, fork, reattach by name
|
|
51
|
+
tests/smoke.sh <device> # real model on a device: streaming, exit mid-tool, reattach
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
From the repo root, `just tui check` and `just test smoke [faux|<id>]` run the same. The smoke script needs tmux, curl and openssl. The device run archives its session through the hub at `http://localhost:4781`, so start the hub first or archive the session yourself.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dpi-tui",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": { "dpi-tui": "./bin/dpi-tui.mjs" },
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "tsx src/main.ts",
|
|
8
|
+
"check": "tsc --noEmit -p .",
|
|
9
|
+
"test": "tsx --test tests/*.test.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@earendil-works/pi-tui": "1.3.0",
|
|
13
|
+
"tsx": "^4.20.0",
|
|
14
|
+
"yaml": "^2.8.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^24.0.0",
|
|
18
|
+
"typescript": "5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|