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,280 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
Activity,
|
|
5
|
+
Bot,
|
|
6
|
+
Clock3,
|
|
7
|
+
Layers,
|
|
8
|
+
Octagon,
|
|
9
|
+
Play,
|
|
10
|
+
Repeat,
|
|
11
|
+
ShieldCheck,
|
|
12
|
+
Workflow,
|
|
13
|
+
Wrench,
|
|
14
|
+
} from "lucide-vue-next";
|
|
15
|
+
import type { TaskNode, TaskOutcome } from "@sdk/api";
|
|
16
|
+
import { jobRunDone } from "@sdk/jobs";
|
|
17
|
+
import { message } from "../client/hub";
|
|
18
|
+
import { agentHandle, sessionHandle, tasks } from "../client/store";
|
|
19
|
+
import {
|
|
20
|
+
decide,
|
|
21
|
+
jobDef,
|
|
22
|
+
jobDefs,
|
|
23
|
+
jobRuns,
|
|
24
|
+
openAgent,
|
|
25
|
+
pending,
|
|
26
|
+
revealRun,
|
|
27
|
+
startJob,
|
|
28
|
+
} from "../client/jobs";
|
|
29
|
+
|
|
30
|
+
const error = ref("");
|
|
31
|
+
const stopping = ref(false);
|
|
32
|
+
const recent = ref<TaskOutcome[]>([]);
|
|
33
|
+
const inspection = ref<unknown>();
|
|
34
|
+
|
|
35
|
+
const status = (value: string) =>
|
|
36
|
+
({
|
|
37
|
+
running: "Running",
|
|
38
|
+
waiting: "Waiting",
|
|
39
|
+
completed: "Completed",
|
|
40
|
+
aborted: "Stopped",
|
|
41
|
+
declined: "Declined",
|
|
42
|
+
failed: "Failed",
|
|
43
|
+
pending: "Queued",
|
|
44
|
+
completing: "Finishing",
|
|
45
|
+
terminal: "Finished",
|
|
46
|
+
})[value] ?? value;
|
|
47
|
+
|
|
48
|
+
/** The newest run of each job, for its row in the list. */
|
|
49
|
+
const latest = computed(
|
|
50
|
+
() => new Map(jobDefs.value.map((d) => [d.id, jobRuns.value.find((r) => r.job === d.id)])),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// ------------------------------------------------------------------ the live task tree
|
|
54
|
+
|
|
55
|
+
type Row = { task: TaskNode & { effectiveBackground: boolean }; depth: number };
|
|
56
|
+
const rows = computed<Row[]>(() => {
|
|
57
|
+
const all = tasks.value;
|
|
58
|
+
const ids = new Set(all.map((t) => t.id));
|
|
59
|
+
const children = new Map<number | undefined, typeof all>();
|
|
60
|
+
for (const t of all) {
|
|
61
|
+
const parent = t.owner !== undefined && ids.has(t.owner) ? t.owner : undefined;
|
|
62
|
+
children.set(parent, [...(children.get(parent) ?? []), t]);
|
|
63
|
+
}
|
|
64
|
+
const out: Row[] = [];
|
|
65
|
+
const walk = (parent: number | undefined, depth: number) => {
|
|
66
|
+
for (const task of (children.get(parent) ?? []).sort((a, b) => a.id - b.id)) {
|
|
67
|
+
out.push({ task, depth });
|
|
68
|
+
walk(task.id, depth + 1);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walk(undefined, 0);
|
|
72
|
+
return out;
|
|
73
|
+
});
|
|
74
|
+
const kindOf = (t: TaskNode) =>
|
|
75
|
+
t.kind === "pi.tool"
|
|
76
|
+
? { icon: Wrench, name: t.label ?? "Tool call", what: "tool call" }
|
|
77
|
+
: t.kind === "pi.generation"
|
|
78
|
+
? { icon: Bot, name: "Model response", what: "generation" }
|
|
79
|
+
: t.kind === "pi.compaction"
|
|
80
|
+
? { icon: Layers, name: "Compaction", what: "compaction" }
|
|
81
|
+
: t.kind.startsWith("demo.job.")
|
|
82
|
+
? {
|
|
83
|
+
icon: Workflow,
|
|
84
|
+
name: t.label ?? jobDef(t.kind.slice(9))?.title ?? t.kind,
|
|
85
|
+
what: "durable job",
|
|
86
|
+
}
|
|
87
|
+
: t.kind === "demo.job-approval"
|
|
88
|
+
? { icon: ShieldCheck, name: "Approval", what: "waits for you" }
|
|
89
|
+
: { icon: Activity, name: t.label ?? t.kind, what: t.kind };
|
|
90
|
+
const phaseOf = (t: TaskNode) => {
|
|
91
|
+
if (!t.state.phase) return "";
|
|
92
|
+
if (t.kind.startsWith("demo.job."))
|
|
93
|
+
return (
|
|
94
|
+
jobDef(t.kind.slice(9))?.phases.find((p) => p.id === t.state.phase)?.label ?? t.state.phase
|
|
95
|
+
);
|
|
96
|
+
return t.state.phase;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
async function stopAll() {
|
|
100
|
+
if (stopping.value || !agentHandle.value) return;
|
|
101
|
+
stopping.value = true;
|
|
102
|
+
error.value = "";
|
|
103
|
+
try {
|
|
104
|
+
await agentHandle.value.abort(true);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
error.value = message(e);
|
|
107
|
+
} finally {
|
|
108
|
+
stopping.value = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recent outcomes and recovery inspection are read on demand, not streamed.
|
|
113
|
+
async function loadRuntime(e: Event) {
|
|
114
|
+
const handle = sessionHandle.value;
|
|
115
|
+
if (!(e.target as HTMLDetailsElement).open || !handle) return;
|
|
116
|
+
try {
|
|
117
|
+
const [r, i] = await Promise.all([handle.tasks("terminal", 40), handle.inspect()]);
|
|
118
|
+
recent.value = r;
|
|
119
|
+
inspection.value = i;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
error.value = message(err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const outcomes = computed(() =>
|
|
125
|
+
recent.value
|
|
126
|
+
.filter((task) => task.state.status === "terminal")
|
|
127
|
+
.slice(-12)
|
|
128
|
+
.reverse(),
|
|
129
|
+
);
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<template>
|
|
133
|
+
<div class="tasks-panel">
|
|
134
|
+
<div class="panel-heading">
|
|
135
|
+
<h2>Tasks</h2>
|
|
136
|
+
<span class="tag">{{ tasks.length }} active</span>
|
|
137
|
+
</div>
|
|
138
|
+
<p v-if="error" class="message-error" role="alert">{{ error }}</p>
|
|
139
|
+
|
|
140
|
+
<section class="task-section" aria-label="Durable jobs">
|
|
141
|
+
<h3><Repeat :size="18" />Durable jobs</h3>
|
|
142
|
+
<p class="hint">
|
|
143
|
+
Repeatable workflows with named phases. Every run follows the same phases and saves a
|
|
144
|
+
checkpoint after each one. Start one here, or ask your agent: it finds these with
|
|
145
|
+
<code>list_durable_jobs</code>, starts them with <code>run_durable_job</code> and checks
|
|
146
|
+
them with <code>check_durable_job</code>. Runs play out live in the chat.
|
|
147
|
+
</p>
|
|
148
|
+
<p v-if="!jobDefs.length" class="task-empty">
|
|
149
|
+
This device has no durable jobs. Deploy the latest backend to it.
|
|
150
|
+
</p>
|
|
151
|
+
<article v-for="def in jobDefs" :key="def.id" class="job-def" :data-job="def.id">
|
|
152
|
+
<div class="job-def-head">
|
|
153
|
+
<Workflow :size="18" />
|
|
154
|
+
<strong>{{ def.title }}</strong>
|
|
155
|
+
<span v-if="def.approval" class="tag">needs approval</span>
|
|
156
|
+
<span class="tag">~{{ def.seconds }}s</span>
|
|
157
|
+
</div>
|
|
158
|
+
<p>{{ def.summary }}</p>
|
|
159
|
+
<ol class="job-chain" :aria-label="`${def.title} phases`">
|
|
160
|
+
<li v-for="(phase, i) in def.phases" :key="phase.id" :title="phase.summary">
|
|
161
|
+
<span>{{ i + 1 }}</span
|
|
162
|
+
>{{ phase.label }}<Repeat v-if="phase.kind === 'repeat'" :size="13" /><ShieldCheck
|
|
163
|
+
v-if="phase.kind === 'approval'"
|
|
164
|
+
:size="13"
|
|
165
|
+
/>
|
|
166
|
+
</li>
|
|
167
|
+
</ol>
|
|
168
|
+
<div class="job-def-meta">
|
|
169
|
+
<code v-for="input in def.inputs" :key="input.name" :title="input.description"
|
|
170
|
+
>{{ input.name }}={{ input.default }}</code
|
|
171
|
+
>
|
|
172
|
+
<code :title="`Each run saves its result to App State ${def.resultPath}`"
|
|
173
|
+
>→ {{ def.resultPath }}</code
|
|
174
|
+
>
|
|
175
|
+
</div>
|
|
176
|
+
<div v-if="latest.get(def.id)" class="job-def-last">
|
|
177
|
+
<span
|
|
178
|
+
>Run #{{ latest.get(def.id)!.run }}:
|
|
179
|
+
<strong>{{ status(latest.get(def.id)!.status) }}</strong
|
|
180
|
+
><template v-if="!jobRunDone(latest.get(def.id)!)">
|
|
181
|
+
in
|
|
182
|
+
{{
|
|
183
|
+
latest.get(def.id)!.phases.find((p) => p.id === latest.get(def.id)!.phase)?.label
|
|
184
|
+
}}</template
|
|
185
|
+
></span
|
|
186
|
+
>
|
|
187
|
+
<button class="text-button" @click="revealRun(latest.get(def.id)!.id)">
|
|
188
|
+
Show in chat
|
|
189
|
+
</button>
|
|
190
|
+
<template v-if="latest.get(def.id)!.approval === 'pending'">
|
|
191
|
+
<button
|
|
192
|
+
class="primary small"
|
|
193
|
+
:disabled="!!pending"
|
|
194
|
+
@click="decide(latest.get(def.id)!, true)"
|
|
195
|
+
>
|
|
196
|
+
Approve
|
|
197
|
+
</button>
|
|
198
|
+
<button
|
|
199
|
+
class="secondary"
|
|
200
|
+
:disabled="!!pending"
|
|
201
|
+
@click="decide(latest.get(def.id)!, false)"
|
|
202
|
+
>
|
|
203
|
+
Decline
|
|
204
|
+
</button>
|
|
205
|
+
</template>
|
|
206
|
+
</div>
|
|
207
|
+
<button
|
|
208
|
+
class="secondary job-def-run"
|
|
209
|
+
:disabled="!!pending || !agentHandle"
|
|
210
|
+
@click="startJob(def)"
|
|
211
|
+
>
|
|
212
|
+
<Play :size="16" />Run {{ def.title }}
|
|
213
|
+
</button>
|
|
214
|
+
</article>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
<section class="task-section" aria-label="Agent activity">
|
|
218
|
+
<div class="panel-heading">
|
|
219
|
+
<h3>Agent activity</h3>
|
|
220
|
+
<Activity :size="18" />
|
|
221
|
+
</div>
|
|
222
|
+
<p class="hint">
|
|
223
|
+
Every model response and every tool call is its own durable task with an id, an owner and a
|
|
224
|
+
checkpoint. Follow them live here; after a restart each one resumes or reports how it ended.
|
|
225
|
+
</p>
|
|
226
|
+
<p v-if="!rows.length" class="task-empty">Nothing running. Send a message or run a job.</p>
|
|
227
|
+
<ul v-else class="task-tree">
|
|
228
|
+
<li
|
|
229
|
+
v-for="{ task, depth } in rows"
|
|
230
|
+
:key="task.id"
|
|
231
|
+
class="task-row"
|
|
232
|
+
:style="{ '--depth': depth }"
|
|
233
|
+
:data-task-kind="task.kind"
|
|
234
|
+
>
|
|
235
|
+
<component :is="kindOf(task).icon" :size="16" />
|
|
236
|
+
<strong>{{ kindOf(task).name }}</strong>
|
|
237
|
+
<span class="tag">{{ status(task.state.status) }}</span>
|
|
238
|
+
<span v-if="phaseOf(task)" class="task-phase">{{ phaseOf(task) }}</span>
|
|
239
|
+
<small
|
|
240
|
+
>#{{ task.id }} {{ kindOf(task).what }}, agent #{{ task.conversationId
|
|
241
|
+
}}{{ task.effectiveBackground ? ", background" : "" }}</small
|
|
242
|
+
>
|
|
243
|
+
<button
|
|
244
|
+
v-for="cid in task.conversations"
|
|
245
|
+
:key="cid"
|
|
246
|
+
class="text-button"
|
|
247
|
+
@click="openAgent(cid)"
|
|
248
|
+
>
|
|
249
|
+
Open agent #{{ cid }}
|
|
250
|
+
</button>
|
|
251
|
+
</li>
|
|
252
|
+
</ul>
|
|
253
|
+
<p class="hint">
|
|
254
|
+
Interrupt or Esc stops foreground work and what it owns. Stop all also stops background
|
|
255
|
+
runs.
|
|
256
|
+
</p>
|
|
257
|
+
<button class="secondary danger" :disabled="!tasks.length || stopping" @click="stopAll">
|
|
258
|
+
<Octagon :size="18" />Stop all
|
|
259
|
+
</button>
|
|
260
|
+
</section>
|
|
261
|
+
|
|
262
|
+
<details class="raw task-section" @toggle="loadRuntime">
|
|
263
|
+
<summary><Clock3 :size="18" />Runtime details</summary>
|
|
264
|
+
<p class="hint">
|
|
265
|
+
Recent task results and recovery records, read from the device when opened.
|
|
266
|
+
</p>
|
|
267
|
+
<details v-for="task in outcomes" :key="task.id" class="raw">
|
|
268
|
+
<summary>
|
|
269
|
+
{{ task.kind }} #{{ task.id
|
|
270
|
+
}}<span>{{ status(task.state.outcome?.status ?? task.state.status) }}</span>
|
|
271
|
+
</summary>
|
|
272
|
+
<pre>{{ JSON.stringify(task.state.outcome, null, 2) }}</pre>
|
|
273
|
+
</details>
|
|
274
|
+
<details class="raw">
|
|
275
|
+
<summary>Recovery inspection</summary>
|
|
276
|
+
<pre>{{ JSON.stringify(inspection, null, 2) }}</pre>
|
|
277
|
+
</details>
|
|
278
|
+
</details>
|
|
279
|
+
</div>
|
|
280
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Toast: bottom-center pill, bound to the existing ui.toast / notify() timer in the store.
|
|
3
|
+
import { Check } from "lucide-vue-next";
|
|
4
|
+
import { ui } from "../client/store";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Transition name="toast">
|
|
9
|
+
<div v-if="ui.toast" class="toast" role="status"><Check :size="18" />{{ ui.toast }}</div>
|
|
10
|
+
</Transition>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* P2-25 — TokenManager: the public view of a device pairing. Only public
|
|
4
|
+
* metadata ever renders here: pairing status, key fingerprint, paired-at,
|
|
5
|
+
* credential generation, rotationPending. No token, no hash, no secret.
|
|
6
|
+
*
|
|
7
|
+
* Honesty contract:
|
|
8
|
+
* - rotate 202 = queued intent → badge "rotation queued (waiting device ACK)";
|
|
9
|
+
* nothing says "delivered" until the device's new generation appears.
|
|
10
|
+
* - revoke asks for confirmation; a 202 says the hub committed — it does NOT
|
|
11
|
+
* claim the device files were wiped (that is only known from the device's
|
|
12
|
+
* own matching notice / auth failure on a later connection).
|
|
13
|
+
*/
|
|
14
|
+
import { computed, ref } from "vue";
|
|
15
|
+
import { KeyRound, RefreshCw, ShieldOff } from "lucide-vue-next";
|
|
16
|
+
import type { NetworkDevice } from "@sdk/api";
|
|
17
|
+
import { pairingAdmin } from "../client/pair";
|
|
18
|
+
import { message } from "../client/hub";
|
|
19
|
+
import { ago, notify, ui } from "../client/store";
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{ device: NetworkDevice }>();
|
|
22
|
+
const busy = ref(false);
|
|
23
|
+
const error = ref("");
|
|
24
|
+
|
|
25
|
+
const pairing = computed(() => props.device.pairing);
|
|
26
|
+
|
|
27
|
+
async function rotate(): Promise<void> {
|
|
28
|
+
if (busy.value) return;
|
|
29
|
+
busy.value = true;
|
|
30
|
+
error.value = "";
|
|
31
|
+
try {
|
|
32
|
+
await pairingAdmin.rotate(props.device.id);
|
|
33
|
+
notify("Rotation queued — it completes when the device acknowledges the new credential.");
|
|
34
|
+
} catch (e) {
|
|
35
|
+
error.value = message(e);
|
|
36
|
+
} finally {
|
|
37
|
+
busy.value = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function revoke(): Promise<void> {
|
|
42
|
+
if (busy.value) return;
|
|
43
|
+
ui.dialog = {
|
|
44
|
+
kind: "prompt",
|
|
45
|
+
title: `Revoke ${props.device.id}?`,
|
|
46
|
+
hint: "Every tunnel credential for this device dies at commit and the live tunnel is cut within a second. The device cleans its own files the next time it talks to the hub.",
|
|
47
|
+
submit: "Revoke",
|
|
48
|
+
danger: true,
|
|
49
|
+
run: async () => {
|
|
50
|
+
await pairingAdmin.revoke(props.device.id, "user");
|
|
51
|
+
notify(
|
|
52
|
+
"Revoke committed on the hub. The tunnel is cut; the device cleans up on its next contact.",
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<section v-if="pairing" class="token-manager" aria-label="Credential manager">
|
|
61
|
+
<header>
|
|
62
|
+
<KeyRound :size="15" />
|
|
63
|
+
<h3>Credential</h3>
|
|
64
|
+
<span class="pairing-status" :class="pairing.status">{{ pairing.status }}</span>
|
|
65
|
+
</header>
|
|
66
|
+
<dl>
|
|
67
|
+
<dt>Key fingerprint</dt>
|
|
68
|
+
<dd>
|
|
69
|
+
<code class="credential-fingerprint">{{ pairing.keyFingerprint }}</code>
|
|
70
|
+
</dd>
|
|
71
|
+
<dt>Paired</dt>
|
|
72
|
+
<dd :title="new Date(pairing.pairedAt).toISOString()">{{ ago(pairing.pairedAt) }}</dd>
|
|
73
|
+
<dt>Generation</dt>
|
|
74
|
+
<dd>{{ pairing.credentialGeneration }}</dd>
|
|
75
|
+
</dl>
|
|
76
|
+
<p v-if="pairing.rotationPending" class="rotation-badge" role="status">
|
|
77
|
+
<RefreshCw :size="13" class="spin" /> rotation queued — waiting for the device to ACK
|
|
78
|
+
</p>
|
|
79
|
+
<div class="token-actions">
|
|
80
|
+
<button
|
|
81
|
+
v-if="pairing.status === 'paired'"
|
|
82
|
+
class="small"
|
|
83
|
+
:disabled="busy || pairing.rotationPending"
|
|
84
|
+
@click="rotate"
|
|
85
|
+
>
|
|
86
|
+
<RefreshCw :size="14" /> Rotate credential
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
v-if="pairing.status === 'paired'"
|
|
90
|
+
class="small danger"
|
|
91
|
+
:disabled="busy"
|
|
92
|
+
@click="revoke"
|
|
93
|
+
>
|
|
94
|
+
<ShieldOff :size="14" /> Revoke
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
<p v-if="error" class="message-error" role="alert">{{ error }}</p>
|
|
98
|
+
</section>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.token-manager {
|
|
103
|
+
border: 1px solid var(--border, #333);
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
padding: 0.6rem 0.8rem;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: 0.5rem;
|
|
109
|
+
font-size: 0.8rem;
|
|
110
|
+
}
|
|
111
|
+
.token-manager header {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: 0.4rem;
|
|
115
|
+
}
|
|
116
|
+
.token-manager h3 {
|
|
117
|
+
margin: 0;
|
|
118
|
+
font-size: 0.8rem;
|
|
119
|
+
text-transform: uppercase;
|
|
120
|
+
letter-spacing: 0.08em;
|
|
121
|
+
}
|
|
122
|
+
.token-manager .pairing-status {
|
|
123
|
+
margin-left: auto;
|
|
124
|
+
font-size: 0.7rem;
|
|
125
|
+
padding: 0.05rem 0.5rem;
|
|
126
|
+
border-radius: 999px;
|
|
127
|
+
border: 1px solid var(--border, #333);
|
|
128
|
+
}
|
|
129
|
+
.token-manager .pairing-status.paired {
|
|
130
|
+
color: var(--ok, #6cc070);
|
|
131
|
+
}
|
|
132
|
+
.token-manager .pairing-status.revoked {
|
|
133
|
+
color: var(--warn, #e0b040);
|
|
134
|
+
}
|
|
135
|
+
.token-manager dl {
|
|
136
|
+
display: grid;
|
|
137
|
+
grid-template-columns: auto 1fr;
|
|
138
|
+
gap: 0.15rem 0.8rem;
|
|
139
|
+
margin: 0;
|
|
140
|
+
}
|
|
141
|
+
.token-manager dt {
|
|
142
|
+
color: var(--muted, #888);
|
|
143
|
+
}
|
|
144
|
+
.token-manager dd {
|
|
145
|
+
margin: 0;
|
|
146
|
+
min-width: 0;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
text-overflow: ellipsis;
|
|
149
|
+
}
|
|
150
|
+
.token-manager .credential-fingerprint {
|
|
151
|
+
white-space: normal;
|
|
152
|
+
overflow-wrap: anywhere;
|
|
153
|
+
}
|
|
154
|
+
.token-manager .rotation-badge {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 0.35rem;
|
|
158
|
+
margin: 0;
|
|
159
|
+
color: var(--warn, #e0b040);
|
|
160
|
+
font-size: 0.75rem;
|
|
161
|
+
}
|
|
162
|
+
.token-manager .token-actions {
|
|
163
|
+
display: flex;
|
|
164
|
+
gap: 0.5rem;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
}
|
|
167
|
+
.token-manager .spin {
|
|
168
|
+
animation: tm-spin 1.2s linear infinite;
|
|
169
|
+
}
|
|
170
|
+
@keyframes tm-spin {
|
|
171
|
+
to {
|
|
172
|
+
transform: rotate(360deg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// TopBar: the top strip, bound to the real client store — device/session/agent counts,
|
|
3
|
+
// stream state, and the panel/new-session actions. Presentation only; all data comes
|
|
4
|
+
// from client/store.ts.
|
|
5
|
+
import { computed, ref } from "vue";
|
|
6
|
+
import { CircleHelp, Network, PanelLeft, PanelRight, Plus } from "lucide-vue-next";
|
|
7
|
+
import {
|
|
8
|
+
attempt,
|
|
9
|
+
network,
|
|
10
|
+
notify,
|
|
11
|
+
online,
|
|
12
|
+
select,
|
|
13
|
+
selected,
|
|
14
|
+
sessionHandle,
|
|
15
|
+
streamState,
|
|
16
|
+
ui,
|
|
17
|
+
showInspector,
|
|
18
|
+
hideInspector,
|
|
19
|
+
} from "../client/store";
|
|
20
|
+
|
|
21
|
+
const onlineCount = computed(
|
|
22
|
+
() => network.devices.value.filter((d) => d.status === "online").length,
|
|
23
|
+
);
|
|
24
|
+
const sessionCount = computed(() =>
|
|
25
|
+
network.devices.value.reduce((n, d) => n + (d.tree?.sessions.length ?? 0), 0),
|
|
26
|
+
);
|
|
27
|
+
const agentCount = computed(() =>
|
|
28
|
+
network.devices.value.reduce(
|
|
29
|
+
(n, d) => n + (d.tree?.sessions.reduce((m, s) => m + s.agents.length, 0) ?? 0),
|
|
30
|
+
0,
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
const creatingAgent = ref(false);
|
|
34
|
+
|
|
35
|
+
async function newAgent() {
|
|
36
|
+
const handle = sessionHandle.value;
|
|
37
|
+
const t = selected.value;
|
|
38
|
+
if (!handle || !t || creatingAgent.value) return;
|
|
39
|
+
creatingAgent.value = true;
|
|
40
|
+
const created = await attempt(() => handle.createAgent({}));
|
|
41
|
+
creatingAgent.value = false;
|
|
42
|
+
if (created) {
|
|
43
|
+
select({ ...t, agentId: created.id });
|
|
44
|
+
notify("New agent. Same session, same shared App State.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<{ newSession: [] }>();
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<header class="topbar">
|
|
53
|
+
<div class="brand">
|
|
54
|
+
<button
|
|
55
|
+
class="icon-button tree-toggle"
|
|
56
|
+
aria-label="Toggle network"
|
|
57
|
+
title="Network"
|
|
58
|
+
@click="ui.drawer = !ui.drawer"
|
|
59
|
+
>
|
|
60
|
+
<PanelLeft :size="19" /></button
|
|
61
|
+
><img class="brandmark" src="/icons/durable-pi.svg" alt="Durable Pi" /><strong
|
|
62
|
+
>Durable Pi</strong
|
|
63
|
+
>
|
|
64
|
+
</div>
|
|
65
|
+
<span class="topbar-sep" aria-hidden="true"></span>
|
|
66
|
+
<div class="daemon" data-testid="topbar-stats">
|
|
67
|
+
<span class="dot" :class="{ off: !onlineCount }" aria-hidden="true"></span>
|
|
68
|
+
<span
|
|
69
|
+
class="network-summary"
|
|
70
|
+
:title="`${onlineCount} of ${network.devices.value.length} devices online`"
|
|
71
|
+
><Network :size="16" />{{ onlineCount }} of {{ network.devices.value.length }} online</span
|
|
72
|
+
><span class="sep" aria-hidden="true">·</span
|
|
73
|
+
><span
|
|
74
|
+
><b>{{ sessionCount }}</b> sessions</span
|
|
75
|
+
><span class="sep" aria-hidden="true">·</span
|
|
76
|
+
><span
|
|
77
|
+
><b>{{ agentCount }}</b> agents</span
|
|
78
|
+
>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="top-actions">
|
|
81
|
+
<span class="chip connection" :class="{ accent: streamState === 'live' }"
|
|
82
|
+
><span class="dot" :class="{ off: streamState !== 'live' }" aria-hidden="true" />{{
|
|
83
|
+
streamState === "live" ? "Connected" : "Reconnecting"
|
|
84
|
+
}}</span
|
|
85
|
+
><button
|
|
86
|
+
class="icon-button"
|
|
87
|
+
title="How this works"
|
|
88
|
+
aria-label="How this works"
|
|
89
|
+
@click="ui.dialog = { kind: 'help' }"
|
|
90
|
+
>
|
|
91
|
+
<CircleHelp :size="19" /></button
|
|
92
|
+
><button
|
|
93
|
+
class="secondary small new-agent-button"
|
|
94
|
+
:disabled="!selected || !online || creatingAgent"
|
|
95
|
+
title="Start a fresh agent in this session"
|
|
96
|
+
@click="newAgent"
|
|
97
|
+
>
|
|
98
|
+
<Plus :size="18" />New agent
|
|
99
|
+
</button>
|
|
100
|
+
<button class="primary small" @click="emit('newSession')">
|
|
101
|
+
<Plus :size="18" />New session
|
|
102
|
+
</button>
|
|
103
|
+
<button
|
|
104
|
+
class="icon-button inspector-toggle"
|
|
105
|
+
aria-label="Toggle inspector"
|
|
106
|
+
title="Inspector"
|
|
107
|
+
:disabled="!selected || !online"
|
|
108
|
+
@click="ui.inspectorOverlay ? hideInspector() : showInspector()"
|
|
109
|
+
>
|
|
110
|
+
<PanelRight :size="19" />
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
</header>
|
|
114
|
+
</template>
|