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,425 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
Activity,
|
|
5
|
+
Braces,
|
|
6
|
+
ChevronDown,
|
|
7
|
+
FolderOpen,
|
|
8
|
+
Pencil,
|
|
9
|
+
Server,
|
|
10
|
+
ShieldCheck,
|
|
11
|
+
Wrench,
|
|
12
|
+
PanelLeftClose,
|
|
13
|
+
PanelLeftOpen,
|
|
14
|
+
Puzzle,
|
|
15
|
+
} from "lucide-vue-next";
|
|
16
|
+
import { THINKING_LEVELS, type ThinkingLevel } from "@sdk/api";
|
|
17
|
+
import type { StateChange } from "@sdk/app-state";
|
|
18
|
+
import StateEditor from "./StateEditor.vue";
|
|
19
|
+
import TunnelStatusBar from "./TunnelStatusBar.vue";
|
|
20
|
+
import TokenManager from "./TokenManager.vue";
|
|
21
|
+
import TasksPanel from "./TasksPanel.vue";
|
|
22
|
+
import ExtensionsPanel from "./ExtensionsPanel.vue";
|
|
23
|
+
import ContextBar from "./ContextBar.vue";
|
|
24
|
+
import { message } from "../client/hub";
|
|
25
|
+
import { toolSignature } from "../client/signature";
|
|
26
|
+
import {
|
|
27
|
+
agentHandle,
|
|
28
|
+
agentState,
|
|
29
|
+
attempt,
|
|
30
|
+
busy,
|
|
31
|
+
currentModel,
|
|
32
|
+
detail,
|
|
33
|
+
device,
|
|
34
|
+
deviceFeed,
|
|
35
|
+
hideInspector,
|
|
36
|
+
icon,
|
|
37
|
+
inspectorExpanded,
|
|
38
|
+
json,
|
|
39
|
+
notify,
|
|
40
|
+
number,
|
|
41
|
+
refreshDetail,
|
|
42
|
+
session,
|
|
43
|
+
sessionHandle,
|
|
44
|
+
sessionSummary,
|
|
45
|
+
showInspector,
|
|
46
|
+
tasks,
|
|
47
|
+
ui,
|
|
48
|
+
usage,
|
|
49
|
+
panelKeys,
|
|
50
|
+
} from "../client/store";
|
|
51
|
+
|
|
52
|
+
// Each tool's parameters as a highlighted TypeScript declaration, rebuilt only when the tools change.
|
|
53
|
+
const signatures = computed(
|
|
54
|
+
() => new Map(detail.value?.tools.map((t) => [t.name, toolSignature(t.name, t.parameters)])),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const tabs = [
|
|
58
|
+
{ id: "session", label: "Session", icon: FolderOpen },
|
|
59
|
+
{ id: "state", label: "State", icon: Braces },
|
|
60
|
+
{ id: "tasks", label: "Tasks", icon: Activity },
|
|
61
|
+
{ id: "tools", label: "Tools", icon: Wrench },
|
|
62
|
+
{ id: "extensions", label: "Extend", icon: Puzzle },
|
|
63
|
+
];
|
|
64
|
+
const usageRows = computed(() => [
|
|
65
|
+
{ label: "Input", tokens: usage.value.input, cost: usage.value.inputCost },
|
|
66
|
+
{ label: "Output", tokens: usage.value.output, cost: usage.value.outputCost },
|
|
67
|
+
{ label: "Cache read", tokens: usage.value.cacheRead, cost: usage.value.cacheReadCost },
|
|
68
|
+
{ label: "Cache write", tokens: usage.value.cacheWrite, cost: usage.value.cacheWriteCost },
|
|
69
|
+
]);
|
|
70
|
+
const background = computed(() => tasks.value.filter((t) => t.background).length);
|
|
71
|
+
|
|
72
|
+
function configure(
|
|
73
|
+
patch: Parameters<NonNullable<typeof agentHandle.value>["configure"]>[0],
|
|
74
|
+
done?: string,
|
|
75
|
+
) {
|
|
76
|
+
void attempt(async () => {
|
|
77
|
+
await agentHandle.value!.configure(patch);
|
|
78
|
+
await refreshDetail();
|
|
79
|
+
if (done) notify(done);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renameSession() {
|
|
84
|
+
const handle = sessionHandle.value;
|
|
85
|
+
if (!handle) return;
|
|
86
|
+
ui.dialog = {
|
|
87
|
+
kind: "prompt",
|
|
88
|
+
title: "Rename session",
|
|
89
|
+
label: "Session name",
|
|
90
|
+
value: sessionSummary.value?.name ?? "",
|
|
91
|
+
submit: "Save name",
|
|
92
|
+
run: (name) => handle.rename(name),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ------------------------------------------------------------ App State (device-wide document)
|
|
97
|
+
|
|
98
|
+
const shared = deviceFeed.sharedState;
|
|
99
|
+
const stateDraft = ref("");
|
|
100
|
+
const stateBaseline = ref("");
|
|
101
|
+
const stateDraftRevision = ref(0);
|
|
102
|
+
const stateSaving = ref(false);
|
|
103
|
+
const stateError = ref("");
|
|
104
|
+
const stateDirty = computed(() => stateDraft.value !== stateBaseline.value);
|
|
105
|
+
const stateConflict = computed(
|
|
106
|
+
() => stateDirty.value && shared.value?.revision !== stateDraftRevision.value,
|
|
107
|
+
);
|
|
108
|
+
function resetStateDraft() {
|
|
109
|
+
stateDraft.value = json(shared.value?.value ?? {});
|
|
110
|
+
stateBaseline.value = stateDraft.value;
|
|
111
|
+
stateDraftRevision.value = shared.value?.revision ?? 0;
|
|
112
|
+
stateError.value = "";
|
|
113
|
+
}
|
|
114
|
+
watch(shared, () => !stateDirty.value && resetStateDraft(), { immediate: true });
|
|
115
|
+
|
|
116
|
+
async function editState(change: StateChange) {
|
|
117
|
+
if (stateSaving.value || !agentHandle.value) return;
|
|
118
|
+
stateSaving.value = true;
|
|
119
|
+
stateError.value = "";
|
|
120
|
+
try {
|
|
121
|
+
await agentHandle.value.demo("state", change);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
stateError.value = message(e);
|
|
124
|
+
} finally {
|
|
125
|
+
stateSaving.value = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function saveStateJson() {
|
|
129
|
+
if (stateSaving.value || !agentHandle.value) return;
|
|
130
|
+
stateError.value = "";
|
|
131
|
+
stateSaving.value = true;
|
|
132
|
+
try {
|
|
133
|
+
const value = JSON.parse(stateDraft.value);
|
|
134
|
+
if (!value || Array.isArray(value) || typeof value !== "object")
|
|
135
|
+
throw Error("App State must be a JSON object.");
|
|
136
|
+
const saved = await agentHandle.value.demo<{ value: unknown; revision: number }>("state", {
|
|
137
|
+
operation: "update",
|
|
138
|
+
path: "",
|
|
139
|
+
value,
|
|
140
|
+
expectedRevision: stateDraftRevision.value,
|
|
141
|
+
});
|
|
142
|
+
stateDraft.value = json(saved.value);
|
|
143
|
+
stateBaseline.value = stateDraft.value;
|
|
144
|
+
stateDraftRevision.value = saved.revision;
|
|
145
|
+
notify("Shared App State saved.");
|
|
146
|
+
} catch (e) {
|
|
147
|
+
stateError.value = message(e);
|
|
148
|
+
} finally {
|
|
149
|
+
stateSaving.value = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const readOnly = computed(() => detail.value?.config.readOnly === true);
|
|
154
|
+
const offeredTools = computed(() => detail.value?.tools.filter((t) => t.offered).length ?? 0);
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<template>
|
|
158
|
+
<aside class="inspector" :class="{ collapsed: !inspectorExpanded, overlay: ui.inspectorOverlay }">
|
|
159
|
+
<div v-if="!inspectorExpanded" class="panel-rail" aria-label="Inspector (collapsed)">
|
|
160
|
+
<button
|
|
161
|
+
class="icon-button panel-toggle"
|
|
162
|
+
aria-label="Expand inspector"
|
|
163
|
+
:title="`Expand inspector (${panelKeys.inspector})`"
|
|
164
|
+
@click="showInspector()"
|
|
165
|
+
>
|
|
166
|
+
<PanelLeftOpen :size="18" />
|
|
167
|
+
</button>
|
|
168
|
+
<button
|
|
169
|
+
v-for="t in tabs"
|
|
170
|
+
:key="t.id"
|
|
171
|
+
class="rail-tab"
|
|
172
|
+
:class="{ active: ui.tab === t.id }"
|
|
173
|
+
:aria-label="`Open ${t.label}`"
|
|
174
|
+
:title="t.label"
|
|
175
|
+
@click="showInspector(t.id)"
|
|
176
|
+
>
|
|
177
|
+
<component :is="t.icon" :size="20" /><span
|
|
178
|
+
v-if="t.id === 'tasks' && tasks.length"
|
|
179
|
+
class="count"
|
|
180
|
+
>{{ tasks.length }}</span
|
|
181
|
+
>
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
<template v-else>
|
|
185
|
+
<div class="inspector-tabs" role="tablist" aria-label="Session details">
|
|
186
|
+
<button
|
|
187
|
+
v-for="t in tabs"
|
|
188
|
+
:id="`inspector-tab-${t.id}`"
|
|
189
|
+
:key="t.id"
|
|
190
|
+
role="tab"
|
|
191
|
+
aria-controls="inspector-panel"
|
|
192
|
+
:aria-selected="ui.tab === t.id"
|
|
193
|
+
:class="{ active: ui.tab === t.id }"
|
|
194
|
+
@click="ui.tab = t.id"
|
|
195
|
+
>
|
|
196
|
+
<component :is="t.icon" :size="18" />{{ t.label
|
|
197
|
+
}}<span v-if="t.id === 'tasks' && tasks.length" class="count">{{ tasks.length }}</span>
|
|
198
|
+
</button>
|
|
199
|
+
<button
|
|
200
|
+
class="icon-button panel-toggle"
|
|
201
|
+
aria-label="Collapse inspector"
|
|
202
|
+
:title="`Collapse inspector (${panelKeys.inspector})`"
|
|
203
|
+
@click="hideInspector()"
|
|
204
|
+
>
|
|
205
|
+
<PanelLeftClose :size="18" />
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
<div
|
|
209
|
+
id="inspector-panel"
|
|
210
|
+
class="inspector-body"
|
|
211
|
+
:class="{ 'session-panel': ui.tab === 'session' }"
|
|
212
|
+
role="tabpanel"
|
|
213
|
+
:aria-labelledby="`inspector-tab-${ui.tab}`"
|
|
214
|
+
>
|
|
215
|
+
<template v-if="ui.tab === 'session'">
|
|
216
|
+
<section class="session-heading">
|
|
217
|
+
<h1>
|
|
218
|
+
<span>{{ sessionSummary?.name ?? "Loading session" }}</span
|
|
219
|
+
><button
|
|
220
|
+
class="icon-button"
|
|
221
|
+
title="Rename session"
|
|
222
|
+
aria-label="Rename session"
|
|
223
|
+
@click="renameSession"
|
|
224
|
+
>
|
|
225
|
+
<Pencil :size="18" />
|
|
226
|
+
</button>
|
|
227
|
+
</h1>
|
|
228
|
+
<button
|
|
229
|
+
class="path cwd-button"
|
|
230
|
+
:title="sessionSummary?.cwd"
|
|
231
|
+
aria-label="Show full working directory"
|
|
232
|
+
@click="ui.dialog = { kind: 'path', path: sessionSummary?.cwd ?? '' }"
|
|
233
|
+
>
|
|
234
|
+
<FolderOpen :size="18" /><span class="cwd-trail" dir="rtl"
|
|
235
|
+
><bdi dir="ltr">{{ sessionSummary?.cwd }}</bdi></span
|
|
236
|
+
>
|
|
237
|
+
</button>
|
|
238
|
+
<div class="session-status">
|
|
239
|
+
<span class="status-pill" :class="{ working: busy }"
|
|
240
|
+
><i />{{ busy ? "Working" : "Idle" }}</span
|
|
241
|
+
><span v-if="background" class="background-pill">{{ background }} background</span
|
|
242
|
+
><span class="saved"><Server :size="18" />{{ device?.id }}</span>
|
|
243
|
+
<TunnelStatusBar v-if="device" :device="device" />
|
|
244
|
+
</div>
|
|
245
|
+
</section>
|
|
246
|
+
<TokenManager v-if="device" :device="device" />
|
|
247
|
+
<section class="model-section">
|
|
248
|
+
<div class="section-label">
|
|
249
|
+
<span>AGENT</span>
|
|
250
|
+
</div>
|
|
251
|
+
<button class="model-button" @click="ui.dialog = { kind: 'models' }">
|
|
252
|
+
<img :src="icon(agentState.agent.model?.modelId)" alt="" />
|
|
253
|
+
<div>
|
|
254
|
+
<strong>{{
|
|
255
|
+
currentModel?.name ?? agentState.agent.model?.modelId ?? "Loading…"
|
|
256
|
+
}}</strong
|
|
257
|
+
><small
|
|
258
|
+
>{{ currentModel?.tier ?? "" }} /
|
|
259
|
+
{{ agentState.agent.model?.provider ?? "" }}</small
|
|
260
|
+
>
|
|
261
|
+
</div>
|
|
262
|
+
<ChevronDown :size="18" />
|
|
263
|
+
</button>
|
|
264
|
+
<div class="thinking-row">
|
|
265
|
+
<span>Thinking</span
|
|
266
|
+
><select
|
|
267
|
+
aria-label="Thinking level"
|
|
268
|
+
:value="agentState.agent.thinkingLevel ?? 'low'"
|
|
269
|
+
@change="
|
|
270
|
+
configure(
|
|
271
|
+
{ thinkingLevel: ($event.target as HTMLSelectElement).value as ThinkingLevel },
|
|
272
|
+
`Thinking ${($event.target as HTMLSelectElement).value}. Used on the next turn.`,
|
|
273
|
+
)
|
|
274
|
+
"
|
|
275
|
+
>
|
|
276
|
+
<option v-for="level in THINKING_LEVELS" :key="level">{{ level }}</option>
|
|
277
|
+
</select>
|
|
278
|
+
</div>
|
|
279
|
+
<ContextBar />
|
|
280
|
+
<section class="stats-section" aria-labelledby="stats-title">
|
|
281
|
+
<div class="section-label"><span id="stats-title">STATS</span></div>
|
|
282
|
+
<div class="metrics">
|
|
283
|
+
<div :title="`${usage.tokens.toLocaleString('en-US')} total tokens`">
|
|
284
|
+
<span>{{ number(usage.tokens) }}</span
|
|
285
|
+
><small>total tokens</small>
|
|
286
|
+
</div>
|
|
287
|
+
<div>
|
|
288
|
+
<span>${{ usage.cost.toFixed(4) }}</span
|
|
289
|
+
><small>agent cost</small>
|
|
290
|
+
</div>
|
|
291
|
+
<div>
|
|
292
|
+
<span>{{ detail?.tools.length ?? 0 }}</span
|
|
293
|
+
><small>tools</small>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
<table class="usage-breakdown" aria-label="Agent token and cost breakdown">
|
|
297
|
+
<thead>
|
|
298
|
+
<tr>
|
|
299
|
+
<th scope="col">Type</th>
|
|
300
|
+
<th scope="col">Tokens</th>
|
|
301
|
+
<th scope="col">Cost (USD)</th>
|
|
302
|
+
</tr>
|
|
303
|
+
</thead>
|
|
304
|
+
<tbody>
|
|
305
|
+
<tr v-for="row in usageRows" :key="row.label">
|
|
306
|
+
<th scope="row">{{ row.label }}</th>
|
|
307
|
+
<td>{{ row.tokens.toLocaleString("en-US") }}</td>
|
|
308
|
+
<td>${{ row.cost.toFixed(4) }}</td>
|
|
309
|
+
</tr>
|
|
310
|
+
</tbody>
|
|
311
|
+
</table>
|
|
312
|
+
</section>
|
|
313
|
+
</section>
|
|
314
|
+
</template>
|
|
315
|
+
<template v-if="ui.tab === 'state'">
|
|
316
|
+
<div class="panel-heading">
|
|
317
|
+
<h2>App State</h2>
|
|
318
|
+
<Braces :size="18" />
|
|
319
|
+
</div>
|
|
320
|
+
<p class="hint app-state-description">
|
|
321
|
+
One shared JSON object for every agent, session and fork on {{ device?.id }}. You and
|
|
322
|
+
your agents can read, create, update or delete values and fields. Changes appear live
|
|
323
|
+
and persist across closed browsers, interrupted agents and backend restarts.
|
|
324
|
+
</p>
|
|
325
|
+
<textarea
|
|
326
|
+
v-model="stateDraft"
|
|
327
|
+
class="app-state-json"
|
|
328
|
+
aria-label="App State JSON"
|
|
329
|
+
spellcheck="false"
|
|
330
|
+
:disabled="stateSaving || !shared"
|
|
331
|
+
rows="12"
|
|
332
|
+
/>
|
|
333
|
+
<div class="state-json-actions">
|
|
334
|
+
<span v-if="stateConflict" class="hint"
|
|
335
|
+
>State changed elsewhere. Load the latest JSON before saving.</span
|
|
336
|
+
>
|
|
337
|
+
<button
|
|
338
|
+
v-if="stateDirty"
|
|
339
|
+
class="secondary"
|
|
340
|
+
:disabled="stateSaving"
|
|
341
|
+
@click="resetStateDraft"
|
|
342
|
+
>
|
|
343
|
+
{{ stateConflict ? "Load latest" : "Reset" }}
|
|
344
|
+
</button>
|
|
345
|
+
<button
|
|
346
|
+
class="primary small"
|
|
347
|
+
:disabled="!stateDirty || stateSaving || stateConflict"
|
|
348
|
+
@click="saveStateJson"
|
|
349
|
+
>
|
|
350
|
+
{{ stateSaving ? "Saving…" : "Save JSON" }}
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
353
|
+
<p v-if="stateError" class="message-error" role="alert">{{ stateError }}</p>
|
|
354
|
+
<StateEditor
|
|
355
|
+
:value="shared?.value ?? {}"
|
|
356
|
+
:disabled="stateSaving || stateDirty || !shared"
|
|
357
|
+
@change="editState"
|
|
358
|
+
/>
|
|
359
|
+
</template>
|
|
360
|
+
<TasksPanel v-if="ui.tab === 'tasks'" />
|
|
361
|
+
<ExtensionsPanel v-if="ui.tab === 'extensions'" />
|
|
362
|
+
<template v-if="ui.tab === 'tools'"
|
|
363
|
+
><div class="panel-heading">
|
|
364
|
+
<h2>Tools</h2>
|
|
365
|
+
<span class="tag">{{ offeredTools }} / {{ detail?.tools.length ?? 0 }}</span>
|
|
366
|
+
</div>
|
|
367
|
+
<p class="hint">
|
|
368
|
+
Every tool this agent's extensions provide. Open one to see its definition.
|
|
369
|
+
</p>
|
|
370
|
+
<div class="hook-card">
|
|
371
|
+
<div>
|
|
372
|
+
<ShieldCheck :size="18" /><strong>Read only</strong
|
|
373
|
+
><button
|
|
374
|
+
class="switch"
|
|
375
|
+
role="switch"
|
|
376
|
+
:aria-checked="readOnly"
|
|
377
|
+
aria-label="Read only"
|
|
378
|
+
:class="{ on: readOnly }"
|
|
379
|
+
@click="
|
|
380
|
+
configure(
|
|
381
|
+
{ readOnly: !readOnly },
|
|
382
|
+
readOnly
|
|
383
|
+
? 'Read only off. Every tool is back.'
|
|
384
|
+
: 'Read only on. Tools that write are disabled.',
|
|
385
|
+
)
|
|
386
|
+
"
|
|
387
|
+
>
|
|
388
|
+
<span />
|
|
389
|
+
</button>
|
|
390
|
+
</div>
|
|
391
|
+
<p>
|
|
392
|
+
Offers this agent only tools that read. Tools that create, update or delete leave its
|
|
393
|
+
next request, and the choice is stored with the agent.
|
|
394
|
+
</p>
|
|
395
|
+
</div>
|
|
396
|
+
<details
|
|
397
|
+
v-for="tool in detail?.tools"
|
|
398
|
+
:key="tool.name"
|
|
399
|
+
class="tool-row"
|
|
400
|
+
:class="{ disabled: !tool.offered }"
|
|
401
|
+
>
|
|
402
|
+
<summary>
|
|
403
|
+
<Wrench :size="18" /><strong>{{ tool.name }}</strong
|
|
404
|
+
><span v-if="!tool.offered" class="tag tool-disabled">disabled</span
|
|
405
|
+
><span class="tag">{{
|
|
406
|
+
tool.replay === "safe" ? "replay safe" : "interrupt on crash"
|
|
407
|
+
}}</span>
|
|
408
|
+
</summary>
|
|
409
|
+
<p>{{ tool.description }}</p>
|
|
410
|
+
<pre
|
|
411
|
+
v-if="tool.parameters"
|
|
412
|
+
class="tool-signature"
|
|
413
|
+
:aria-label="`${tool.name} TypeScript definition`"
|
|
414
|
+
><code><span v-for="(t, i) in signatures.get(tool.name)" :key="i" :class="`tk-${t.kind}`">{{ t.text }}</span></code></pre>
|
|
415
|
+
<small>{{
|
|
416
|
+
tool.replay === "safe"
|
|
417
|
+
? "An interrupted execution may run again after recovery."
|
|
418
|
+
: "A crash records an interrupted result instead of rerunning this effect."
|
|
419
|
+
}}</small>
|
|
420
|
+
</details></template
|
|
421
|
+
>
|
|
422
|
+
</div>
|
|
423
|
+
</template>
|
|
424
|
+
</aside>
|
|
425
|
+
</template>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// One durable job run, live: its named phases as a stepper, every step each phase took (commands, a subagent's
|
|
3
|
+
// tool calls, App State writes, checkpoints), the approval when it waits, and how it is owned.
|
|
4
|
+
import { computed, h, type FunctionalComponent } from "vue";
|
|
5
|
+
import {
|
|
6
|
+
Bot,
|
|
7
|
+
Braces,
|
|
8
|
+
Check,
|
|
9
|
+
Flag,
|
|
10
|
+
Hand,
|
|
11
|
+
ListChecks,
|
|
12
|
+
LoaderCircle,
|
|
13
|
+
Octagon,
|
|
14
|
+
Repeat,
|
|
15
|
+
ShieldCheck,
|
|
16
|
+
Terminal,
|
|
17
|
+
Workflow,
|
|
18
|
+
Wrench,
|
|
19
|
+
X,
|
|
20
|
+
} from "lucide-vue-next";
|
|
21
|
+
import { jobRunDone, type JobPhaseRun, type JobRun, type JobStep } from "@sdk/jobs";
|
|
22
|
+
import { decide, jobDef, now, openAgent, pending, seconds, stopRun } from "../client/jobs";
|
|
23
|
+
|
|
24
|
+
const props = defineProps<{ run: JobRun }>();
|
|
25
|
+
const def = computed(() => jobDef(props.run.job));
|
|
26
|
+
const done = computed(() => jobRunDone(props.run));
|
|
27
|
+
const elapsed = computed(() => seconds((props.run.endedAt ?? now.value) - props.run.startedAt));
|
|
28
|
+
const statusText: Record<JobRun["status"], string> = {
|
|
29
|
+
running: "Running",
|
|
30
|
+
waiting: "Waiting for you",
|
|
31
|
+
completed: "Completed",
|
|
32
|
+
failed: "Failed",
|
|
33
|
+
aborted: "Stopped",
|
|
34
|
+
declined: "Declined",
|
|
35
|
+
};
|
|
36
|
+
const stepIcon = {
|
|
37
|
+
command: Terminal,
|
|
38
|
+
tool: Wrench,
|
|
39
|
+
agent: Bot,
|
|
40
|
+
state: Braces,
|
|
41
|
+
approval: ShieldCheck,
|
|
42
|
+
checkpoint: Flag,
|
|
43
|
+
note: ListChecks,
|
|
44
|
+
};
|
|
45
|
+
const stepTitle = (s: JobStep) =>
|
|
46
|
+
s.kind === "tool" ? `${s.label}: a subagent tool call, its own durable task` : s.label;
|
|
47
|
+
|
|
48
|
+
/** One step: what kind, what ran, how it ended, how long it took. */
|
|
49
|
+
const JobStepLine: FunctionalComponent<{ step: JobStep }> = (p) => {
|
|
50
|
+
const s = p.step;
|
|
51
|
+
const label = ["command", "tool", "state"].includes(s.kind)
|
|
52
|
+
? h("code", { title: stepTitle(s) }, s.label)
|
|
53
|
+
: h("span", { class: "job-step-text" }, s.label);
|
|
54
|
+
const state =
|
|
55
|
+
s.status === "running"
|
|
56
|
+
? h(LoaderCircle, { size: 15, class: "spin" })
|
|
57
|
+
: s.status === "done"
|
|
58
|
+
? h(Check, { size: 15 })
|
|
59
|
+
: s.status === "waiting"
|
|
60
|
+
? h(Hand, { size: 15 })
|
|
61
|
+
: h(X, { size: 15 });
|
|
62
|
+
return [
|
|
63
|
+
h(stepIcon[s.kind], { size: 16, class: "job-step-kind" }),
|
|
64
|
+
label,
|
|
65
|
+
s.kind === "tool" && s.detail
|
|
66
|
+
? h("span", { class: "job-step-args", title: s.detail }, s.detail)
|
|
67
|
+
: null,
|
|
68
|
+
h("span", { class: "job-step-state" }, [
|
|
69
|
+
state,
|
|
70
|
+
s.ms !== undefined && s.kind !== "note" ? h("span", seconds(s.ms)) : null,
|
|
71
|
+
]),
|
|
72
|
+
];
|
|
73
|
+
};
|
|
74
|
+
JobStepLine.props = ["step"];
|
|
75
|
+
const phaseSpec = (id: string) => def.value?.phases.find((p) => p.id === id);
|
|
76
|
+
const phaseTime = (p: JobPhaseRun) =>
|
|
77
|
+
p.startedAt
|
|
78
|
+
? seconds(
|
|
79
|
+
(p.endedAt ??
|
|
80
|
+
(p.status === "running" || p.status === "waiting" ? now.value : p.startedAt)) -
|
|
81
|
+
p.startedAt,
|
|
82
|
+
)
|
|
83
|
+
: "";
|
|
84
|
+
const open = (p: JobPhaseRun) => p.status !== "pending" && p.status !== "skipped";
|
|
85
|
+
const inputs = computed(() => Object.entries(props.run.input));
|
|
86
|
+
const approvalPhase = computed(() => props.run.phases.find((p) => p.status === "waiting"));
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<article
|
|
91
|
+
class="job-run"
|
|
92
|
+
:class="`is-${run.status}`"
|
|
93
|
+
:data-job-run="run.id"
|
|
94
|
+
:aria-label="`${run.title} run ${run.run}: ${statusText[run.status]}`"
|
|
95
|
+
>
|
|
96
|
+
<header class="job-run-head">
|
|
97
|
+
<span class="job-run-icon"><Workflow :size="22" /></span>
|
|
98
|
+
<div class="job-run-titles">
|
|
99
|
+
<div class="job-run-eyebrow">
|
|
100
|
+
<span>DURABLE JOB</span>
|
|
101
|
+
<span class="job-run-repeat"
|
|
102
|
+
><Repeat :size="15" />Repeatable workflow, run #{{ run.run }}</span
|
|
103
|
+
>
|
|
104
|
+
</div>
|
|
105
|
+
<h3>{{ run.title }}</h3>
|
|
106
|
+
</div>
|
|
107
|
+
<span class="job-run-status" :class="run.status"><i />{{ statusText[run.status] }}</span>
|
|
108
|
+
</header>
|
|
109
|
+
<p v-if="def" class="job-run-about">{{ def.summary }}</p>
|
|
110
|
+
<div class="job-run-meta">
|
|
111
|
+
<span>task #{{ run.id }}</span>
|
|
112
|
+
<span>{{ run.startedBy === "agent" ? "started by the agent" : "started by you" }}</span>
|
|
113
|
+
<span>{{ elapsed }}</span>
|
|
114
|
+
<span v-for="[k, v] in inputs" :key="k" class="job-run-input">{{ k }}={{ v }}</span>
|
|
115
|
+
<span v-if="run.resumed" class="job-run-resumed"
|
|
116
|
+
>resumed {{ run.resumed }}× after restart</span
|
|
117
|
+
>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<ol class="job-stepper" aria-label="Phases">
|
|
121
|
+
<li
|
|
122
|
+
v-for="(phase, i) in run.phases"
|
|
123
|
+
:key="phase.id"
|
|
124
|
+
:class="`ph-${phase.status}`"
|
|
125
|
+
:aria-current="run.phase === phase.id && !done ? 'step' : undefined"
|
|
126
|
+
>
|
|
127
|
+
<span class="job-chip"
|
|
128
|
+
><span class="job-step-dot">
|
|
129
|
+
<Check v-if="phase.status === 'done'" :size="15" />
|
|
130
|
+
<LoaderCircle v-else-if="phase.status === 'running'" :size="15" class="spin" />
|
|
131
|
+
<Hand v-else-if="phase.status === 'waiting'" :size="15" />
|
|
132
|
+
<X v-else-if="['failed', 'aborted'].includes(phase.status)" :size="15" />
|
|
133
|
+
<template v-else>{{ i + 1 }}</template>
|
|
134
|
+
</span>
|
|
135
|
+
<span class="job-step-label">{{ phase.label }}</span>
|
|
136
|
+
<span v-if="phase.progress" class="job-step-progress"
|
|
137
|
+
>{{ phase.progress.done }}/{{ phase.progress.total }}</span
|
|
138
|
+
></span
|
|
139
|
+
>
|
|
140
|
+
</li>
|
|
141
|
+
</ol>
|
|
142
|
+
|
|
143
|
+
<div class="job-phases">
|
|
144
|
+
<details
|
|
145
|
+
v-for="(phase, i) in run.phases"
|
|
146
|
+
:key="phase.id"
|
|
147
|
+
class="job-phase"
|
|
148
|
+
:class="`ph-${phase.status}`"
|
|
149
|
+
:open="open(phase)"
|
|
150
|
+
>
|
|
151
|
+
<summary>
|
|
152
|
+
<span class="job-phase-n">{{ i + 1 }}</span>
|
|
153
|
+
<strong>{{ phase.label }}</strong>
|
|
154
|
+
<span class="job-phase-summary">{{
|
|
155
|
+
phase.summary ??
|
|
156
|
+
(phase.status === "pending"
|
|
157
|
+
? phaseSpec(phase.id)?.summary
|
|
158
|
+
: phase.status === "skipped"
|
|
159
|
+
? "Skipped"
|
|
160
|
+
: "")
|
|
161
|
+
}}</span>
|
|
162
|
+
<span class="job-phase-time">{{ phaseTime(phase) }}</span>
|
|
163
|
+
</summary>
|
|
164
|
+
<p v-if="phase.status !== 'done'" class="job-phase-about">
|
|
165
|
+
{{ phaseSpec(phase.id)?.summary }}
|
|
166
|
+
</p>
|
|
167
|
+
<ul v-if="phase.steps.length" class="job-steps">
|
|
168
|
+
<li
|
|
169
|
+
v-for="step in phase.steps"
|
|
170
|
+
:key="step.id"
|
|
171
|
+
:class="[`k-${step.kind}`, `st-${step.status}`]"
|
|
172
|
+
>
|
|
173
|
+
<details v-if="step.detail && step.kind !== 'tool'" :open="step.status === 'error'">
|
|
174
|
+
<summary><JobStepLine :step="step" /></summary>
|
|
175
|
+
<pre>{{ step.detail }}</pre>
|
|
176
|
+
</details>
|
|
177
|
+
<div v-else class="job-step-line"><JobStepLine :step="step" /></div>
|
|
178
|
+
<button
|
|
179
|
+
v-if="step.kind === 'agent' && step.conversationId"
|
|
180
|
+
class="text-button job-step-link"
|
|
181
|
+
@click="openAgent(step.conversationId)"
|
|
182
|
+
>
|
|
183
|
+
Open subagent #{{ step.conversationId }}
|
|
184
|
+
</button>
|
|
185
|
+
</li>
|
|
186
|
+
</ul>
|
|
187
|
+
<p v-else-if="phase.status === 'pending'" class="job-phase-empty">Not started yet.</p>
|
|
188
|
+
</details>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div v-if="run.approval === 'pending'" class="job-approval" role="group" aria-label="Approval">
|
|
192
|
+
<div><ShieldCheck :size="20" /><strong>Approve to continue</strong></div>
|
|
193
|
+
<p>
|
|
194
|
+
{{ approvalPhase ? phaseSpec(approvalPhase.id)?.summary : "This run waits for you" }}. It is
|
|
195
|
+
paused on a saved checkpoint: close the browser or restart the device and the decision will
|
|
196
|
+
still be waiting.
|
|
197
|
+
</p>
|
|
198
|
+
<div class="job-approval-buttons">
|
|
199
|
+
<button class="primary small" :disabled="!!pending" @click="decide(run, true)">
|
|
200
|
+
<Check :size="18" />Approve & continue
|
|
201
|
+
</button>
|
|
202
|
+
<button class="secondary" :disabled="!!pending" @click="decide(run, false)">Decline</button>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<p v-if="run.error && run.status !== 'aborted'" class="message-error">{{ run.error }}</p>
|
|
206
|
+
|
|
207
|
+
<footer class="job-run-foot">
|
|
208
|
+
<span
|
|
209
|
+
>{{
|
|
210
|
+
run.background
|
|
211
|
+
? "Runs in the background: the conversation owns it, so Interrupt leaves it running."
|
|
212
|
+
: "Owned by this tool call: Interrupt stops it."
|
|
213
|
+
}}
|
|
214
|
+
Each phase saves a checkpoint, so a restart resumes the current phase.</span
|
|
215
|
+
>
|
|
216
|
+
<button v-if="!done" class="secondary danger" :disabled="!!pending" @click="stopRun(run)">
|
|
217
|
+
<Octagon :size="16" />Stop
|
|
218
|
+
</button>
|
|
219
|
+
</footer>
|
|
220
|
+
</article>
|
|
221
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// MobileNav: three-view phone nav (chat | details | network) mapped onto the existing
|
|
3
|
+
// store state instead of a new mview field — net = the tree drawer, details = the inspector
|
|
4
|
+
// overlay, chat = neither. The store keeps the overlays mutually exclusive.
|
|
5
|
+
import { MessageSquare, PanelRight, Waypoints } from "lucide-vue-next";
|
|
6
|
+
import {
|
|
7
|
+
inspectorExpanded,
|
|
8
|
+
tasks,
|
|
9
|
+
ui,
|
|
10
|
+
hideInspector,
|
|
11
|
+
showInspector,
|
|
12
|
+
toggleTree,
|
|
13
|
+
} from "../client/store";
|
|
14
|
+
|
|
15
|
+
function showChat() {
|
|
16
|
+
ui.drawer = false;
|
|
17
|
+
ui.inspectorOverlay = false;
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<nav class="mnav" aria-label="Panels">
|
|
23
|
+
<button type="button" :class="{ 'is-on': !ui.drawer && !inspectorExpanded }" @click="showChat">
|
|
24
|
+
<MessageSquare :size="18" />Chat
|
|
25
|
+
</button>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
:class="{ 'is-on': inspectorExpanded }"
|
|
29
|
+
@click="inspectorExpanded ? hideInspector() : showInspector()"
|
|
30
|
+
>
|
|
31
|
+
<PanelRight :size="18" />Details<span v-if="tasks.length" class="badge">{{
|
|
32
|
+
tasks.length
|
|
33
|
+
}}</span>
|
|
34
|
+
</button>
|
|
35
|
+
<button type="button" :class="{ 'is-on': ui.drawer }" @click="toggleTree()">
|
|
36
|
+
<Waypoints :size="18" />Network
|
|
37
|
+
</button>
|
|
38
|
+
</nav>
|
|
39
|
+
</template>
|