@agimon-ai/doompi-runner 0.0.1-alpha.86 → 0.0.1-alpha.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/web.mjs +2 -2
- package/dist/extensions/web.mjs.map +1 -1
- package/package.json +15 -15
package/dist/extensions/web.mjs
CHANGED
|
@@ -805,7 +805,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
805
805
|
ref: body,
|
|
806
806
|
onScroll,
|
|
807
807
|
"data-testid": "runner-log-body",
|
|
808
|
-
className: "h-full overflow-auto bg-doom-
|
|
808
|
+
className: "h-full overflow-auto bg-doom-deep px-3 py-3 sm:px-[26px]",
|
|
809
809
|
children: [error !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
810
810
|
"data-testid": "runner-log-error",
|
|
811
811
|
className: "text-xs text-doom-red",
|
|
@@ -1045,7 +1045,7 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1045
1045
|
]
|
|
1046
1046
|
}),
|
|
1047
1047
|
/* @__PURE__ */ jsxs("div", {
|
|
1048
|
-
className: "relative min-h-0 flex-1 overflow-hidden bg-doom-
|
|
1048
|
+
className: "relative min-h-0 flex-1 overflow-hidden bg-doom-deep px-3 py-2 sm:px-[26px]",
|
|
1049
1049
|
children: [/* @__PURE__ */ jsx(TerminalView, {
|
|
1050
1050
|
ref: terminal,
|
|
1051
1051
|
"data-testid": "runner-shell-terminal",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.mjs","names":["isRecord","routes","isRecord","MAX_RETRIES","RETRY_BASE_MS","TICK_MS","TAB_ID_PREFIX","BYTES_PER_KB","BYTES_PER_MB","formatSize","isRecord","TICK_MS","activityGroupRunners","channelRunnerRuns","fillRunners","toolBash"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/_lib/format.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts","../../src/constants/webRunners.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/runnersStore.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/LaunchRunnerDialog.tsx","../../src/constants/webRunnerLog.ts","../../src/types/webRunnerLog.ts","../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/logApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnerLogPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_lib/screenApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnerShellPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/runnerTail.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/RunnersActivitySection.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/Runners.activity.runners.web.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/bashToolFormat.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/BashToolMessage.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/bash.web.tsx","../../generated/web.ts"],"sourcesContent":["/**\n * Local copy of the runtime's formatUptime (src/services/bashResult/index.ts):\n * the web plugin may reach only src/types, so the one helper it needs lives here.\n */\nexport function formatRunnerUptime(startedAt: string, now: number): string {\n const started = Date.parse(startedAt);\n if (Number.isNaN(started)) return 'unknown';\n const seconds = Math.max(0, Math.floor((now - started) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;\n}\n\n/** A bounded log view: what to render, and what the bound left out. */\nexport interface LogView {\n lines: string[];\n /** Lines dropped off the top to stay within the bound. */\n hidden: number;\n}\n\nexport interface LogViewOptions {\n /**\n * Drop the slice's last line because it is a fragment the follow stream will\n * deliver again, whole. Set only while following: a finished run's last line\n * may legitimately lack a newline, and dropping it would hide real output.\n */\n dropPartialTail?: boolean;\n}\n\n/**\n * The lines a log view shows: the slice's text, then whatever the follow\n * stream appended, bounded to the newest `max`.\n *\n * A log file ends with a newline, and the reader hands its text back with that\n * newline intact, so a naive split reports one blank line more than the file\n * has. Dropping exactly one trailing empty entry keeps the count the reader\n * sees equal to the count the server reported.\n */\nexport function logViewLines(\n text: string,\n appended: readonly string[],\n max: number,\n options: LogViewOptions = {},\n): LogView {\n const split = text.split('\\n');\n if (split.length > 0 && split[split.length - 1] === '') split.pop();\n if (options.dropPartialTail === true && split.length > 0) split.pop();\n const all = [...split, ...appended];\n const lines = all.slice(-max);\n return { lines, hidden: all.length - lines.length };\n}\n\n/**\n * Absolute 1-based numbers for a tail, counted back from the last line.\n *\n * A tail is contiguous, so only its end has to be known. Grep results are not\n * contiguous and carry their own numbers from the reader instead.\n */\nexport function tailLineNumbers(lastLineNumber: number, count: number): number[] {\n const first = Math.max(1, lastLineNumber - count + 1);\n return Array.from({ length: count }, (_, index) => first + index);\n}\n\n/**\n * Whether the view is actually following a growing log.\n *\n * Three things must hold: the reader asked to follow, no query is set (a\n * filtered view is a snapshot of the whole file, which a stream cannot\n * assemble a chunk at a time), and the runner is still writing. Without the\n * last one a finished run would claim to be tailing a file nothing will ever\n * append to.\n */\nexport function isFollowingLive(following: boolean, filtering: boolean, running: boolean): boolean {\n return following && !filtering && running;\n}\n","/**\n * The `/runners start` line the launch form sends.\n *\n * WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:\n * Cockpit code is compiled into the host bundle and may import only this\n * package's own web and type modules, so it cannot share the session's parser\n * in src/services. The two are pinned together by a test that parses what this\n * writes, which is the only guarantee that matters: the line has to survive\n * the trip.\n */\n\nexport const RUNNERS_START_LINE = '/runners start';\n/** A persistent login shell for the direct shell action in Runner Space. */\nexport const RUNNER_SHELL_COMMAND = 'exec \"${SHELL:-/bin/bash}\" -l';\n/** Ends the options and begins the command; the command may hold `=` and flags of its own. */\nexport const RUNNERS_COMMAND_SEPARATOR = '--';\n\nexport interface RunnerLaunchRequest {\n command: string;\n cwd?: string;\n name?: string;\n interactive?: boolean;\n}\n\n/** The no-form launch used when a reader asks for a shell rather than a command. */\nexport const RUNNER_SHELL_REQUEST: RunnerLaunchRequest = {\n command: RUNNER_SHELL_COMMAND,\n name: 'shell',\n interactive: true,\n};\n\n/** Quotes a value that carries whitespace, which is how a value survives tokenizing. */\nfunction quote(value: string): string {\n return /\\s/u.test(value) ? `\"${value}\"` : value;\n}\n\nexport function runnerLaunchLine(request: RunnerLaunchRequest): string {\n const cwd = request.cwd?.trim() ?? '';\n const name = request.name?.trim() ?? '';\n const pairs = [\n ...(name === '' ? [] : [`name=${quote(name)}`]),\n ...(cwd === '' ? [] : [`cwd=${quote(cwd)}`]),\n ...(request.interactive === true ? ['interactive=true'] : []),\n ];\n // The separator is always written, even with no options before it, so the\n // command is never re-read as a `key=value` pair.\n return [RUNNERS_START_LINE, ...pairs, RUNNERS_COMMAND_SEPARATOR, request.command.trim()].join(' ');\n}\n\n/** What the form will not let a reader send, worded the way the session would answer. */\nexport function launchProblems(request: RunnerLaunchRequest): string[] {\n const problems: string[] = [];\n if (request.command.trim() === '') problems.push('A runner needs a command to run.');\n return problems;\n}\n","export const RUNNER_RUNS_TYPE = 'runner_runs';\n","import { defineSessionStore, type SessionFrameSender } from '@agimon-ai/doompi-core/web';\n\nimport { RUNNER_RUNS_TYPE } from '../../../../../constants/webRunners';\nimport { type RunnerRunView } from '../../../../../types/webRunners';\nimport { type RunnerLaunchRequest, runnerLaunchLine } from './launchLine';\n\n/** The runtime's slash verb that stops one runner headlessly. */\nconst STOP_COMMAND = '/runners stop';\n\n/** One session's record: the hub's last report plus what this page asked for. */\nexport interface RunnersSession {\n /** The runners the hub last reported; presented order preserved. */\n runs: RunnerRunView[];\n /** Runners a stop was asked for and that have not yet reported an exit. */\n stopRequested: string[];\n}\n\nexport const runners = defineSessionStore<RunnersSession>({ runs: [], stopRequested: [] });\n\n/** The runner channel keeps the dock's busy state aligned with its rendered run list. */\nexport const runnerActivitySource = {\n subscribe(listener: () => void) {\n const subscription = runners.store.subscribe(listener);\n return () => subscription.unsubscribe();\n },\n isActive(sessionId: string | null) {\n return runners.select(runners.store.state, sessionId).runs.some((run) => run.state === 'running');\n },\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Asks the runtime to stop a runner through the same slash command the TUI's\n * Runner Space uses. The request is remembered until the runner's record\n * reports an exit; the runtime, not this click, decides when that is.\n */\nexport function requestRunnerStop(send: SessionFrameSender, sessionId: string, id: string): void {\n send(sessionId, { type: 'prompt', message: `${STOP_COMMAND} ${id}` });\n runners.update(sessionId, (current) => ({\n ...current,\n stopRequested: [...current.stopRequested.filter((known) => known !== id), id],\n }));\n}\n\n/**\n * Asks the runtime to start a runner, through the same prompt frame the stop\n * request uses and the same way agents and workflows launch their own work.\n *\n * Nothing is recorded as pending here. The runtime names the run, and it\n * arrives in the feed on its own; guessing at an id before it exists would\n * only have to be reconciled later.\n */\nexport function requestRunnerStart(send: SessionFrameSender, sessionId: string, request: RunnerLaunchRequest): void {\n send(sessionId, { type: 'prompt', message: runnerLaunchLine(request) });\n}\n\nexport interface RunnerRunsPayload {\n runs: RunnerRunView[];\n}\n\n/** The plugin's session data channel: 'runner_runs' payloads into the store. */\nexport const runnerRunsChannel = runners.channel<RunnerRunsPayload>({\n channel: RUNNER_RUNS_TYPE,\n parse(input) {\n if (!isRecord(input) || !Array.isArray(input.runs)) return null;\n return { runs: input.runs.filter(isRecord) as unknown as RunnerRunView[] };\n },\n reduce(current, { runs }) {\n // A stop request is spent once the runner reports an exit or leaves the feed.\n const stillRunning = new Set(runs.filter((run) => run.state === 'running').map((run) => run.id));\n return { runs, stopRequested: current.stopRequested.filter((id) => stillRunning.has(id)) };\n },\n});\n","import type { SessionFrameSender } from '@agimon-ai/doompi-core/web';\nimport {\n Button,\n Checkbox,\n Dialog,\n DialogBody,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Textarea,\n} from '@agimon-ai/doompi-web-components';\nimport { useState } from 'react';\n\nimport { launchProblems, type RunnerLaunchRequest, runnerLaunchLine } from '../_lib/launchLine';\nimport { requestRunnerStart } from '../_lib/runnersStore';\n\nconst COMMAND_ROWS = 3;\nconst INTERACTIVE_FIELD_ID = 'runner-launch-interactive-field';\n\nfunction FieldLabel({ children }: { children: string }) {\n return <span className=\"text-2xs font-bold tracking-widest text-doom-faint\">{children}</span>;\n}\n\n/**\n * Starts one runner by hand: the command, where it runs, what to call it, and\n * whether it needs a terminal, then the exact `/runners start` line the\n * session will parse.\n *\n * The line is shown rather than hidden. This sends a shell command through the\n * session, and a reader is entitled to see exactly what they are about to run\n * before they run it.\n */\nexport function LaunchRunnerDialog({\n sessionId,\n sendSessionFrame,\n defaultCwd,\n onClose,\n}: {\n sessionId: string;\n sendSessionFrame: SessionFrameSender;\n defaultCwd?: string;\n onClose: () => void;\n}) {\n const [command, setCommand] = useState('');\n const [cwd, setCwd] = useState(defaultCwd ?? '');\n const [name, setName] = useState('');\n const [interactive, setInteractive] = useState(false);\n\n const request: RunnerLaunchRequest = { command, cwd, name, interactive };\n const problems = launchProblems(request);\n const line = runnerLaunchLine(request);\n\n const launch = (): void => {\n if (problems.length > 0) return;\n requestRunnerStart(sendSessionFrame, sessionId, request);\n onClose();\n };\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <DialogContent width=\"lg\" data-testid=\"runner-launch-dialog\" aria-describedby={undefined}>\n <DialogHeader>\n <DialogTitle data-testid=\"runner-launch-title\">launch a runner</DialogTitle>\n </DialogHeader>\n <DialogBody>\n <div className=\"flex flex-col gap-1\">\n <FieldLabel>COMMAND</FieldLabel>\n <Textarea\n data-testid=\"runner-launch-command\"\n rows={COMMAND_ROWS}\n value={command}\n placeholder=\"pnpm test\"\n onChange={(event) => setCommand(event.target.value)}\n className=\"text-sm\"\n />\n </div>\n\n <div className=\"flex flex-col gap-2.5 sm:flex-row\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <FieldLabel>CWD</FieldLabel>\n <Input\n size=\"sm\"\n data-testid=\"runner-launch-cwd\"\n value={cwd}\n placeholder=\"the session's own directory\"\n onChange={(event) => setCwd(event.target.value)}\n className=\"text-xs\"\n />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <FieldLabel>NAME</FieldLabel>\n <Input\n size=\"sm\"\n data-testid=\"runner-launch-name\"\n value={name}\n placeholder=\"chosen for you when empty\"\n onChange={(event) => setName(event.target.value)}\n className=\"text-xs\"\n />\n </div>\n </div>\n\n {/* Paired by id rather than by nesting: the checkbox renders as a\n button, which a wrapping label does not associate with. */}\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id={INTERACTIVE_FIELD_ID}\n data-testid=\"runner-launch-interactive\"\n checked={interactive}\n onCheckedChange={(checked) => setInteractive(checked === true)}\n />\n <label htmlFor={INTERACTIVE_FIELD_ID} className=\"text-xs text-doom-dim\">\n needs a terminal, for a command that prompts (requires rmux or tmux)\n </label>\n </div>\n\n <div className=\"flex flex-col gap-1 rounded-md border border-doom-border bg-doom-deep px-3 py-2\">\n <FieldLabel>SENT TO THE SESSION</FieldLabel>\n <pre data-testid=\"runner-launch-line\" className=\"whitespace-pre-wrap break-words text-xs text-doom-green\">\n {line}\n </pre>\n </div>\n\n {problems.length > 0 ? (\n <p data-testid=\"runner-launch-problem\" className=\"text-xs text-doom-red\">\n {problems.join(' ')}\n </p>\n ) : null}\n\n <DialogFooter className=\"flex-wrap sm:flex-nowrap\">\n <span className=\"w-full text-2xs text-doom-faint sm:w-auto\">\n it appears above once the runtime starts it\n </span>\n <span className=\"min-w-0 flex-1\" />\n <Button variant=\"outline\" size=\"xs\" data-testid=\"runner-launch-cancel\" onClick={onClose}>\n cancel\n </Button>\n <Button\n variant=\"primary\"\n size=\"xs\"\n data-testid=\"runner-launch-submit\"\n disabled={problems.length > 0}\n onClick={launch}\n >\n launch\n </Button>\n </DialogFooter>\n </DialogBody>\n </DialogContent>\n </Dialog>\n );\n}\n","export const RUNNER_API_BASE_PATH = 'runner';\nexport const RUN_ID_PARAM = 'runId';\nexport const RUNNER_SCREEN_EVENT = 'screen';\nexport const RUNNER_LOG_STREAM_EVENT = 'append';\nexport const RUNNER_LOG_PING_EVENT = 'ping';\n","import type { LogSlice } from './logReader';\n\n/**\n * The runner log API, shared by this package's session-scoped routes and its\n * cockpit plugin. The two halves run in different processes, so the wire\n * vocabulary is declared here: the browser half may reach `src/types` and\n * nothing else on the server side.\n *\n * Where the routes live is not here. That is `src/types/apiRoutes`, which both\n * halves read, and the generated client turns into the URLs the page addresses.\n * The file a run writes to is still read from its metadata record by the server\n * alone.\n */\n\n/**\n * One chunk of an attached pane's output.\n *\n * Base64 because these are terminal bytes, escape sequences and all, and a\n * server-sent event is a line-oriented text frame that would mangle them.\n */\nexport interface RunnerScreenEvent {\n chunk: string;\n /** Byte offset just past this chunk, so a reconnect resumes rather than replays. */\n offset?: number;\n /** Set once the runner exits, so the page can stop watching. */\n ended?: boolean;\n}\n\n/** The body of a keystroke POST. Text, not key codes: the pane wants bytes. */\nexport interface RunnerInputRequest {\n text: string;\n}\n\n/** The named SSE event carrying a RunnerScreenEvent payload. */\n\n/**\n * A log request as query parameters. `grep` is a literal substring, not a\n * regular expression, because that is what the reader matches; `lines` bounds\n * the answer to the last N lines of whatever survived the filter.\n */\nexport interface RunnerLogQueryParams {\n lines?: number;\n grep?: string;\n ignoreCase?: boolean;\n contextLines?: number;\n}\n\n/** The slice the log route answers with, plus which runner it came from. */\nexport interface RunnerLogResponse extends LogSlice {\n runId: string;\n /** True while the runner is still writing, so the page knows following is worth offering. */\n running: boolean;\n}\n\n/**\n * One server-sent event on the follow stream: the lines appended since the last\n * one. The stream never filters: a page that sets a query stops following and\n * reads a filtered slice instead, because grep with context is a whole-file\n * answer and cannot be assembled from a chunk at a time.\n */\nexport interface RunnerLogStreamEvent {\n /** Appended lines, in order; empty when the event only reports the runner ending. */\n lines: string[];\n /**\n * Byte offset just past the last line in this event.\n *\n * A follower that loses the stream resumes here. Without it the page could\n * only fall back to the offset its last slice reported, which every line\n * delivered since has already moved past, so a reconnect would replay them.\n */\n offset?: number;\n /** Set once the runner exits, so the page can stop following without polling the run list. */\n ended?: boolean;\n}\n\n/** The named SSE event carrying a RunnerLogStreamEvent payload. */\n\n/**\n * The named SSE event that carries nothing.\n *\n * A runner can be alive and silent for minutes, which on the wire is\n * indistinguishable from a connection that died. This is sent on a timer so\n * the socket keeps proving itself. Readers that only listen for `append`\n * ignore it, which is what makes it safe to add.\n */\n\n/** Query parameter names, shared so the page and the route cannot drift apart. */\nexport const RUNNER_LOG_PARAMS = {\n lines: 'lines',\n grep: 'grep',\n ignoreCase: 'ignoreCase',\n contextLines: 'contextLines',\n from: 'from',\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport { RUN_ID_PARAM } from '../constants/webRunnerLog';\nimport { RUNNER_LOG_PARAMS, type RunnerLogResponse } from './webRunnerLog';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path: the build reads both off\n * `src/extensions/workspaces/sessions/(backend)/api/runner/`, so the mount is\n * stated once, by the folder that creates it. This package stated it four times\n * before, once per URL builder, each of them re-spelling `/plugins/` and the\n * session prefix by hand.\n *\n * The Hono app registers these paths verbatim and the browser addresses them,\n * so a path cannot move on one side alone. `:runId` is Hono's own parameter\n * syntax for that reason; a caller supplies the value as `params`, and the\n * client encodes the segment.\n */\nexport default defineApiRoutes({\n log: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/log`,\n query: [\n RUNNER_LOG_PARAMS.lines,\n RUNNER_LOG_PARAMS.grep,\n RUNNER_LOG_PARAMS.ignoreCase,\n RUNNER_LOG_PARAMS.contextLines,\n ],\n response: apiResponse<RunnerLogResponse>(),\n },\n /**\n * The live tail of that same log.\n *\n * `stream: true`, so the client exposes a URL and no call method: the\n * consumer is the browser's own `EventSource`, and a relayed call would\n * buffer a stream that never ends.\n */\n logStream: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/log/stream`,\n query: [RUNNER_LOG_PARAMS.from],\n stream: true,\n },\n /** The unscrubbed pane bytes, for a terminal emulator rather than a reader. */\n screenStream: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/screen/stream`,\n query: [RUNNER_LOG_PARAMS.from],\n stream: true,\n },\n input: {\n method: 'POST',\n path: `/runners/:${RUN_ID_PARAM}/screen/input`,\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/doompi-core/web';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'runner' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'runner',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","import { api } from '../../../../../../generated/client';\nimport { RUN_ID_PARAM, RUNNER_LOG_STREAM_EVENT } from '../../../../../constants/webRunnerLog';\nimport {\n RUNNER_LOG_PARAMS,\n type RunnerLogQueryParams,\n type RunnerLogResponse,\n type RunnerLogStreamEvent,\n} from '../../../../../types/webRunnerLog';\n\n/**\n * The page's half of this package's log API. The only place the cockpit talks\n * HTTP to the hub for a runner, so if the transport ever changes, it changes\n * here alone.\n *\n * The generated client owns every URL: a route is named, a session id selects\n * that session's own copy of the API, and the run id is a path parameter the\n * client substitutes and encodes. Nothing here spells a mount, a prefix or a\n * proxy parameter, which is what the four deleted URL builders each did by\n * hand.\n */\n\nexport type RunnerLogResult = { slice: RunnerLogResponse } | { error: string };\n\nconst UNREACHABLE = 'The cockpit hub is unreachable.';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Reads one runner's log: the last N lines, or the lines matching a substring with context. */\nexport async function fetchRunnerLog(\n sessionId: string,\n runId: string,\n params: RunnerLogQueryParams = {},\n signal?: AbortSignal,\n): Promise<RunnerLogResult> {\n const result = await api.session(sessionId).log({\n params: { [RUN_ID_PARAM]: runId },\n // An unset parameter is left out rather than sent empty: the route reads a\n // missing one as its own default, and an empty grep as no filter at all.\n query: {\n [RUNNER_LOG_PARAMS.lines]: params.lines,\n [RUNNER_LOG_PARAMS.grep]: params.grep === '' ? undefined : params.grep,\n [RUNNER_LOG_PARAMS.ignoreCase]: params.ignoreCase === true ? true : undefined,\n [RUNNER_LOG_PARAMS.contextLines]: params.contextLines,\n },\n ...(signal === undefined ? {} : { signal }),\n });\n if (!result.ok) {\n // Status 0 is the transport never answering, which covers both a dead hub\n // and the caller replacing this request. An abort is the caller's own doing\n // and is reported as no answer rather than as a failure to show.\n if (result.status === 0) return { error: signal?.aborted === true ? '' : UNREACHABLE };\n return { error: result.error === '' ? `The hub answered ${String(result.status)}.` : result.error };\n }\n if (!isRecord(result.data) || typeof result.data.text !== 'string') {\n return { error: `The hub answered ${String(result.status)}.` };\n }\n return { slice: result.data };\n}\n\nexport interface RunnerLogFollow {\n close(): void;\n}\n\n/** Attempts before a dropped stream is reported as lost rather than retried. */\nconst MAX_RETRIES = 5;\nconst RETRY_BASE_MS = 500;\n\n/**\n * Follows a running log from the offset already read, so no line is missed\n * between the slice and the stream. The stream never filters; a page with a\n * query stops following and reads a filtered slice instead.\n *\n * A dropped connection is retried with backoff from the offset the last event\n * reported, not from the offset the page started at, so a reconnect resumes\n * instead of replaying. The browser's own EventSource retry cannot do this:\n * it reopens the URL it was given, whose offset is stale the moment a line\n * arrives, so each of its reconnects would duplicate everything since.\n */\nexport function followRunnerLog(\n sessionId: string,\n runId: string,\n from: number,\n handlers: { onEvent(event: RunnerLogStreamEvent): void; onLost(): void },\n): RunnerLogFollow {\n let offset = from;\n let retries = 0;\n let closed = false;\n let source: EventSource | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const open = (): void => {\n if (closed) return;\n const current = new EventSource(\n api\n .session(sessionId)\n .logStream.url({ params: { [RUN_ID_PARAM]: runId }, query: { [RUNNER_LOG_PARAMS.from]: offset } }),\n );\n source = current;\n current.addEventListener(RUNNER_LOG_STREAM_EVENT, (message) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse((message as MessageEvent<string>).data);\n } catch {\n return; // A truncated frame is the next one's problem, not a reason to drop the stream.\n }\n if (!isRecord(parsed) || !Array.isArray(parsed.lines)) return;\n const event = parsed as unknown as RunnerLogStreamEvent;\n if (typeof event.offset === 'number') offset = event.offset;\n retries = 0;\n // The server closes after this one, and a close reaches us as an error.\n // Standing down first keeps that from looking like a dropped stream.\n if (event.ended === true) {\n closed = true;\n current.close();\n }\n handlers.onEvent(event);\n });\n current.addEventListener('error', () => {\n current.close();\n if (closed) return;\n retries += 1;\n if (retries > MAX_RETRIES) {\n handlers.onLost();\n return;\n }\n timer = setTimeout(open, RETRY_BASE_MS * 2 ** (retries - 1));\n });\n };\n\n open();\n return {\n close: () => {\n closed = true;\n if (timer !== undefined) clearTimeout(timer);\n source?.close();\n },\n };\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { AnsiLine, Button, Input, SearchIcon, StatusBadge, type StatusTone } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { RunnerLogResponse } from '../../../../../types/webRunnerLog';\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { formatRunnerUptime, isFollowingLive, logViewLines, tailLineNumbers } from '../_lib/format';\nimport { fetchRunnerLog, followRunnerLog } from '../_lib/logApi';\nimport { requestRunnerStop, runners } from '../_lib/runnersStore';\n\nconst TICK_MS = 10_000;\n/** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */\nconst TAB_ID_PREFIX = 'runner-log-';\n/** Lines held in the view; a follow that ran all day must not grow without bound. */\nconst MAX_VIEW_LINES = 2000;\n/** Context lines each side of a match, the same either-side window grep would use. */\nconst CONTEXT_LINES = 2;\n/** A query is typed a character at a time; each one costs a whole-file read on the hub. */\nconst SEARCH_DEBOUNCE_MS = 200;\n/** How close to the bottom still counts as \"at the bottom\" for following. */\nconst NEAR_BOTTOM_PX = 24;\nconst BYTES_PER_KB = 1024;\nconst BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;\n\n/** The temporary tab for one runner's log; the host keeps this panel while the tab is open. */\nexport function runnerLogTab(run: RunnerRunView): TransientTab {\n const runId = run.id;\n return {\n id: `${TAB_ID_PREFIX}${runId}`,\n label: `log · ${run.name}`,\n panel: (props: WebPluginSlotProps) => <RunnerLogPanel {...props} runId={runId} />,\n };\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\nfunction badgeOf(run: RunnerRunView | undefined): { tone: StatusTone; label: string } {\n if (run === undefined) return { tone: 'neutral', label: 'gone' };\n if (run.state === 'running') return { tone: 'running', label: 'running' };\n if (run.exit === undefined || run.exit.reason === 'stopped') return { tone: 'neutral', label: 'stopped' };\n const clean = run.exit.reason === 'completed' && (run.exit.code === 0 || run.exit.code === null);\n return clean ? { tone: 'ok', label: 'done' } : { tone: 'error', label: run.exit.reason.replace('_', ' ') };\n}\n\n/** Whether one line is a match rather than the context printed around one. */\nfunction isMatch(line: string, needle: string, ignoreCase: boolean): boolean {\n if (needle === '') return false;\n return ignoreCase ? line.toLowerCase().includes(needle.toLowerCase()) : line.includes(needle);\n}\n\n/** One fact of the runner, on its own line so a long command is never elided. */\nfunction MetaRow({ label, value }: { label: string; value: string }) {\n return (\n <span className=\"flex min-w-0 gap-2.5\">\n <span className=\"w-14 shrink-0 text-xs text-doom-faint\">{label}</span>\n <span className=\"min-w-0 flex-1 break-all text-xs text-doom-dim\">{value}</span>\n </span>\n );\n}\n\n/**\n * One rendered log line: its number, whether it matched, and the text.\n *\n * A row per line rather than one block of joined text. The block form re-parsed\n * every line's escapes whenever a single line arrived, because the parse was\n * keyed on the joined string; keyed by line number instead, an append only\n * costs the line that appended.\n */\nfunction LogRow({ number, line, matched }: { number: number | undefined; line: string; matched: boolean }) {\n return (\n <div data-testid=\"runner-log-row\" data-matched={matched} className=\"flex min-w-0 gap-2\">\n {number === undefined ? null : (\n <span className=\"w-12 shrink-0 select-none text-right text-xs leading-relaxed text-doom-faint\">{number}</span>\n )}\n <span\n aria-hidden\n className={`w-4 shrink-0 select-none text-xs leading-relaxed ${matched ? 'text-doom-yellow' : 'text-doom-faint'}`}\n >\n {matched ? '>>' : ''}\n </span>\n <AnsiLine\n line={line}\n className={`min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-xs leading-relaxed ${\n matched ? 'text-doom-text' : 'text-doom-dim'\n }`}\n />\n </div>\n );\n}\n\n/**\n * One runner's log: the bounded tail, following while the runner writes, and\n * a grep over the whole file when the reader types a query.\n *\n * Search is not find-next. The reader matches a literal substring and answers\n * with the matching lines and their context, so a query replaces the view\n * rather than moving a cursor through it, and following pauses while one is\n * set because a filtered view is a snapshot of a file that is still growing.\n */\nexport function RunnerLogPanel({ sessionId, runId, sendSessionFrame }: WebPluginSlotProps & { runId: string }) {\n const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));\n const run = runs.find((candidate) => candidate.id === runId);\n const [now, setNow] = useState(() => Date.now());\n const [typed, setTyped] = useState('');\n const [query, setQuery] = useState('');\n const [ignoreCase, setIgnoreCase] = useState(false);\n const [context, setContext] = useState(CONTEXT_LINES);\n const [following, setFollowing] = useState(true);\n const [slice, setSlice] = useState<RunnerLogResponse | undefined>(undefined);\n const [streamFrom, setStreamFrom] = useState<number | undefined>(undefined);\n const [appended, setAppended] = useState<string[]>([]);\n const [error, setError] = useState<string | undefined>(undefined);\n /** False once the reader scrolls up: following must not yank them back down. */\n const [pinned, setPinned] = useState(true);\n const bottom = useRef<HTMLDivElement | null>(null);\n const body = useRef<HTMLDivElement | null>(null);\n\n const filtering = query !== '';\n const live = isFollowingLive(following, filtering, slice?.running === true);\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n // Typing is not a question yet. Reading the log costs a whole-file scan on\n // the hub, so the question is asked once the reader stops typing.\n useEffect(() => {\n const timer = setTimeout(() => setQuery(typed), SEARCH_DEBOUNCE_MS);\n return () => clearTimeout(timer);\n }, [typed]);\n\n const load = useCallback(\n (signal: AbortSignal) => {\n if (sessionId === null) return;\n const params = filtering ? { grep: query, ignoreCase, contextLines: context } : {};\n void fetchRunnerLog(sessionId, runId, params, signal).then((result) => {\n if (signal.aborted) return;\n if ('error' in result) {\n if (result.error !== '') setError(result.error);\n return;\n }\n setError(undefined);\n setAppended([]);\n setSlice(result.slice);\n setStreamFrom(result.slice.completeBytes);\n });\n },\n [sessionId, runId, filtering, query, ignoreCase, context],\n );\n\n // Re-read whenever the question changes. A query is debounced above, so the\n // in-flight request for the previous one is abandoned only on a real change.\n useEffect(() => {\n const controller = new AbortController();\n load(controller.signal);\n return () => controller.abort();\n }, [load]);\n\n // Keyed on the offset rather than the slice object: a re-read that lands on\n // the same place must not tear down a healthy stream and reopen it.\n useEffect(() => {\n if (!live || sessionId === null || streamFrom === undefined) return;\n const follow = followRunnerLog(sessionId, runId, streamFrom, {\n onEvent: (event) => {\n if (event.lines.length > 0) {\n setAppended((current) => [...current, ...event.lines].slice(-MAX_VIEW_LINES));\n }\n if (event.ended === true) setFollowing(false);\n },\n onLost: () => setFollowing(false),\n });\n return () => follow.close();\n }, [live, sessionId, runId, streamFrom]);\n\n // A slice read mid-line ends in a fragment the stream will send again whole.\n const partialTail = slice !== undefined && slice.exists && slice.completeBytes < slice.fileSize;\n const view = logViewLines(slice?.text ?? '', appended, MAX_VIEW_LINES, { dropPartialTail: live && partialTail });\n const numbers = useMemo(() => {\n if (slice === undefined) return [];\n if (filtering) return slice.lineNumbers ?? [];\n const last = slice.totalLines + appended.length - (live && partialTail ? 1 : 0);\n return tailLineNumbers(last, view.lines.length);\n }, [slice, filtering, appended.length, live, partialTail, view.lines.length]);\n\n useEffect(() => {\n if (live && pinned) bottom.current?.scrollIntoView({ block: 'end' });\n }, [live, pinned, view.lines.length]);\n\n // Following is a promise to show the newest line, not a promise to drag the\n // reader there. Scrolling up is how they say they are reading something else.\n const onScroll = (): void => {\n const element = body.current;\n if (element === null) return;\n setPinned(element.scrollHeight - element.scrollTop - element.clientHeight <= NEAR_BOTTOM_PX);\n };\n\n const badge = badgeOf(run);\n const stopping = run !== undefined && stopRequested.includes(run.id);\n const elapsed =\n run === undefined ? '' : formatRunnerUptime(run.startedAt, run.exit ? Date.parse(run.exit.finishedAt) : now);\n\n return (\n <div data-testid=\"runner-log-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex min-h-11 shrink-0 flex-wrap items-center gap-2 border-b border-doom-border-soft px-3 py-2 sm:h-11 sm:flex-nowrap sm:gap-2.5 sm:px-[26px] sm:py-0\">\n <span data-testid=\"runner-log-name\" className=\"shrink-0 truncate text-sm font-bold text-doom-hi\">\n {run?.name ?? runId}\n </span>\n <StatusBadge tone={badge.tone} data-testid=\"runner-log-state\">\n {badge.label}\n </StatusBadge>\n {run ? (\n <>\n <span className=\"shrink-0 text-xs text-doom-faint\">{elapsed}</span>\n <span className=\"shrink-0 text-xs text-doom-faint\">\n {`pid ${String(run.pid)} · ${run.backend}${run.interactive ? ' · tty' : ''}`}\n </span>\n </>\n ) : (\n <span data-testid=\"runner-log-gone\" className=\"text-xs text-doom-faint\">\n no longer listed; its log stays readable\n </span>\n )}\n <span className=\"min-w-0 flex-1\" />\n {run !== undefined && run.state === 'running' && sessionId !== null ? (\n <Button\n variant={stopping ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid=\"runner-log-stop\"\n disabled={stopping}\n title={stopping ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'}\n onClick={() => requestRunnerStop(sendSessionFrame, sessionId, run.id)}\n className=\"px-2 text-2xs font-bold\"\n >\n {stopping ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </div>\n\n {run ? (\n <div className=\"flex shrink-0 flex-col gap-1.5 border-b border-doom-border-soft px-3 py-2.5 sm:px-[26px]\">\n <MetaRow label=\"command\" value={run.command} />\n <MetaRow label=\"cwd\" value={run.cwd} />\n </div>\n ) : null}\n\n <div className=\"flex min-h-10 shrink-0 flex-wrap items-center gap-2 border-b border-doom-border-soft px-3 py-2 sm:h-10 sm:flex-nowrap sm:gap-2.5 sm:px-[26px] sm:py-0\">\n <span className=\"relative flex min-w-0 flex-1 items-center sm:w-[300px] sm:flex-none\">\n <SearchIcon className=\"pointer-events-none absolute left-2.5 h-3 w-3 text-doom-faint\" />\n <Input\n size=\"sm\"\n data-testid=\"runner-log-search\"\n value={typed}\n placeholder=\"search this log\"\n onChange={(event) => setTyped(event.target.value)}\n className=\"pl-7 text-xs\"\n />\n </span>\n <Button\n variant={ignoreCase ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-ignore-case\"\n aria-pressed={ignoreCase}\n title=\"match regardless of case\"\n onClick={() => setIgnoreCase((current) => !current)}\n className=\"px-2 text-2xs font-bold\"\n >\n aA\n </Button>\n <Button\n variant={context > 0 && filtering ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-context\"\n disabled={!filtering}\n title=\"lines of context kept either side of a match\"\n onClick={() => setContext((current) => (current === 0 ? CONTEXT_LINES : 0))}\n className=\"px-2 text-2xs font-bold\"\n >\n {`± ${String(context)} lines`}\n </Button>\n <span className=\"min-w-0 flex-1\" />\n <span\n data-testid=\"runner-log-stats\"\n className=\"min-w-0 basis-full truncate text-xs text-doom-faint sm:basis-auto\"\n >\n {slice === undefined\n ? 'reading…'\n : filtering\n ? `${String(slice.lineCount)} matching of ${slice.totalLines.toLocaleString('en-US')} lines`\n : `${live ? 'tailing' : 'showing'} last ${String(view.lines.length)} of ${slice.totalLines.toLocaleString('en-US')} lines`}\n {view.hidden > 0 ? ` · ${view.hidden.toLocaleString('en-US')} older hidden` : ''}\n </span>\n <Button\n variant={live ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-follow\"\n aria-pressed={live}\n disabled={filtering || slice?.running !== true}\n title={filtering ? 'a filtered view is a snapshot; clear the query to follow' : 'follow the log as it grows'}\n onClick={() => setFollowing((current) => !current)}\n className=\"px-2 text-2xs font-bold\"\n >\n {filtering ? 'follow · paused' : 'follow'}\n </Button>\n </div>\n\n <div className=\"relative min-h-0 flex-1\">\n <div\n ref={body}\n onScroll={onScroll}\n data-testid=\"runner-log-body\"\n className=\"h-full overflow-auto bg-doom-panel-deep px-3 py-3 sm:px-[26px]\"\n >\n {error !== undefined ? (\n <p data-testid=\"runner-log-error\" className=\"text-xs text-doom-red\">\n {error}\n </p>\n ) : slice === undefined ? (\n <p data-testid=\"runner-log-reading\" className=\"text-xs text-doom-faint\">\n reading…\n </p>\n ) : slice.exists === false ? (\n <p className=\"text-xs text-doom-faint\">this runner has not written a log yet</p>\n ) : view.lines.length === 0 ? (\n <p className=\"text-xs text-doom-faint\">{filtering ? 'nothing matched' : 'the log is empty'}</p>\n ) : (\n view.lines.map((line, index) => {\n const number = numbers[index];\n const previous = numbers[index - 1];\n const gap = previous !== undefined && number !== undefined && number - previous > 1;\n return (\n <Fragment key={number ?? `row-${String(index)}`}>\n {gap ? (\n <div\n data-testid=\"runner-log-gap\"\n aria-hidden\n className=\"select-none py-0.5 pl-12 text-xs leading-relaxed text-doom-faint\"\n >\n ⋯\n </div>\n ) : null}\n <LogRow number={number} line={line} matched={filtering && isMatch(line, query, ignoreCase)} />\n </Fragment>\n );\n })\n )}\n <div ref={bottom} />\n </div>\n {live && !pinned ? (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n data-testid=\"runner-log-jump\"\n title=\"scroll back to the newest lines and keep following\"\n onClick={() => {\n setPinned(true);\n bottom.current?.scrollIntoView({ block: 'end' });\n }}\n className=\"absolute bottom-3 right-3 px-2 text-2xs font-bold\"\n >\n jump to latest\n </Button>\n ) : null}\n </div>\n\n <div className=\"flex h-8 shrink-0 items-center gap-2.5 border-t border-doom-border-soft px-3 sm:px-[26px]\">\n <span data-testid=\"runner-log-path\" className=\"min-w-0 flex-1 truncate text-2xs text-doom-faint\">\n {run?.logPath ?? slice?.path ?? ''}\n </span>\n {slice !== undefined ? (\n <span className=\"shrink-0 text-2xs text-doom-faint\">{formatSize(slice.fileSize)}</span>\n ) : null}\n </div>\n </div>\n );\n}\n","import { api } from '../../../../../../generated/client';\nimport { RUN_ID_PARAM, RUNNER_SCREEN_EVENT } from '../../../../../constants/webRunnerLog';\nimport { RUNNER_LOG_PARAMS, type RunnerInputRequest, type RunnerScreenEvent } from '../../../../../types/webRunnerLog';\n\n/**\n * The page's half of the attached pane.\n *\n * Two directions over two mechanisms, because the hub proxies requests and\n * server-sent events but cannot upgrade a socket: pane bytes arrive on an\n * EventSource, and keystrokes go back as ordinary POSTs. Both addresses come\n * from the generated client: the stream route offers a URL and no call method,\n * which is exactly what an EventSource wants and all it can be given.\n */\n\n/** Turns one base64 chunk back into the bytes a terminal was sent. */\nexport function decodeChunk(chunk: string): Uint8Array {\n const binary = atob(chunk);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n}\n\nexport interface RunnerScreenWatch {\n close(): void;\n}\n\n/** Attempts before a dropped screen stream is reported as lost rather than retried. */\nconst MAX_RETRIES = 5;\nconst RETRY_BASE_MS = 500;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function watchRunnerScreen(\n sessionId: string,\n runId: string,\n from: number,\n handlers: { onEvent(event: RunnerScreenEvent): void; onLost(): void },\n): RunnerScreenWatch {\n let offset = from;\n let retries = 0;\n let closed = false;\n let source: EventSource | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const open = (): void => {\n if (closed) return;\n const current = new EventSource(\n api\n .session(sessionId)\n .screenStream.url({ params: { [RUN_ID_PARAM]: runId }, query: { [RUNNER_LOG_PARAMS.from]: offset } }),\n );\n source = current;\n current.addEventListener(RUNNER_SCREEN_EVENT, (message) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse((message as MessageEvent<string>).data);\n } catch {\n return;\n }\n if (!isRecord(parsed) || typeof parsed.chunk !== 'string') return;\n const event = parsed as unknown as RunnerScreenEvent;\n // Every delivered byte moves the resume point, so a reconnect asks for\n // what comes next rather than repainting everything again.\n if (typeof event.offset === 'number') offset = event.offset;\n retries = 0;\n // The server closes after this one, and a close reaches us as an error.\n if (event.ended === true) {\n closed = true;\n current.close();\n }\n handlers.onEvent(event);\n });\n current.addEventListener('error', () => {\n current.close();\n if (closed) return;\n retries += 1;\n if (retries > MAX_RETRIES) {\n handlers.onLost();\n return;\n }\n timer = setTimeout(open, RETRY_BASE_MS * 2 ** (retries - 1));\n });\n };\n\n open();\n return {\n close: () => {\n closed = true;\n if (timer !== undefined) clearTimeout(timer);\n source?.close();\n },\n };\n}\n\n/** Sends one batch of keystrokes. Resolves to whether the pane took them. */\nexport async function sendRunnerInput(sessionId: string, runId: string, text: string): Promise<boolean> {\n const body: RunnerInputRequest = { text };\n const result = await api.session(sessionId).input({ params: { [RUN_ID_PARAM]: runId }, body });\n return result.ok;\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button, StatusBadge, type TerminalHandle, TerminalView } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { runners } from '../_lib/runnersStore';\nimport { decodeChunk, sendRunnerInput, watchRunnerScreen } from '../_lib/screenApi';\n\nconst TAB_ID_PREFIX = 'runner-shell-';\n/** Keystrokes are gathered for this long and sent as one request. */\nconst FLUSH_MS = 30;\n\n/** The temporary tab holding one interactive runner's pane. */\nexport function runnerShellTab(run: RunnerRunView): TransientTab {\n const runId = run.id;\n return {\n id: `${TAB_ID_PREFIX}${runId}`,\n label: `shell · ${run.name}`,\n panel: (props: WebPluginSlotProps) => <RunnerShellPanel {...props} runId={runId} />,\n };\n}\n\n/**\n * An interactive runner's pane, attached.\n *\n * A real terminal emulator, fed the pane's own bytes. The runner's sink keeps\n * an unscrubbed copy of its output beside the log for exactly this: the log is\n * stripped of cursor movement so it stays worth grepping, which is precisely\n * what a terminal needs. Because these are the original bytes, colour, cursor\n * placement and full-screen programs all render the way they would in a shell.\n *\n * xterm is loaded when the tab opens rather than with the plugin. Most sessions\n * never attach to anything, and an emulator is not a small thing to carry.\n */\nexport function RunnerShellPanel({ sessionId, runId }: WebPluginSlotProps & { runId: string }) {\n const { runs } = useStore(runners.store, (state) => runners.select(state, sessionId));\n const run = runs.find((candidate) => candidate.id === runId);\n const [ended, setEnded] = useState(false);\n const [lost, setLost] = useState(false);\n const [ready, setReady] = useState(false);\n const terminal = useRef<TerminalHandle | null>(null);\n const pending = useRef<string[]>([]);\n const flush = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n if (sessionId === null || !ready) return;\n const watch = watchRunnerScreen(sessionId, runId, 0, {\n onEvent: (event) => {\n if (event.chunk !== '') terminal.current?.write(decodeChunk(event.chunk));\n if (event.ended === true) setEnded(true);\n },\n onLost: () => setLost(true),\n });\n return () => watch.close();\n }, [sessionId, runId, ready]);\n\n useEffect(\n () => () => {\n if (flush.current !== undefined) clearTimeout(flush.current);\n },\n [],\n );\n\n // Batched: a burst of typing is one request rather than one per character,\n // and the pane sees the keys in the order they were pressed either way.\n const send = (text: string): void => {\n if (sessionId === null) return;\n pending.current.push(text);\n if (flush.current !== undefined) return;\n flush.current = setTimeout(() => {\n const batch = pending.current.join('');\n pending.current = [];\n flush.current = undefined;\n if (batch !== '') void sendRunnerInput(sessionId, runId, batch);\n }, FLUSH_MS);\n };\n\n const live = run?.state === 'running' && !ended;\n\n return (\n <div data-testid=\"runner-shell-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex min-h-11 shrink-0 items-center gap-2.5 border-b border-doom-border-soft px-3 sm:h-11 sm:px-[26px]\">\n <span data-testid=\"runner-shell-name\" className=\"shrink-0 truncate text-sm font-bold text-doom-hi\">\n {run?.name ?? runId}\n </span>\n <StatusBadge tone={live ? 'running' : 'neutral'} data-testid=\"runner-shell-state\">\n {live ? 'attached' : 'detached'}\n </StatusBadge>\n <span className=\"min-w-0 flex-1\" />\n {lost ? (\n <span data-testid=\"runner-shell-lost\" className=\"shrink-0 text-2xs text-doom-red\">\n the connection to this pane dropped\n </span>\n ) : null}\n {live ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"runner-shell-interrupt\"\n title=\"send ctrl-c\"\n onClick={() => send('\\u0003')}\n className=\"px-2 text-2xs font-bold\"\n >\n ctrl-c\n </Button>\n ) : null}\n </div>\n\n <div className=\"relative min-h-0 flex-1 overflow-hidden bg-doom-panel-deep px-3 py-2 sm:px-[26px]\">\n <TerminalView\n ref={terminal}\n data-testid=\"runner-shell-terminal\"\n onData={(text) => send(text)}\n onReady={() => setReady(true)}\n />\n {ready ? null : (\n <p data-testid=\"runner-shell-reading\" className=\"text-xs text-doom-faint\">\n attaching…\n </p>\n )}\n </div>\n\n <div className=\"flex h-8 shrink-0 items-center border-t border-doom-border-soft px-3 sm:px-[26px]\">\n <span className=\"min-w-0 flex-1 truncate text-2xs text-doom-faint\">\n {live ? 'typing goes straight to the runner' : 'this runner is no longer accepting input'}\n </span>\n </div>\n </div>\n );\n}\n","import { ansiSpans } from '@agimon-ai/doompi-web-components';\nimport { useEffect, useState } from 'react';\n\nimport { fetchRunnerLog, followRunnerLog, type RunnerLogFollow } from '../_lib/logApi';\n\n/**\n * The newest line a running runner has written, for a row that has one line to\n * spend.\n *\n * The dock answers \"what is happening\", and a command that has not changed\n * since it started answers \"what was asked\". The last line of the log is the\n * closest thing to progress the cockpit can show without opening the log, so a\n * row that is still running trades its command for it.\n *\n * The line comes from the same two calls the log panel makes: read the tail,\n * then follow the stream from exactly where that read ended. Nothing new goes\n * on the wire, and a runner nobody is looking at is not followed, because the\n * stream lives and dies with the row.\n */\n\n/** Enough lines that a trailing blank, or a final progress redraw, is not the whole answer. */\nconst TAIL_REQUEST_LINES = 5;\n\n/**\n * The last line worth showing, as plain text.\n *\n * Blank lines are skipped, because a log that ends with a newline would\n * otherwise report nothing at all. Colour is dropped rather than rendered: the\n * row is nine pixels of faint text beside a name and a stop control, and a\n * green tick from someone else's palette in the middle of it reads as damage.\n */\nexport function plainTailLine(lines: readonly string[]): string | undefined {\n for (let index = lines.length - 1; index >= 0; index -= 1) {\n const text = ansiSpans(lines[index] ?? '')\n .map((span) => span.text)\n .join('')\n .trim();\n if (text.length > 0) return text;\n }\n return undefined;\n}\n\n/**\n * The newest log line of a running runner, or undefined for a runner that is\n * finished, unreachable, or has not written anything yet. The caller decides\n * what to show instead; this hook never invents a placeholder.\n */\nexport function useRunnerTail(sessionId: string | null, runId: string, running: boolean): string | undefined {\n // Kept with the run it belongs to, so a row that is reused for another\n // runner shows nothing rather than the previous runner's output.\n const [tail, setTail] = useState<{ runId: string; text: string } | undefined>(undefined);\n\n useEffect(() => {\n if (!running || sessionId === null) return;\n let live = true;\n let follow: RunnerLogFollow | undefined;\n const controller = new AbortController();\n void fetchRunnerLog(sessionId, runId, { lines: TAIL_REQUEST_LINES }, controller.signal).then((result) => {\n // A log the hub cannot serve leaves the row on its command, which is\n // still true; the log tab is where an unreachable runner is diagnosed.\n if (!live || 'error' in result) return;\n const initial = plainTailLine(result.slice.text.split('\\n'));\n if (initial !== undefined) setTail({ runId, text: initial });\n if (!result.slice.running) return;\n follow = followRunnerLog(sessionId, runId, result.slice.completeBytes, {\n onEvent: (event) => {\n const next = plainTailLine(event.lines);\n if (live && next !== undefined) setTail({ runId, text: next });\n },\n // A stream that stays down past its retries freezes the line at the\n // last one that arrived. The row is a glance, and saying so would cost\n // more room than the fact is worth.\n onLost: () => undefined,\n });\n });\n return () => {\n live = false;\n controller.abort();\n follow?.close();\n };\n }, [sessionId, runId, running]);\n\n return running && tail?.runId === runId ? tail.text : undefined;\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport {\n Button,\n Dot,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EmptyState,\n PlusIcon,\n} from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { formatRunnerUptime } from '../_lib/format';\nimport { RUNNER_SHELL_REQUEST } from '../_lib/launchLine';\nimport { requestRunnerStart, requestRunnerStop, runners } from '../_lib/runnersStore';\nimport { LaunchRunnerDialog } from './LaunchRunnerDialog';\nimport { runnerLogTab } from './RunnerLogPanel';\nimport { runnerShellTab } from './RunnerShellPanel';\nimport { useRunnerTail } from './runnerTail';\n\nconst TICK_MS = 10_000;\nconst SHELL_LAUNCH_TIMEOUT_MS = 30_000;\nconst RUNNERS_TAB_ID = 'runner-runs';\n\n/** The temporary tab the dock's runners group opens from its own name. */\nexport function runnersTab(): TransientTab {\n return { id: RUNNERS_TAB_ID, label: 'runners', panel: RunnersPanel };\n}\n\n/**\n * Everything this session has up, one card each.\n *\n * The dock's rail gives a runner one line, which is enough to notice it and\n * not enough to work with it. This is the same set with room: the command in\n * full, where it runs, and the last thing it said.\n *\n * Running runners only, the same rule the rail follows. A finished run leaves\n * the moment it exits, because this answers \"what is happening\"; its log stays\n * readable from its own tab, and `doom-runner list` is where history lives.\n */\nexport function RunnersPanel({ sessionId, sendSessionFrame, openTransientTab }: WebPluginSlotProps) {\n const session = useStore(runners.store, (state) => runners.select(state, sessionId));\n const running = session.runs.filter((run) => run.state === 'running');\n const [now, setNow] = useState(() => Date.now());\n const [launching, setLaunching] = useState(false);\n const shellBaseline = useRef<ReadonlySet<string> | null>(null);\n const shellLaunchTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Somewhere to start from, borrowed from whatever is already up.\n const defaultCwd = running[0]?.cwd;\n\n const startShell = (): void => {\n if (sessionId === null) return;\n shellBaseline.current = new Set(session.runs.map((run) => run.id));\n clearTimeout(shellLaunchTimeout.current);\n shellLaunchTimeout.current = setTimeout(() => {\n shellBaseline.current = null;\n }, SHELL_LAUNCH_TIMEOUT_MS);\n requestRunnerStart(sendSessionFrame, sessionId, RUNNER_SHELL_REQUEST);\n };\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n useEffect(() => {\n const baseline = shellBaseline.current;\n if (baseline === null) return;\n const shell = session.runs.find((run) => run.state === 'running' && run.interactive && !baseline.has(run.id));\n if (!shell) return;\n shellBaseline.current = null;\n clearTimeout(shellLaunchTimeout.current);\n openTransientTab(runnerShellTab(shell));\n }, [openTransientTab, session.runs]);\n\n useEffect(() => () => clearTimeout(shellLaunchTimeout.current), []);\n\n return (\n <div data-testid=\"runners-panel\" className=\"flex min-h-0 flex-1 flex-col overflow-auto\">\n <div className=\"flex min-h-11 shrink-0 items-center gap-2.5 border-b border-doom-border-soft px-3 sm:h-11 sm:px-[26px]\">\n <span className=\"text-sm font-bold text-doom-hi\">runners</span>\n <span data-testid=\"runners-panel-count\" className=\"text-xs text-doom-faint\">\n {running.length === 0 ? 'nothing running' : `${String(running.length)} running`}\n </span>\n <span className=\"min-w-0 flex-1\" />\n {sessionId === null ? null : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid=\"runners-panel-launch\"\n title=\"launch a runner\"\n aria-label=\"launch a runner\"\n className=\"text-doom-faint hover:text-doom-hi\"\n >\n <PlusIcon className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" data-testid=\"runners-panel-launch-menu\">\n <DropdownMenuItem data-testid=\"runners-panel-start-shell\" onSelect={startShell}>\n start shell\n </DropdownMenuItem>\n <DropdownMenuItem data-testid=\"runners-panel-run-command\" onSelect={() => setLaunching(true)}>\n run command\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {running.length === 0 ? (\n <EmptyState\n data-testid=\"runners-panel-empty\"\n className=\"px-4 py-8\"\n title=\"nothing running\"\n description=\"a command that outlives its turn moves to a runner and appears here while it works.\"\n >\n {sessionId === null ? null : (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"runners-panel-launch-empty\"\n onClick={() => setLaunching(true)}\n className=\"px-2 text-2xs font-bold\"\n >\n launch a runner\n </Button>\n )}\n </EmptyState>\n ) : (\n <div\n data-testid=\"runners-panel-grid\"\n className=\"grid grid-cols-1 gap-2.5 px-3 py-3 sm:grid-cols-2 sm:px-[26px] xl:grid-cols-3\"\n >\n {running.map((run) => (\n <RunnerCard\n key={run.id}\n run={run}\n now={now}\n stopRequested={session.stopRequested.includes(run.id)}\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n openTransientTab={openTransientTab}\n />\n ))}\n </div>\n )}\n\n {launching && sessionId !== null ? (\n <LaunchRunnerDialog\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n {...(defaultCwd === undefined ? {} : { defaultCwd })}\n onClose={() => setLaunching(false)}\n />\n ) : null}\n </div>\n );\n}\n\n/**\n * One running runner. Its own component because the tail line is a\n * subscription per card, and a card that leaves the grid closes it.\n */\nfunction RunnerCard({\n run,\n now,\n stopRequested,\n sessionId,\n sendSessionFrame,\n openTransientTab,\n}: {\n run: RunnerRunView;\n now: number;\n stopRequested: boolean;\n} & Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame' | 'openTransientTab'>) {\n const tail = useRunnerTail(sessionId, run.id, run.state === 'running');\n return (\n <div\n data-testid={`runners-card-${run.id}`}\n className=\"flex min-w-0 flex-col gap-1.5 rounded-md border border-doom-border-soft bg-doom-panel p-2.5\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Dot tone=\"yellow\" pulse />\n <span className=\"min-w-0 flex-1 truncate text-sm font-bold text-doom-hi\">{run.name}</span>\n <span className=\"shrink-0 text-2xs text-doom-faint\">\n {formatRunnerUptime(run.startedAt, now)}\n {run.interactive ? ' · tty' : ''}\n </span>\n </div>\n\n <span className=\"min-w-0 break-all text-2xs leading-normal text-doom-dim\">{run.command}</span>\n <span className=\"min-w-0 truncate text-2xs text-doom-faint\">{run.cwd}</span>\n\n {/* What it is doing, falling back to what it was asked to do until the\n first line of output arrives. */}\n <span\n data-testid={`runners-card-detail-${run.id}`}\n data-detail={tail === undefined ? 'command' : 'tail'}\n className=\"min-w-0 truncate text-2xs text-doom-faint\"\n >\n {tail ?? '…'}\n </span>\n\n <div className=\"flex items-center gap-1.5 pt-0.5\">\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid={`runners-card-log-${run.id}`}\n title=\"open this runner's log\"\n onClick={() => openTransientTab(runnerLogTab(run))}\n className=\"px-2 text-2xs font-bold\"\n >\n log\n </Button>\n {/* Only an interactive run has a pane to attach to. A plain one has\n nothing to type into, so it is not offered. */}\n {run.interactive && sessionId !== null ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid={`runners-card-shell-${run.id}`}\n title=\"attach to this runner's terminal\"\n onClick={() => openTransientTab(runnerShellTab(run))}\n className=\"px-2 text-2xs font-bold\"\n >\n shell\n </Button>\n ) : null}\n <span className=\"min-w-0 flex-1\" />\n {sessionId !== null ? (\n <Button\n variant={stopRequested ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid={`runners-card-stop-${run.id}`}\n disabled={stopRequested}\n title={\n stopRequested ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n onClick={() => requestRunnerStop(sendSessionFrame, sessionId, run.id)}\n className=\"px-2 text-2xs font-bold\"\n >\n {stopRequested ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n","import { defineActivityGroup } from '@agimon-ai/doompi-core/web';\n\nimport { runnersTab } from '../_components/RunnersPanel';\nimport { runnerActivitySource } from '../_lib/runnersStore';\nexport default defineActivityGroup({\n keys: 'r l',\n statusKey: 'doom-runner-runners',\n activeSource: runnerActivitySource,\n transientTab: runnersTab,\n order: 20,\n});\n","import { defineChannelFile } from '@agimon-ai/doompi-core/web';\n\nimport { runnerRunsChannel } from '../_lib/runnersStore';\nexport default defineChannelFile(runnerRunsChannel);\n","import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button, Dot } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../../types/webRunners';\nimport { runnerLogTab } from '../../_components/RunnerLogPanel';\nimport { runnersTab } from '../../_components/RunnersPanel';\nimport { useRunnerTail } from '../../_components/runnerTail';\nimport { formatRunnerUptime } from '../../_lib/format';\nimport { requestRunnerStop, runners } from '../../_lib/runnersStore';\n\nconst TICK_MS = 10_000;\n\n/**\n * The runners group's body in the activity dock: what this session has up\n * right now.\n *\n * Only running runners are listed. A finished one leaves the moment it exits,\n * because the dock answers \"what is happening\", and a rail that keeps ten\n * completed playwright calls around buries the one command still working. The\n * runtime keeps every record; `doom-runner list` and the run's own log tab are\n * where a finished run is read.\n */\nexport function RunnersActivitySection({ sessionId, sendSessionFrame, openTransientTab }: WebPluginSlotProps) {\n const session = useStore(runners.store, (state) => runners.select(state, sessionId));\n const running = session.runs.filter((run) => run.state === 'running');\n const [now, setNow] = useState(() => Date.now());\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n if (running.length === 0) {\n return (\n <div className=\"flex items-center gap-2 px-1\">\n <p data-testid=\"activity-summary-runners\" className=\"px-1 text-xs text-doom-faint\">\n idle\n </p>\n {sessionId === null ? null : (\n <Button\n variant=\"link\"\n size=\"xs\"\n data-testid=\"activity-runner-launch\"\n className=\"px-0\"\n onClick={() => openTransientTab(runnersTab())}\n >\n launch a runner\n </Button>\n )}\n </div>\n );\n }\n\n return (\n <div data-testid=\"activity-runner-runs\" className=\"flex flex-col gap-0.5\">\n {running.map((run: RunnerRunView) => (\n <RunnerRow\n key={run.id}\n run={run}\n now={now}\n stopRequested={session.stopRequested.includes(run.id)}\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n openTransientTab={openTransientTab}\n />\n ))}\n </div>\n );\n}\n\n/**\n * One running runner: its name, how long it has been up, a stop control, and\n * one line of what it is doing. Its own component because the tail line is a\n * subscription per row, and a row that scrolls out of the dock closes it.\n */\nfunction RunnerRow({\n run,\n now,\n stopRequested,\n sessionId,\n sendSessionFrame,\n openTransientTab,\n}: {\n run: RunnerRunView;\n now: number;\n stopRequested: boolean;\n} & Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame' | 'openTransientTab'>) {\n const tail = useRunnerTail(sessionId, run.id, run.state === 'running');\n return (\n <Button\n variant=\"ghost\"\n size=\"card\"\n data-testid={`activity-runner-${run.id}`}\n data-runner-tone=\"running\"\n title=\"open this runner's log\"\n onClick={() => {\n if (sessionId === null) return;\n openTransientTab(runnerLogTab(run));\n }}\n className=\"min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel\"\n >\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <Dot tone=\"yellow\" pulse />\n <span className=\"min-w-0 flex-1 truncate text-left text-xs font-bold text-doom-hi\">{run.name}</span>\n <span className=\"shrink-0 text-2xs text-doom-faint\">\n {formatRunnerUptime(run.startedAt, now)}\n {run.interactive ? ' · tty' : ''}\n </span>\n {sessionId !== null ? (\n <Button\n asChild\n variant={stopRequested ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid={`activity-runner-stop-${run.id}`}\n data-stopping={stopRequested}\n title={\n stopRequested ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n className=\"px-1.5 text-2xs font-bold\"\n >\n {/* The row is already a button, so the stop control lends it\n the primitive's styling rather than nesting a second one. */}\n <span\n role=\"button\"\n tabIndex={stopRequested ? -1 : 0}\n aria-disabled={stopRequested}\n onClick={(event) => {\n event.stopPropagation();\n if (!stopRequested) requestRunnerStop(sendSessionFrame, sessionId, run.id);\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n event.stopPropagation();\n if (!stopRequested) requestRunnerStop(sendSessionFrame, sessionId, run.id);\n }}\n >\n {stopRequested ? 'stopping…' : 'stop'}\n </span>\n </Button>\n ) : null}\n </span>\n {/* What it is doing, falling back to what it was asked to do until the\n first line of output arrives. */}\n <span\n data-testid={`activity-runner-detail-${run.id}`}\n data-detail={tail === undefined ? 'command' : 'tail'}\n className=\"truncate pl-3 text-left text-2xs text-doom-faint\"\n >\n {tail ?? run.command}\n </span>\n </Button>\n );\n}\n","import { defineFill } from '@agimon-ai/doompi-core/web';\n\nimport { RunnersActivitySection } from './_components/RunnersActivitySection';\nexport default defineFill({ component: RunnersActivitySection });\n","/**\n * Pure view logic for the bash tool card, the browser counterpart of\n * src/tui/bashRender.ts. The plugin may reach only its own files and\n * src/types, so the few TUI helpers it mirrors are copied here rather than\n * imported, and every input is the wire JSON of a tool_execution frame.\n */\n\nconst ELLIPSIS = '…';\n/** Spaced so the join never glues two words into one unreadable token. */\nconst GAP = ' … ';\n/** Wide enough to keep a realistic command intact, short enough to stay on a few rows. */\nconst MAX_COMMAND_LENGTH = 160;\n/** Lines of log kept in the collapsed result, before the card is expanded. */\nconst COLLAPSED_TAIL_LINES = 12;\n/** Live output is unbounded while a command runs, so the running state keeps only a window. */\nconst STREAM_TAIL_LINES = 12;\nconst BYTES_PER_KB = 1024;\nconst BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;\n/** The user's home on the two platforms the runner ships for; the browser cannot ask the OS. */\nconst HOME_PATH = /(?<![\\w.-])(?:\\/Users|\\/home)\\/[^/\\s]+(?=\\/|\\s|$)/g;\n\n/** The bash tool's arguments as the frame carries them (src/schemas/bashTool.ts). */\nexport interface BashCallArgs {\n command?: unknown;\n timeout?: unknown;\n background?: unknown;\n interactive?: unknown;\n name?: unknown;\n}\n\n/** What formatRunResult reports, read only by the renderers. */\nexport interface BashResultDetails {\n id?: string;\n runner?: string;\n exitCode?: number | null;\n logPath?: string;\n fileSize?: number;\n lines?: number;\n timedOut?: boolean;\n promoted?: boolean;\n reason?: string;\n tail?: string;\n tailLines?: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Narrows the result details to the fields the card reads; anything else is ignored. */\nexport function bashResultDetails(details: unknown): BashResultDetails {\n if (!isRecord(details)) return {};\n const out: BashResultDetails = {};\n if (typeof details.id === 'string') out.id = details.id;\n if (typeof details.runner === 'string') out.runner = details.runner;\n if (typeof details.exitCode === 'number' || details.exitCode === null) out.exitCode = details.exitCode;\n if (typeof details.logPath === 'string') out.logPath = details.logPath;\n if (typeof details.fileSize === 'number') out.fileSize = details.fileSize;\n if (typeof details.lines === 'number') out.lines = details.lines;\n if (details.timedOut === true) out.timedOut = true;\n if (details.promoted === true) out.promoted = true;\n if (typeof details.reason === 'string') out.reason = details.reason;\n if (typeof details.tail === 'string') out.tail = details.tail;\n if (typeof details.tailLines === 'number') out.tailLines = details.tailLines;\n return out;\n}\n\n/** `/Users/me/x` becomes `~/x`. Absolute home paths are what push these lines onto extra rows. */\nexport function abbreviateHome(text: string): string {\n return text.replace(HOME_PATH, '~');\n}\n\n/** Collapse a heredoc or multi-line script to one scannable line plus a count. */\nexport function collapseCommand(command: string): string {\n const lines = command\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n const first = lines[0] ?? '';\n return lines.length > 1 ? `${first} ${ELLIPSIS} +${lines.length - 1} lines` : first;\n}\n\n/** Drop the middle rather than the end, so a long pipeline keeps both its source and its sink. */\nexport function truncateMiddle(text: string, max: number = MAX_COMMAND_LENGTH): string {\n if (text.length <= max) return text;\n const keep = max - GAP.length;\n const head = Math.ceil(keep / 2);\n let start = text.slice(0, head);\n let end = text.slice(text.length - (keep - head));\n\n // Snap both cuts to a token boundary, so neither side ends mid-word. Each is\n // only moved when a boundary exists in the half nearest the cut, otherwise a\n // single long argument would swallow the whole budget.\n const lastSpace = start.lastIndexOf(' ');\n if (lastSpace > start.length / 2) start = start.slice(0, lastSpace);\n const firstSpace = end.indexOf(' ');\n if (firstSpace >= 0 && firstSpace < end.length / 2) end = end.slice(firstSpace + 1);\n\n return `${start}${GAP}${end}`;\n}\n\nexport function formatBashCommand(command: string): string {\n return truncateMiddle(abbreviateHome(collapseCommand(command)));\n}\n\n/** Modifiers worth seeing at a glance, in the widget's `·` idiom. */\nexport function formatBashFlags(args: BashCallArgs): string[] {\n const flags: string[] = [];\n if (args.background === true) flags.push('bg');\n if (args.interactive === true) flags.push('tty');\n if (typeof args.timeout === 'number') flags.push(`${args.timeout}s`);\n if (typeof args.name === 'string' && args.name.length > 0) flags.push(args.name);\n return flags;\n}\n\n/** Human-readable byte count, matching how pi reports output sizes. */\nexport function formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\n/** The metadata footer, collapsed from five lines to one. */\nexport function formatResultSummary(details: BashResultDetails): string {\n const parts: string[] = [];\n if (details.lines !== undefined) parts.push(`${details.lines.toLocaleString('en-US')} lines`);\n if (details.fileSize !== undefined) parts.push(formatSize(details.fileSize));\n if (details.runner !== undefined) parts.push(details.runner);\n return parts.join(' · ');\n}\n\n/** Last `max` lines, so a chatty command cannot push the transcript off screen. */\nfunction lastLines(text: string, max: number): string[] {\n const all = text.split('\\n');\n while (all.length > 0 && all[all.length - 1]?.trim() === '') all.pop();\n return all.length <= max ? all : all.slice(-max);\n}\n\nexport type BashStatusTone = 'running' | 'ok' | 'error' | 'background';\n\nexport interface BashStatusLine {\n glyph: string;\n tone: BashStatusTone;\n text: string;\n}\n\n/** What the result body shows: the log lines, then the one-line status under them. */\nexport interface BashResultView {\n lines: string[];\n status: BashStatusLine | null;\n /** Lines the collapsed view is hiding, so the card can say so. */\n hidden: number;\n}\n\nexport interface BashResultInput {\n details: unknown;\n /** The result's text blocks joined, all a thrown failure leaves behind. */\n output: string;\n expanded: boolean;\n isPartial: boolean;\n isError: boolean;\n}\n\nexport function bashResultView(input: BashResultInput): BashResultView {\n const details = bashResultDetails(input.details);\n\n // Running. Details do not exist yet, so the streamed text is the only source,\n // and it is bounded here because it grows without limit while the command runs.\n if (input.isPartial) {\n return {\n lines: lastLines(input.output, STREAM_TAIL_LINES),\n status: { glyph: '◐', tone: 'running', text: 'running' },\n hidden: 0,\n };\n }\n\n // Failed. formatRunResult throws on a bad exit, so Pi hands back the error\n // text with no details at all; without this the block would show a bare tick.\n if (input.isError) {\n const all = lastLines(input.output, Number.MAX_SAFE_INTEGER);\n const lines = input.expanded ? all : all.slice(-COLLAPSED_TAIL_LINES);\n const summary = formatResultSummary(details);\n return {\n lines,\n status: { glyph: '✗', tone: 'error', text: summary.length > 0 ? summary : 'failed' },\n hidden: all.length - lines.length,\n };\n }\n\n if (details.promoted === true) {\n const label = details.runner ?? details.id ?? 'runner';\n const suffix = details.id === undefined ? '' : ` · ${details.id}`;\n return { lines: [], status: { glyph: '●', tone: 'background', text: `${label}${suffix} · background` }, hidden: 0 };\n }\n\n const all = lastLines(details.tail ?? '', Number.MAX_SAFE_INTEGER);\n const lines = input.expanded ? all : all.slice(-COLLAPSED_TAIL_LINES);\n // An absent exit code means the runner never reported one, which is not a failure.\n const exited = details.exitCode !== undefined && details.exitCode !== null;\n const failed = details.timedOut === true || (exited && details.exitCode !== 0);\n\n const meta: string[] = [];\n if (details.timedOut === true) meta.push('timed out');\n else if (failed) meta.push(`exit ${details.exitCode}`);\n const summary = formatResultSummary(details);\n if (summary.length > 0) meta.push(summary);\n\n const text = meta.length > 0 ? meta.join(' · ') : failed ? 'failed' : lines.length === 0 ? 'done' : '';\n return {\n lines,\n status: text.length > 0 ? { glyph: failed ? '✗' : '✓', tone: failed ? 'error' : 'ok', text } : null,\n hidden: all.length - lines.length,\n };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/doompi-core/web';\nimport {\n AnsiText,\n Button,\n FileIcon,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n type StatusTone,\n SyntaxText,\n toolTone,\n} from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { runnerLogTab } from '../../_components/RunnerLogPanel';\nimport { requestRunnerStop, runners } from '../../_lib/runnersStore';\nimport {\n type BashStatusTone,\n bashResultDetails,\n bashResultView,\n formatBashCommand,\n formatBashFlags,\n} from '../_lib/bashToolFormat';\n\nconst STATUS_TONE: Record<BashStatusTone, StatusTone> = {\n running: 'running',\n ok: 'ok',\n error: 'error',\n background: 'info',\n};\n\n/**\n * The bash tool's timeline item, the web half of renderBashCall and\n * renderBashResult. Collapsed it is a single header row: the tool name, the\n * command shortened to one line, its modifiers, and the outcome badge, so a\n * transcript of many calls stays scannable. Expanding it opens the body: the\n * command exactly as it was run, then the whole tail the frame carries, each\n * in its own scroll box so one chatty command cannot own the viewport. A\n * command promoted to a background runner offers the same stop the activity\n * dock does, for as long as the runners channel reports it running.\n */\nexport function BashToolMessage({\n sessionId,\n args,\n result,\n output,\n running,\n isError,\n sendSessionFrame,\n openTransientTab,\n}: ToolMessageRenderProps) {\n const details = bashResultDetails(result?.details);\n const runnerId = details.promoted === true ? details.id : undefined;\n // Every bash call is a runner and writes a log, promoted or not, so the log\n // control looks the run up by its own id; only stopping needs a promoted one.\n const run = useStore(runners.store, (state) =>\n details.id === undefined\n ? undefined\n : runners.select(state, sessionId).runs.find((entry) => entry.id === details.id),\n );\n const stopping = useStore(\n runners.store,\n (state) => runnerId !== undefined && runners.select(state, sessionId).stopRequested.includes(runnerId),\n );\n const rawCommand = typeof args.command === 'string' ? args.command : '';\n const command = formatBashCommand(rawCommand);\n const flags = formatBashFlags(args);\n const stoppable = sessionId !== null && runnerId !== undefined && run !== undefined && run.exit === undefined;\n\n return (\n <MessageItem tone={toolTone({ running, isError })} expandable>\n {({ expanded }) => {\n // Nothing is shown until the card is open, so the body always reads the\n // unclipped view; the collapsed state has no lines to bound.\n const view = bashResultView({\n details: result?.details,\n output,\n expanded: true,\n isPartial: running,\n isError,\n });\n return (\n <>\n <MessageItemHeader title=\"bash\">\n <span data-testid=\"tool-call-bash\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"min-w-0 flex-1 truncate font-mono text-doom-text\">{command}</span>\n {flags.length > 0 ? (\n <span className=\"hidden shrink-0 text-doom-faint sm:inline\">· {flags.join(' · ')}</span>\n ) : null}\n </span>\n {/* The log and the stop live in the header, not the body: a\n collapsed row still has to reach the whole log and still has\n to be able to stop a run it promoted to the background. */}\n {run !== undefined && sessionId !== null ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"tool-result-bash-open-log\"\n title=\"open this runner's full log\"\n onClick={(event) => {\n event.stopPropagation();\n openTransientTab(runnerLogTab(run));\n }}\n className=\"shrink-0 gap-1 px-1.5 text-2xs font-bold\"\n >\n <FileIcon className=\"h-2.5 w-2.5\" />\n log\n </Button>\n ) : null}\n {stoppable ? (\n <Button\n size=\"xs\"\n variant={stopping ? 'outline' : 'danger-outline'}\n disabled={stopping}\n data-testid=\"tool-result-bash-stop\"\n data-stopping={stopping}\n title={\n stopping ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n onClick={(event) => {\n event.stopPropagation();\n if (sessionId !== null && runnerId !== undefined)\n requestRunnerStop(sendSessionFrame, sessionId, runnerId);\n }}\n className=\"shrink-0 px-1.5 text-2xs font-bold\"\n >\n {stopping ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </MessageItemHeader>\n {expanded ? (\n <MessageItemBody data-testid=\"tool-result-bash\" className=\"flex flex-col gap-2\">\n {/* The header had to shorten the command to one row; this is\n the command as it was actually run, wrapped rather than\n clipped and coloured as the shell it is. */}\n {rawCommand.length > 0 ? (\n <SyntaxText\n data-testid=\"tool-result-bash-command\"\n grammar=\"shell\"\n text={rawCommand}\n className=\"max-h-40 overflow-auto text-doom-text\"\n />\n ) : null}\n {/* The tail arrives with the colours the command wrote: the\n log keeps them, and only the model's copy is flattened. */}\n {view.lines.length > 0 ? (\n <AnsiText\n data-testid=\"tool-result-bash-output\"\n text={view.lines.join('\\n')}\n className=\"max-h-96 overflow-auto whitespace-pre-wrap break-words [overflow-wrap:anywhere] border-doom-border-soft border-t pt-2 font-mono text-doom-dim\"\n />\n ) : null}\n {view.status ? (\n <MessageItemStatus\n data-testid=\"tool-result-bash-status\"\n tone={STATUS_TONE[view.status.tone]}\n glyph={view.status.glyph}\n className=\"min-w-0\"\n >\n <span className=\"break-words\">{view.status.text}</span>\n </MessageItemStatus>\n ) : null}\n {details.logPath !== undefined ? (\n // The tail is bounded by the runner; the whole log only ever exists on\n // disk, so the item names the file rather than pretending this is all.\n <span data-testid=\"tool-result-bash-log\" className=\"min-w-0 break-all text-doom-faint\">\n full log · <span className=\"text-doom-dim\">{details.logPath}</span>\n </span>\n ) : null}\n </MessageItemBody>\n ) : null}\n </>\n );\n }}\n </MessageItem>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/doompi-core/web';\n\nimport { BashToolMessage } from './_components/BashToolMessage';\nexport default defineToolRenderer({ message: BashToolMessage });\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport activityGroupRunners from '../src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web';\nimport channelRunnerRuns from '../src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web';\nimport fillRunners from '../src/extensions/workspaces/sessions/(frontend)/fill/Runners.activity.runners.web';\nimport toolBash from '../src/extensions/workspaces/sessions/(frontend)/tool/bash.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'runner',\n session: {\n get activityGroups(): WebPluginContributions['activityGroups'] { return [via({ name: 'runners' }, activityGroupRunners)]; },\n get channels(): WebPluginContributions['channels'] { return [via({ channel: 'runner_runs' }, channelRunnerRuns)]; },\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'activity.runners', id: 'runners' }, fillRunners)]; },\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['bash'] }, toolBash)]; },\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web.mjs","names":["isRecord","routes","isRecord","MAX_RETRIES","RETRY_BASE_MS","TICK_MS","TAB_ID_PREFIX","BYTES_PER_KB","BYTES_PER_MB","formatSize","isRecord","TICK_MS","activityGroupRunners","channelRunnerRuns","fillRunners","toolBash"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/_lib/format.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts","../../src/constants/webRunners.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/runnersStore.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/LaunchRunnerDialog.tsx","../../src/constants/webRunnerLog.ts","../../src/types/webRunnerLog.ts","../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/logApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnerLogPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_lib/screenApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnerShellPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/runnerTail.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/RunnersActivitySection.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/Runners.activity.runners.web.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/bashToolFormat.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/BashToolMessage.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/bash.web.tsx","../../generated/web.ts"],"sourcesContent":["/**\n * Local copy of the runtime's formatUptime (src/services/bashResult/index.ts):\n * the web plugin may reach only src/types, so the one helper it needs lives here.\n */\nexport function formatRunnerUptime(startedAt: string, now: number): string {\n const started = Date.parse(startedAt);\n if (Number.isNaN(started)) return 'unknown';\n const seconds = Math.max(0, Math.floor((now - started) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;\n}\n\n/** A bounded log view: what to render, and what the bound left out. */\nexport interface LogView {\n lines: string[];\n /** Lines dropped off the top to stay within the bound. */\n hidden: number;\n}\n\nexport interface LogViewOptions {\n /**\n * Drop the slice's last line because it is a fragment the follow stream will\n * deliver again, whole. Set only while following: a finished run's last line\n * may legitimately lack a newline, and dropping it would hide real output.\n */\n dropPartialTail?: boolean;\n}\n\n/**\n * The lines a log view shows: the slice's text, then whatever the follow\n * stream appended, bounded to the newest `max`.\n *\n * A log file ends with a newline, and the reader hands its text back with that\n * newline intact, so a naive split reports one blank line more than the file\n * has. Dropping exactly one trailing empty entry keeps the count the reader\n * sees equal to the count the server reported.\n */\nexport function logViewLines(\n text: string,\n appended: readonly string[],\n max: number,\n options: LogViewOptions = {},\n): LogView {\n const split = text.split('\\n');\n if (split.length > 0 && split[split.length - 1] === '') split.pop();\n if (options.dropPartialTail === true && split.length > 0) split.pop();\n const all = [...split, ...appended];\n const lines = all.slice(-max);\n return { lines, hidden: all.length - lines.length };\n}\n\n/**\n * Absolute 1-based numbers for a tail, counted back from the last line.\n *\n * A tail is contiguous, so only its end has to be known. Grep results are not\n * contiguous and carry their own numbers from the reader instead.\n */\nexport function tailLineNumbers(lastLineNumber: number, count: number): number[] {\n const first = Math.max(1, lastLineNumber - count + 1);\n return Array.from({ length: count }, (_, index) => first + index);\n}\n\n/**\n * Whether the view is actually following a growing log.\n *\n * Three things must hold: the reader asked to follow, no query is set (a\n * filtered view is a snapshot of the whole file, which a stream cannot\n * assemble a chunk at a time), and the runner is still writing. Without the\n * last one a finished run would claim to be tailing a file nothing will ever\n * append to.\n */\nexport function isFollowingLive(following: boolean, filtering: boolean, running: boolean): boolean {\n return following && !filtering && running;\n}\n","/**\n * The `/runners start` line the launch form sends.\n *\n * WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:\n * Cockpit code is compiled into the host bundle and may import only this\n * package's own web and type modules, so it cannot share the session's parser\n * in src/services. The two are pinned together by a test that parses what this\n * writes, which is the only guarantee that matters: the line has to survive\n * the trip.\n */\n\nexport const RUNNERS_START_LINE = '/runners start';\n/** A persistent login shell for the direct shell action in Runner Space. */\nexport const RUNNER_SHELL_COMMAND = 'exec \"${SHELL:-/bin/bash}\" -l';\n/** Ends the options and begins the command; the command may hold `=` and flags of its own. */\nexport const RUNNERS_COMMAND_SEPARATOR = '--';\n\nexport interface RunnerLaunchRequest {\n command: string;\n cwd?: string;\n name?: string;\n interactive?: boolean;\n}\n\n/** The no-form launch used when a reader asks for a shell rather than a command. */\nexport const RUNNER_SHELL_REQUEST: RunnerLaunchRequest = {\n command: RUNNER_SHELL_COMMAND,\n name: 'shell',\n interactive: true,\n};\n\n/** Quotes a value that carries whitespace, which is how a value survives tokenizing. */\nfunction quote(value: string): string {\n return /\\s/u.test(value) ? `\"${value}\"` : value;\n}\n\nexport function runnerLaunchLine(request: RunnerLaunchRequest): string {\n const cwd = request.cwd?.trim() ?? '';\n const name = request.name?.trim() ?? '';\n const pairs = [\n ...(name === '' ? [] : [`name=${quote(name)}`]),\n ...(cwd === '' ? [] : [`cwd=${quote(cwd)}`]),\n ...(request.interactive === true ? ['interactive=true'] : []),\n ];\n // The separator is always written, even with no options before it, so the\n // command is never re-read as a `key=value` pair.\n return [RUNNERS_START_LINE, ...pairs, RUNNERS_COMMAND_SEPARATOR, request.command.trim()].join(' ');\n}\n\n/** What the form will not let a reader send, worded the way the session would answer. */\nexport function launchProblems(request: RunnerLaunchRequest): string[] {\n const problems: string[] = [];\n if (request.command.trim() === '') problems.push('A runner needs a command to run.');\n return problems;\n}\n","export const RUNNER_RUNS_TYPE = 'runner_runs';\n","import { defineSessionStore, type SessionFrameSender } from '@agimon-ai/doompi-core/web';\n\nimport { RUNNER_RUNS_TYPE } from '../../../../../constants/webRunners';\nimport { type RunnerRunView } from '../../../../../types/webRunners';\nimport { type RunnerLaunchRequest, runnerLaunchLine } from './launchLine';\n\n/** The runtime's slash verb that stops one runner headlessly. */\nconst STOP_COMMAND = '/runners stop';\n\n/** One session's record: the hub's last report plus what this page asked for. */\nexport interface RunnersSession {\n /** The runners the hub last reported; presented order preserved. */\n runs: RunnerRunView[];\n /** Runners a stop was asked for and that have not yet reported an exit. */\n stopRequested: string[];\n}\n\nexport const runners = defineSessionStore<RunnersSession>({ runs: [], stopRequested: [] });\n\n/** The runner channel keeps the dock's busy state aligned with its rendered run list. */\nexport const runnerActivitySource = {\n subscribe(listener: () => void) {\n const subscription = runners.store.subscribe(listener);\n return () => subscription.unsubscribe();\n },\n isActive(sessionId: string | null) {\n return runners.select(runners.store.state, sessionId).runs.some((run) => run.state === 'running');\n },\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Asks the runtime to stop a runner through the same slash command the TUI's\n * Runner Space uses. The request is remembered until the runner's record\n * reports an exit; the runtime, not this click, decides when that is.\n */\nexport function requestRunnerStop(send: SessionFrameSender, sessionId: string, id: string): void {\n send(sessionId, { type: 'prompt', message: `${STOP_COMMAND} ${id}` });\n runners.update(sessionId, (current) => ({\n ...current,\n stopRequested: [...current.stopRequested.filter((known) => known !== id), id],\n }));\n}\n\n/**\n * Asks the runtime to start a runner, through the same prompt frame the stop\n * request uses and the same way agents and workflows launch their own work.\n *\n * Nothing is recorded as pending here. The runtime names the run, and it\n * arrives in the feed on its own; guessing at an id before it exists would\n * only have to be reconciled later.\n */\nexport function requestRunnerStart(send: SessionFrameSender, sessionId: string, request: RunnerLaunchRequest): void {\n send(sessionId, { type: 'prompt', message: runnerLaunchLine(request) });\n}\n\nexport interface RunnerRunsPayload {\n runs: RunnerRunView[];\n}\n\n/** The plugin's session data channel: 'runner_runs' payloads into the store. */\nexport const runnerRunsChannel = runners.channel<RunnerRunsPayload>({\n channel: RUNNER_RUNS_TYPE,\n parse(input) {\n if (!isRecord(input) || !Array.isArray(input.runs)) return null;\n return { runs: input.runs.filter(isRecord) as unknown as RunnerRunView[] };\n },\n reduce(current, { runs }) {\n // A stop request is spent once the runner reports an exit or leaves the feed.\n const stillRunning = new Set(runs.filter((run) => run.state === 'running').map((run) => run.id));\n return { runs, stopRequested: current.stopRequested.filter((id) => stillRunning.has(id)) };\n },\n});\n","import type { SessionFrameSender } from '@agimon-ai/doompi-core/web';\nimport {\n Button,\n Checkbox,\n Dialog,\n DialogBody,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Textarea,\n} from '@agimon-ai/doompi-web-components';\nimport { useState } from 'react';\n\nimport { launchProblems, type RunnerLaunchRequest, runnerLaunchLine } from '../_lib/launchLine';\nimport { requestRunnerStart } from '../_lib/runnersStore';\n\nconst COMMAND_ROWS = 3;\nconst INTERACTIVE_FIELD_ID = 'runner-launch-interactive-field';\n\nfunction FieldLabel({ children }: { children: string }) {\n return <span className=\"text-2xs font-bold tracking-widest text-doom-faint\">{children}</span>;\n}\n\n/**\n * Starts one runner by hand: the command, where it runs, what to call it, and\n * whether it needs a terminal, then the exact `/runners start` line the\n * session will parse.\n *\n * The line is shown rather than hidden. This sends a shell command through the\n * session, and a reader is entitled to see exactly what they are about to run\n * before they run it.\n */\nexport function LaunchRunnerDialog({\n sessionId,\n sendSessionFrame,\n defaultCwd,\n onClose,\n}: {\n sessionId: string;\n sendSessionFrame: SessionFrameSender;\n defaultCwd?: string;\n onClose: () => void;\n}) {\n const [command, setCommand] = useState('');\n const [cwd, setCwd] = useState(defaultCwd ?? '');\n const [name, setName] = useState('');\n const [interactive, setInteractive] = useState(false);\n\n const request: RunnerLaunchRequest = { command, cwd, name, interactive };\n const problems = launchProblems(request);\n const line = runnerLaunchLine(request);\n\n const launch = (): void => {\n if (problems.length > 0) return;\n requestRunnerStart(sendSessionFrame, sessionId, request);\n onClose();\n };\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <DialogContent width=\"lg\" data-testid=\"runner-launch-dialog\" aria-describedby={undefined}>\n <DialogHeader>\n <DialogTitle data-testid=\"runner-launch-title\">launch a runner</DialogTitle>\n </DialogHeader>\n <DialogBody>\n <div className=\"flex flex-col gap-1\">\n <FieldLabel>COMMAND</FieldLabel>\n <Textarea\n data-testid=\"runner-launch-command\"\n rows={COMMAND_ROWS}\n value={command}\n placeholder=\"pnpm test\"\n onChange={(event) => setCommand(event.target.value)}\n className=\"text-sm\"\n />\n </div>\n\n <div className=\"flex flex-col gap-2.5 sm:flex-row\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <FieldLabel>CWD</FieldLabel>\n <Input\n size=\"sm\"\n data-testid=\"runner-launch-cwd\"\n value={cwd}\n placeholder=\"the session's own directory\"\n onChange={(event) => setCwd(event.target.value)}\n className=\"text-xs\"\n />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <FieldLabel>NAME</FieldLabel>\n <Input\n size=\"sm\"\n data-testid=\"runner-launch-name\"\n value={name}\n placeholder=\"chosen for you when empty\"\n onChange={(event) => setName(event.target.value)}\n className=\"text-xs\"\n />\n </div>\n </div>\n\n {/* Paired by id rather than by nesting: the checkbox renders as a\n button, which a wrapping label does not associate with. */}\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id={INTERACTIVE_FIELD_ID}\n data-testid=\"runner-launch-interactive\"\n checked={interactive}\n onCheckedChange={(checked) => setInteractive(checked === true)}\n />\n <label htmlFor={INTERACTIVE_FIELD_ID} className=\"text-xs text-doom-dim\">\n needs a terminal, for a command that prompts (requires rmux or tmux)\n </label>\n </div>\n\n <div className=\"flex flex-col gap-1 rounded-md border border-doom-border bg-doom-deep px-3 py-2\">\n <FieldLabel>SENT TO THE SESSION</FieldLabel>\n <pre data-testid=\"runner-launch-line\" className=\"whitespace-pre-wrap break-words text-xs text-doom-green\">\n {line}\n </pre>\n </div>\n\n {problems.length > 0 ? (\n <p data-testid=\"runner-launch-problem\" className=\"text-xs text-doom-red\">\n {problems.join(' ')}\n </p>\n ) : null}\n\n <DialogFooter className=\"flex-wrap sm:flex-nowrap\">\n <span className=\"w-full text-2xs text-doom-faint sm:w-auto\">\n it appears above once the runtime starts it\n </span>\n <span className=\"min-w-0 flex-1\" />\n <Button variant=\"outline\" size=\"xs\" data-testid=\"runner-launch-cancel\" onClick={onClose}>\n cancel\n </Button>\n <Button\n variant=\"primary\"\n size=\"xs\"\n data-testid=\"runner-launch-submit\"\n disabled={problems.length > 0}\n onClick={launch}\n >\n launch\n </Button>\n </DialogFooter>\n </DialogBody>\n </DialogContent>\n </Dialog>\n );\n}\n","export const RUNNER_API_BASE_PATH = 'runner';\nexport const RUN_ID_PARAM = 'runId';\nexport const RUNNER_SCREEN_EVENT = 'screen';\nexport const RUNNER_LOG_STREAM_EVENT = 'append';\nexport const RUNNER_LOG_PING_EVENT = 'ping';\n","import type { LogSlice } from './logReader';\n\n/**\n * The runner log API, shared by this package's session-scoped routes and its\n * cockpit plugin. The two halves run in different processes, so the wire\n * vocabulary is declared here: the browser half may reach `src/types` and\n * nothing else on the server side.\n *\n * Where the routes live is not here. That is `src/types/apiRoutes`, which both\n * halves read, and the generated client turns into the URLs the page addresses.\n * The file a run writes to is still read from its metadata record by the server\n * alone.\n */\n\n/**\n * One chunk of an attached pane's output.\n *\n * Base64 because these are terminal bytes, escape sequences and all, and a\n * server-sent event is a line-oriented text frame that would mangle them.\n */\nexport interface RunnerScreenEvent {\n chunk: string;\n /** Byte offset just past this chunk, so a reconnect resumes rather than replays. */\n offset?: number;\n /** Set once the runner exits, so the page can stop watching. */\n ended?: boolean;\n}\n\n/** The body of a keystroke POST. Text, not key codes: the pane wants bytes. */\nexport interface RunnerInputRequest {\n text: string;\n}\n\n/** The named SSE event carrying a RunnerScreenEvent payload. */\n\n/**\n * A log request as query parameters. `grep` is a literal substring, not a\n * regular expression, because that is what the reader matches; `lines` bounds\n * the answer to the last N lines of whatever survived the filter.\n */\nexport interface RunnerLogQueryParams {\n lines?: number;\n grep?: string;\n ignoreCase?: boolean;\n contextLines?: number;\n}\n\n/** The slice the log route answers with, plus which runner it came from. */\nexport interface RunnerLogResponse extends LogSlice {\n runId: string;\n /** True while the runner is still writing, so the page knows following is worth offering. */\n running: boolean;\n}\n\n/**\n * One server-sent event on the follow stream: the lines appended since the last\n * one. The stream never filters: a page that sets a query stops following and\n * reads a filtered slice instead, because grep with context is a whole-file\n * answer and cannot be assembled from a chunk at a time.\n */\nexport interface RunnerLogStreamEvent {\n /** Appended lines, in order; empty when the event only reports the runner ending. */\n lines: string[];\n /**\n * Byte offset just past the last line in this event.\n *\n * A follower that loses the stream resumes here. Without it the page could\n * only fall back to the offset its last slice reported, which every line\n * delivered since has already moved past, so a reconnect would replay them.\n */\n offset?: number;\n /** Set once the runner exits, so the page can stop following without polling the run list. */\n ended?: boolean;\n}\n\n/** The named SSE event carrying a RunnerLogStreamEvent payload. */\n\n/**\n * The named SSE event that carries nothing.\n *\n * A runner can be alive and silent for minutes, which on the wire is\n * indistinguishable from a connection that died. This is sent on a timer so\n * the socket keeps proving itself. Readers that only listen for `append`\n * ignore it, which is what makes it safe to add.\n */\n\n/** Query parameter names, shared so the page and the route cannot drift apart. */\nexport const RUNNER_LOG_PARAMS = {\n lines: 'lines',\n grep: 'grep',\n ignoreCase: 'ignoreCase',\n contextLines: 'contextLines',\n from: 'from',\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport { RUN_ID_PARAM } from '../constants/webRunnerLog';\nimport { RUNNER_LOG_PARAMS, type RunnerLogResponse } from './webRunnerLog';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path: the build reads both off\n * `src/extensions/workspaces/sessions/(backend)/api/runner/`, so the mount is\n * stated once, by the folder that creates it. This package stated it four times\n * before, once per URL builder, each of them re-spelling `/plugins/` and the\n * session prefix by hand.\n *\n * The Hono app registers these paths verbatim and the browser addresses them,\n * so a path cannot move on one side alone. `:runId` is Hono's own parameter\n * syntax for that reason; a caller supplies the value as `params`, and the\n * client encodes the segment.\n */\nexport default defineApiRoutes({\n log: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/log`,\n query: [\n RUNNER_LOG_PARAMS.lines,\n RUNNER_LOG_PARAMS.grep,\n RUNNER_LOG_PARAMS.ignoreCase,\n RUNNER_LOG_PARAMS.contextLines,\n ],\n response: apiResponse<RunnerLogResponse>(),\n },\n /**\n * The live tail of that same log.\n *\n * `stream: true`, so the client exposes a URL and no call method: the\n * consumer is the browser's own `EventSource`, and a relayed call would\n * buffer a stream that never ends.\n */\n logStream: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/log/stream`,\n query: [RUNNER_LOG_PARAMS.from],\n stream: true,\n },\n /** The unscrubbed pane bytes, for a terminal emulator rather than a reader. */\n screenStream: {\n method: 'GET',\n path: `/runners/:${RUN_ID_PARAM}/screen/stream`,\n query: [RUNNER_LOG_PARAMS.from],\n stream: true,\n },\n input: {\n method: 'POST',\n path: `/runners/:${RUN_ID_PARAM}/screen/input`,\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/doompi-core/web';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'runner' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'runner',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","import { api } from '../../../../../../generated/client';\nimport { RUN_ID_PARAM, RUNNER_LOG_STREAM_EVENT } from '../../../../../constants/webRunnerLog';\nimport {\n RUNNER_LOG_PARAMS,\n type RunnerLogQueryParams,\n type RunnerLogResponse,\n type RunnerLogStreamEvent,\n} from '../../../../../types/webRunnerLog';\n\n/**\n * The page's half of this package's log API. The only place the cockpit talks\n * HTTP to the hub for a runner, so if the transport ever changes, it changes\n * here alone.\n *\n * The generated client owns every URL: a route is named, a session id selects\n * that session's own copy of the API, and the run id is a path parameter the\n * client substitutes and encodes. Nothing here spells a mount, a prefix or a\n * proxy parameter, which is what the four deleted URL builders each did by\n * hand.\n */\n\nexport type RunnerLogResult = { slice: RunnerLogResponse } | { error: string };\n\nconst UNREACHABLE = 'The cockpit hub is unreachable.';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Reads one runner's log: the last N lines, or the lines matching a substring with context. */\nexport async function fetchRunnerLog(\n sessionId: string,\n runId: string,\n params: RunnerLogQueryParams = {},\n signal?: AbortSignal,\n): Promise<RunnerLogResult> {\n const result = await api.session(sessionId).log({\n params: { [RUN_ID_PARAM]: runId },\n // An unset parameter is left out rather than sent empty: the route reads a\n // missing one as its own default, and an empty grep as no filter at all.\n query: {\n [RUNNER_LOG_PARAMS.lines]: params.lines,\n [RUNNER_LOG_PARAMS.grep]: params.grep === '' ? undefined : params.grep,\n [RUNNER_LOG_PARAMS.ignoreCase]: params.ignoreCase === true ? true : undefined,\n [RUNNER_LOG_PARAMS.contextLines]: params.contextLines,\n },\n ...(signal === undefined ? {} : { signal }),\n });\n if (!result.ok) {\n // Status 0 is the transport never answering, which covers both a dead hub\n // and the caller replacing this request. An abort is the caller's own doing\n // and is reported as no answer rather than as a failure to show.\n if (result.status === 0) return { error: signal?.aborted === true ? '' : UNREACHABLE };\n return { error: result.error === '' ? `The hub answered ${String(result.status)}.` : result.error };\n }\n if (!isRecord(result.data) || typeof result.data.text !== 'string') {\n return { error: `The hub answered ${String(result.status)}.` };\n }\n return { slice: result.data };\n}\n\nexport interface RunnerLogFollow {\n close(): void;\n}\n\n/** Attempts before a dropped stream is reported as lost rather than retried. */\nconst MAX_RETRIES = 5;\nconst RETRY_BASE_MS = 500;\n\n/**\n * Follows a running log from the offset already read, so no line is missed\n * between the slice and the stream. The stream never filters; a page with a\n * query stops following and reads a filtered slice instead.\n *\n * A dropped connection is retried with backoff from the offset the last event\n * reported, not from the offset the page started at, so a reconnect resumes\n * instead of replaying. The browser's own EventSource retry cannot do this:\n * it reopens the URL it was given, whose offset is stale the moment a line\n * arrives, so each of its reconnects would duplicate everything since.\n */\nexport function followRunnerLog(\n sessionId: string,\n runId: string,\n from: number,\n handlers: { onEvent(event: RunnerLogStreamEvent): void; onLost(): void },\n): RunnerLogFollow {\n let offset = from;\n let retries = 0;\n let closed = false;\n let source: EventSource | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const open = (): void => {\n if (closed) return;\n const current = new EventSource(\n api\n .session(sessionId)\n .logStream.url({ params: { [RUN_ID_PARAM]: runId }, query: { [RUNNER_LOG_PARAMS.from]: offset } }),\n );\n source = current;\n current.addEventListener(RUNNER_LOG_STREAM_EVENT, (message) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse((message as MessageEvent<string>).data);\n } catch {\n return; // A truncated frame is the next one's problem, not a reason to drop the stream.\n }\n if (!isRecord(parsed) || !Array.isArray(parsed.lines)) return;\n const event = parsed as unknown as RunnerLogStreamEvent;\n if (typeof event.offset === 'number') offset = event.offset;\n retries = 0;\n // The server closes after this one, and a close reaches us as an error.\n // Standing down first keeps that from looking like a dropped stream.\n if (event.ended === true) {\n closed = true;\n current.close();\n }\n handlers.onEvent(event);\n });\n current.addEventListener('error', () => {\n current.close();\n if (closed) return;\n retries += 1;\n if (retries > MAX_RETRIES) {\n handlers.onLost();\n return;\n }\n timer = setTimeout(open, RETRY_BASE_MS * 2 ** (retries - 1));\n });\n };\n\n open();\n return {\n close: () => {\n closed = true;\n if (timer !== undefined) clearTimeout(timer);\n source?.close();\n },\n };\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { AnsiLine, Button, Input, SearchIcon, StatusBadge, type StatusTone } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { RunnerLogResponse } from '../../../../../types/webRunnerLog';\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { formatRunnerUptime, isFollowingLive, logViewLines, tailLineNumbers } from '../_lib/format';\nimport { fetchRunnerLog, followRunnerLog } from '../_lib/logApi';\nimport { requestRunnerStop, runners } from '../_lib/runnersStore';\n\nconst TICK_MS = 10_000;\n/** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */\nconst TAB_ID_PREFIX = 'runner-log-';\n/** Lines held in the view; a follow that ran all day must not grow without bound. */\nconst MAX_VIEW_LINES = 2000;\n/** Context lines each side of a match, the same either-side window grep would use. */\nconst CONTEXT_LINES = 2;\n/** A query is typed a character at a time; each one costs a whole-file read on the hub. */\nconst SEARCH_DEBOUNCE_MS = 200;\n/** How close to the bottom still counts as \"at the bottom\" for following. */\nconst NEAR_BOTTOM_PX = 24;\nconst BYTES_PER_KB = 1024;\nconst BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;\n\n/** The temporary tab for one runner's log; the host keeps this panel while the tab is open. */\nexport function runnerLogTab(run: RunnerRunView): TransientTab {\n const runId = run.id;\n return {\n id: `${TAB_ID_PREFIX}${runId}`,\n label: `log · ${run.name}`,\n panel: (props: WebPluginSlotProps) => <RunnerLogPanel {...props} runId={runId} />,\n };\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\nfunction badgeOf(run: RunnerRunView | undefined): { tone: StatusTone; label: string } {\n if (run === undefined) return { tone: 'neutral', label: 'gone' };\n if (run.state === 'running') return { tone: 'running', label: 'running' };\n if (run.exit === undefined || run.exit.reason === 'stopped') return { tone: 'neutral', label: 'stopped' };\n const clean = run.exit.reason === 'completed' && (run.exit.code === 0 || run.exit.code === null);\n return clean ? { tone: 'ok', label: 'done' } : { tone: 'error', label: run.exit.reason.replace('_', ' ') };\n}\n\n/** Whether one line is a match rather than the context printed around one. */\nfunction isMatch(line: string, needle: string, ignoreCase: boolean): boolean {\n if (needle === '') return false;\n return ignoreCase ? line.toLowerCase().includes(needle.toLowerCase()) : line.includes(needle);\n}\n\n/** One fact of the runner, on its own line so a long command is never elided. */\nfunction MetaRow({ label, value }: { label: string; value: string }) {\n return (\n <span className=\"flex min-w-0 gap-2.5\">\n <span className=\"w-14 shrink-0 text-xs text-doom-faint\">{label}</span>\n <span className=\"min-w-0 flex-1 break-all text-xs text-doom-dim\">{value}</span>\n </span>\n );\n}\n\n/**\n * One rendered log line: its number, whether it matched, and the text.\n *\n * A row per line rather than one block of joined text. The block form re-parsed\n * every line's escapes whenever a single line arrived, because the parse was\n * keyed on the joined string; keyed by line number instead, an append only\n * costs the line that appended.\n */\nfunction LogRow({ number, line, matched }: { number: number | undefined; line: string; matched: boolean }) {\n return (\n <div data-testid=\"runner-log-row\" data-matched={matched} className=\"flex min-w-0 gap-2\">\n {number === undefined ? null : (\n <span className=\"w-12 shrink-0 select-none text-right text-xs leading-relaxed text-doom-faint\">{number}</span>\n )}\n <span\n aria-hidden\n className={`w-4 shrink-0 select-none text-xs leading-relaxed ${matched ? 'text-doom-yellow' : 'text-doom-faint'}`}\n >\n {matched ? '>>' : ''}\n </span>\n <AnsiLine\n line={line}\n className={`min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-xs leading-relaxed ${\n matched ? 'text-doom-text' : 'text-doom-dim'\n }`}\n />\n </div>\n );\n}\n\n/**\n * One runner's log: the bounded tail, following while the runner writes, and\n * a grep over the whole file when the reader types a query.\n *\n * Search is not find-next. The reader matches a literal substring and answers\n * with the matching lines and their context, so a query replaces the view\n * rather than moving a cursor through it, and following pauses while one is\n * set because a filtered view is a snapshot of a file that is still growing.\n */\nexport function RunnerLogPanel({ sessionId, runId, sendSessionFrame }: WebPluginSlotProps & { runId: string }) {\n const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));\n const run = runs.find((candidate) => candidate.id === runId);\n const [now, setNow] = useState(() => Date.now());\n const [typed, setTyped] = useState('');\n const [query, setQuery] = useState('');\n const [ignoreCase, setIgnoreCase] = useState(false);\n const [context, setContext] = useState(CONTEXT_LINES);\n const [following, setFollowing] = useState(true);\n const [slice, setSlice] = useState<RunnerLogResponse | undefined>(undefined);\n const [streamFrom, setStreamFrom] = useState<number | undefined>(undefined);\n const [appended, setAppended] = useState<string[]>([]);\n const [error, setError] = useState<string | undefined>(undefined);\n /** False once the reader scrolls up: following must not yank them back down. */\n const [pinned, setPinned] = useState(true);\n const bottom = useRef<HTMLDivElement | null>(null);\n const body = useRef<HTMLDivElement | null>(null);\n\n const filtering = query !== '';\n const live = isFollowingLive(following, filtering, slice?.running === true);\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n // Typing is not a question yet. Reading the log costs a whole-file scan on\n // the hub, so the question is asked once the reader stops typing.\n useEffect(() => {\n const timer = setTimeout(() => setQuery(typed), SEARCH_DEBOUNCE_MS);\n return () => clearTimeout(timer);\n }, [typed]);\n\n const load = useCallback(\n (signal: AbortSignal) => {\n if (sessionId === null) return;\n const params = filtering ? { grep: query, ignoreCase, contextLines: context } : {};\n void fetchRunnerLog(sessionId, runId, params, signal).then((result) => {\n if (signal.aborted) return;\n if ('error' in result) {\n if (result.error !== '') setError(result.error);\n return;\n }\n setError(undefined);\n setAppended([]);\n setSlice(result.slice);\n setStreamFrom(result.slice.completeBytes);\n });\n },\n [sessionId, runId, filtering, query, ignoreCase, context],\n );\n\n // Re-read whenever the question changes. A query is debounced above, so the\n // in-flight request for the previous one is abandoned only on a real change.\n useEffect(() => {\n const controller = new AbortController();\n load(controller.signal);\n return () => controller.abort();\n }, [load]);\n\n // Keyed on the offset rather than the slice object: a re-read that lands on\n // the same place must not tear down a healthy stream and reopen it.\n useEffect(() => {\n if (!live || sessionId === null || streamFrom === undefined) return;\n const follow = followRunnerLog(sessionId, runId, streamFrom, {\n onEvent: (event) => {\n if (event.lines.length > 0) {\n setAppended((current) => [...current, ...event.lines].slice(-MAX_VIEW_LINES));\n }\n if (event.ended === true) setFollowing(false);\n },\n onLost: () => setFollowing(false),\n });\n return () => follow.close();\n }, [live, sessionId, runId, streamFrom]);\n\n // A slice read mid-line ends in a fragment the stream will send again whole.\n const partialTail = slice !== undefined && slice.exists && slice.completeBytes < slice.fileSize;\n const view = logViewLines(slice?.text ?? '', appended, MAX_VIEW_LINES, { dropPartialTail: live && partialTail });\n const numbers = useMemo(() => {\n if (slice === undefined) return [];\n if (filtering) return slice.lineNumbers ?? [];\n const last = slice.totalLines + appended.length - (live && partialTail ? 1 : 0);\n return tailLineNumbers(last, view.lines.length);\n }, [slice, filtering, appended.length, live, partialTail, view.lines.length]);\n\n useEffect(() => {\n if (live && pinned) bottom.current?.scrollIntoView({ block: 'end' });\n }, [live, pinned, view.lines.length]);\n\n // Following is a promise to show the newest line, not a promise to drag the\n // reader there. Scrolling up is how they say they are reading something else.\n const onScroll = (): void => {\n const element = body.current;\n if (element === null) return;\n setPinned(element.scrollHeight - element.scrollTop - element.clientHeight <= NEAR_BOTTOM_PX);\n };\n\n const badge = badgeOf(run);\n const stopping = run !== undefined && stopRequested.includes(run.id);\n const elapsed =\n run === undefined ? '' : formatRunnerUptime(run.startedAt, run.exit ? Date.parse(run.exit.finishedAt) : now);\n\n return (\n <div data-testid=\"runner-log-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex min-h-11 shrink-0 flex-wrap items-center gap-2 border-b border-doom-border-soft px-3 py-2 sm:h-11 sm:flex-nowrap sm:gap-2.5 sm:px-[26px] sm:py-0\">\n <span data-testid=\"runner-log-name\" className=\"shrink-0 truncate text-sm font-bold text-doom-hi\">\n {run?.name ?? runId}\n </span>\n <StatusBadge tone={badge.tone} data-testid=\"runner-log-state\">\n {badge.label}\n </StatusBadge>\n {run ? (\n <>\n <span className=\"shrink-0 text-xs text-doom-faint\">{elapsed}</span>\n <span className=\"shrink-0 text-xs text-doom-faint\">\n {`pid ${String(run.pid)} · ${run.backend}${run.interactive ? ' · tty' : ''}`}\n </span>\n </>\n ) : (\n <span data-testid=\"runner-log-gone\" className=\"text-xs text-doom-faint\">\n no longer listed; its log stays readable\n </span>\n )}\n <span className=\"min-w-0 flex-1\" />\n {run !== undefined && run.state === 'running' && sessionId !== null ? (\n <Button\n variant={stopping ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid=\"runner-log-stop\"\n disabled={stopping}\n title={stopping ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'}\n onClick={() => requestRunnerStop(sendSessionFrame, sessionId, run.id)}\n className=\"px-2 text-2xs font-bold\"\n >\n {stopping ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </div>\n\n {run ? (\n <div className=\"flex shrink-0 flex-col gap-1.5 border-b border-doom-border-soft px-3 py-2.5 sm:px-[26px]\">\n <MetaRow label=\"command\" value={run.command} />\n <MetaRow label=\"cwd\" value={run.cwd} />\n </div>\n ) : null}\n\n <div className=\"flex min-h-10 shrink-0 flex-wrap items-center gap-2 border-b border-doom-border-soft px-3 py-2 sm:h-10 sm:flex-nowrap sm:gap-2.5 sm:px-[26px] sm:py-0\">\n <span className=\"relative flex min-w-0 flex-1 items-center sm:w-[300px] sm:flex-none\">\n <SearchIcon className=\"pointer-events-none absolute left-2.5 h-3 w-3 text-doom-faint\" />\n <Input\n size=\"sm\"\n data-testid=\"runner-log-search\"\n value={typed}\n placeholder=\"search this log\"\n onChange={(event) => setTyped(event.target.value)}\n className=\"pl-7 text-xs\"\n />\n </span>\n <Button\n variant={ignoreCase ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-ignore-case\"\n aria-pressed={ignoreCase}\n title=\"match regardless of case\"\n onClick={() => setIgnoreCase((current) => !current)}\n className=\"px-2 text-2xs font-bold\"\n >\n aA\n </Button>\n <Button\n variant={context > 0 && filtering ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-context\"\n disabled={!filtering}\n title=\"lines of context kept either side of a match\"\n onClick={() => setContext((current) => (current === 0 ? CONTEXT_LINES : 0))}\n className=\"px-2 text-2xs font-bold\"\n >\n {`± ${String(context)} lines`}\n </Button>\n <span className=\"min-w-0 flex-1\" />\n <span\n data-testid=\"runner-log-stats\"\n className=\"min-w-0 basis-full truncate text-xs text-doom-faint sm:basis-auto\"\n >\n {slice === undefined\n ? 'reading…'\n : filtering\n ? `${String(slice.lineCount)} matching of ${slice.totalLines.toLocaleString('en-US')} lines`\n : `${live ? 'tailing' : 'showing'} last ${String(view.lines.length)} of ${slice.totalLines.toLocaleString('en-US')} lines`}\n {view.hidden > 0 ? ` · ${view.hidden.toLocaleString('en-US')} older hidden` : ''}\n </span>\n <Button\n variant={live ? 'subtle' : 'outline'}\n size=\"xs\"\n data-testid=\"runner-log-follow\"\n aria-pressed={live}\n disabled={filtering || slice?.running !== true}\n title={filtering ? 'a filtered view is a snapshot; clear the query to follow' : 'follow the log as it grows'}\n onClick={() => setFollowing((current) => !current)}\n className=\"px-2 text-2xs font-bold\"\n >\n {filtering ? 'follow · paused' : 'follow'}\n </Button>\n </div>\n\n <div className=\"relative min-h-0 flex-1\">\n <div\n ref={body}\n onScroll={onScroll}\n data-testid=\"runner-log-body\"\n className=\"h-full overflow-auto bg-doom-deep px-3 py-3 sm:px-[26px]\"\n >\n {error !== undefined ? (\n <p data-testid=\"runner-log-error\" className=\"text-xs text-doom-red\">\n {error}\n </p>\n ) : slice === undefined ? (\n <p data-testid=\"runner-log-reading\" className=\"text-xs text-doom-faint\">\n reading…\n </p>\n ) : slice.exists === false ? (\n <p className=\"text-xs text-doom-faint\">this runner has not written a log yet</p>\n ) : view.lines.length === 0 ? (\n <p className=\"text-xs text-doom-faint\">{filtering ? 'nothing matched' : 'the log is empty'}</p>\n ) : (\n view.lines.map((line, index) => {\n const number = numbers[index];\n const previous = numbers[index - 1];\n const gap = previous !== undefined && number !== undefined && number - previous > 1;\n return (\n <Fragment key={number ?? `row-${String(index)}`}>\n {gap ? (\n <div\n data-testid=\"runner-log-gap\"\n aria-hidden\n className=\"select-none py-0.5 pl-12 text-xs leading-relaxed text-doom-faint\"\n >\n ⋯\n </div>\n ) : null}\n <LogRow number={number} line={line} matched={filtering && isMatch(line, query, ignoreCase)} />\n </Fragment>\n );\n })\n )}\n <div ref={bottom} />\n </div>\n {live && !pinned ? (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n data-testid=\"runner-log-jump\"\n title=\"scroll back to the newest lines and keep following\"\n onClick={() => {\n setPinned(true);\n bottom.current?.scrollIntoView({ block: 'end' });\n }}\n className=\"absolute bottom-3 right-3 px-2 text-2xs font-bold\"\n >\n jump to latest\n </Button>\n ) : null}\n </div>\n\n <div className=\"flex h-8 shrink-0 items-center gap-2.5 border-t border-doom-border-soft px-3 sm:px-[26px]\">\n <span data-testid=\"runner-log-path\" className=\"min-w-0 flex-1 truncate text-2xs text-doom-faint\">\n {run?.logPath ?? slice?.path ?? ''}\n </span>\n {slice !== undefined ? (\n <span className=\"shrink-0 text-2xs text-doom-faint\">{formatSize(slice.fileSize)}</span>\n ) : null}\n </div>\n </div>\n );\n}\n","import { api } from '../../../../../../generated/client';\nimport { RUN_ID_PARAM, RUNNER_SCREEN_EVENT } from '../../../../../constants/webRunnerLog';\nimport { RUNNER_LOG_PARAMS, type RunnerInputRequest, type RunnerScreenEvent } from '../../../../../types/webRunnerLog';\n\n/**\n * The page's half of the attached pane.\n *\n * Two directions over two mechanisms, because the hub proxies requests and\n * server-sent events but cannot upgrade a socket: pane bytes arrive on an\n * EventSource, and keystrokes go back as ordinary POSTs. Both addresses come\n * from the generated client: the stream route offers a URL and no call method,\n * which is exactly what an EventSource wants and all it can be given.\n */\n\n/** Turns one base64 chunk back into the bytes a terminal was sent. */\nexport function decodeChunk(chunk: string): Uint8Array {\n const binary = atob(chunk);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n}\n\nexport interface RunnerScreenWatch {\n close(): void;\n}\n\n/** Attempts before a dropped screen stream is reported as lost rather than retried. */\nconst MAX_RETRIES = 5;\nconst RETRY_BASE_MS = 500;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function watchRunnerScreen(\n sessionId: string,\n runId: string,\n from: number,\n handlers: { onEvent(event: RunnerScreenEvent): void; onLost(): void },\n): RunnerScreenWatch {\n let offset = from;\n let retries = 0;\n let closed = false;\n let source: EventSource | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const open = (): void => {\n if (closed) return;\n const current = new EventSource(\n api\n .session(sessionId)\n .screenStream.url({ params: { [RUN_ID_PARAM]: runId }, query: { [RUNNER_LOG_PARAMS.from]: offset } }),\n );\n source = current;\n current.addEventListener(RUNNER_SCREEN_EVENT, (message) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse((message as MessageEvent<string>).data);\n } catch {\n return;\n }\n if (!isRecord(parsed) || typeof parsed.chunk !== 'string') return;\n const event = parsed as unknown as RunnerScreenEvent;\n // Every delivered byte moves the resume point, so a reconnect asks for\n // what comes next rather than repainting everything again.\n if (typeof event.offset === 'number') offset = event.offset;\n retries = 0;\n // The server closes after this one, and a close reaches us as an error.\n if (event.ended === true) {\n closed = true;\n current.close();\n }\n handlers.onEvent(event);\n });\n current.addEventListener('error', () => {\n current.close();\n if (closed) return;\n retries += 1;\n if (retries > MAX_RETRIES) {\n handlers.onLost();\n return;\n }\n timer = setTimeout(open, RETRY_BASE_MS * 2 ** (retries - 1));\n });\n };\n\n open();\n return {\n close: () => {\n closed = true;\n if (timer !== undefined) clearTimeout(timer);\n source?.close();\n },\n };\n}\n\n/** Sends one batch of keystrokes. Resolves to whether the pane took them. */\nexport async function sendRunnerInput(sessionId: string, runId: string, text: string): Promise<boolean> {\n const body: RunnerInputRequest = { text };\n const result = await api.session(sessionId).input({ params: { [RUN_ID_PARAM]: runId }, body });\n return result.ok;\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button, StatusBadge, type TerminalHandle, TerminalView } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { runners } from '../_lib/runnersStore';\nimport { decodeChunk, sendRunnerInput, watchRunnerScreen } from '../_lib/screenApi';\n\nconst TAB_ID_PREFIX = 'runner-shell-';\n/** Keystrokes are gathered for this long and sent as one request. */\nconst FLUSH_MS = 30;\n\n/** The temporary tab holding one interactive runner's pane. */\nexport function runnerShellTab(run: RunnerRunView): TransientTab {\n const runId = run.id;\n return {\n id: `${TAB_ID_PREFIX}${runId}`,\n label: `shell · ${run.name}`,\n panel: (props: WebPluginSlotProps) => <RunnerShellPanel {...props} runId={runId} />,\n };\n}\n\n/**\n * An interactive runner's pane, attached.\n *\n * A real terminal emulator, fed the pane's own bytes. The runner's sink keeps\n * an unscrubbed copy of its output beside the log for exactly this: the log is\n * stripped of cursor movement so it stays worth grepping, which is precisely\n * what a terminal needs. Because these are the original bytes, colour, cursor\n * placement and full-screen programs all render the way they would in a shell.\n *\n * xterm is loaded when the tab opens rather than with the plugin. Most sessions\n * never attach to anything, and an emulator is not a small thing to carry.\n */\nexport function RunnerShellPanel({ sessionId, runId }: WebPluginSlotProps & { runId: string }) {\n const { runs } = useStore(runners.store, (state) => runners.select(state, sessionId));\n const run = runs.find((candidate) => candidate.id === runId);\n const [ended, setEnded] = useState(false);\n const [lost, setLost] = useState(false);\n const [ready, setReady] = useState(false);\n const terminal = useRef<TerminalHandle | null>(null);\n const pending = useRef<string[]>([]);\n const flush = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n if (sessionId === null || !ready) return;\n const watch = watchRunnerScreen(sessionId, runId, 0, {\n onEvent: (event) => {\n if (event.chunk !== '') terminal.current?.write(decodeChunk(event.chunk));\n if (event.ended === true) setEnded(true);\n },\n onLost: () => setLost(true),\n });\n return () => watch.close();\n }, [sessionId, runId, ready]);\n\n useEffect(\n () => () => {\n if (flush.current !== undefined) clearTimeout(flush.current);\n },\n [],\n );\n\n // Batched: a burst of typing is one request rather than one per character,\n // and the pane sees the keys in the order they were pressed either way.\n const send = (text: string): void => {\n if (sessionId === null) return;\n pending.current.push(text);\n if (flush.current !== undefined) return;\n flush.current = setTimeout(() => {\n const batch = pending.current.join('');\n pending.current = [];\n flush.current = undefined;\n if (batch !== '') void sendRunnerInput(sessionId, runId, batch);\n }, FLUSH_MS);\n };\n\n const live = run?.state === 'running' && !ended;\n\n return (\n <div data-testid=\"runner-shell-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex min-h-11 shrink-0 items-center gap-2.5 border-b border-doom-border-soft px-3 sm:h-11 sm:px-[26px]\">\n <span data-testid=\"runner-shell-name\" className=\"shrink-0 truncate text-sm font-bold text-doom-hi\">\n {run?.name ?? runId}\n </span>\n <StatusBadge tone={live ? 'running' : 'neutral'} data-testid=\"runner-shell-state\">\n {live ? 'attached' : 'detached'}\n </StatusBadge>\n <span className=\"min-w-0 flex-1\" />\n {lost ? (\n <span data-testid=\"runner-shell-lost\" className=\"shrink-0 text-2xs text-doom-red\">\n the connection to this pane dropped\n </span>\n ) : null}\n {live ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"runner-shell-interrupt\"\n title=\"send ctrl-c\"\n onClick={() => send('\\u0003')}\n className=\"px-2 text-2xs font-bold\"\n >\n ctrl-c\n </Button>\n ) : null}\n </div>\n\n <div className=\"relative min-h-0 flex-1 overflow-hidden bg-doom-deep px-3 py-2 sm:px-[26px]\">\n <TerminalView\n ref={terminal}\n data-testid=\"runner-shell-terminal\"\n onData={(text) => send(text)}\n onReady={() => setReady(true)}\n />\n {ready ? null : (\n <p data-testid=\"runner-shell-reading\" className=\"text-xs text-doom-faint\">\n attaching…\n </p>\n )}\n </div>\n\n <div className=\"flex h-8 shrink-0 items-center border-t border-doom-border-soft px-3 sm:px-[26px]\">\n <span className=\"min-w-0 flex-1 truncate text-2xs text-doom-faint\">\n {live ? 'typing goes straight to the runner' : 'this runner is no longer accepting input'}\n </span>\n </div>\n </div>\n );\n}\n","import { ansiSpans } from '@agimon-ai/doompi-web-components';\nimport { useEffect, useState } from 'react';\n\nimport { fetchRunnerLog, followRunnerLog, type RunnerLogFollow } from '../_lib/logApi';\n\n/**\n * The newest line a running runner has written, for a row that has one line to\n * spend.\n *\n * The dock answers \"what is happening\", and a command that has not changed\n * since it started answers \"what was asked\". The last line of the log is the\n * closest thing to progress the cockpit can show without opening the log, so a\n * row that is still running trades its command for it.\n *\n * The line comes from the same two calls the log panel makes: read the tail,\n * then follow the stream from exactly where that read ended. Nothing new goes\n * on the wire, and a runner nobody is looking at is not followed, because the\n * stream lives and dies with the row.\n */\n\n/** Enough lines that a trailing blank, or a final progress redraw, is not the whole answer. */\nconst TAIL_REQUEST_LINES = 5;\n\n/**\n * The last line worth showing, as plain text.\n *\n * Blank lines are skipped, because a log that ends with a newline would\n * otherwise report nothing at all. Colour is dropped rather than rendered: the\n * row is nine pixels of faint text beside a name and a stop control, and a\n * green tick from someone else's palette in the middle of it reads as damage.\n */\nexport function plainTailLine(lines: readonly string[]): string | undefined {\n for (let index = lines.length - 1; index >= 0; index -= 1) {\n const text = ansiSpans(lines[index] ?? '')\n .map((span) => span.text)\n .join('')\n .trim();\n if (text.length > 0) return text;\n }\n return undefined;\n}\n\n/**\n * The newest log line of a running runner, or undefined for a runner that is\n * finished, unreachable, or has not written anything yet. The caller decides\n * what to show instead; this hook never invents a placeholder.\n */\nexport function useRunnerTail(sessionId: string | null, runId: string, running: boolean): string | undefined {\n // Kept with the run it belongs to, so a row that is reused for another\n // runner shows nothing rather than the previous runner's output.\n const [tail, setTail] = useState<{ runId: string; text: string } | undefined>(undefined);\n\n useEffect(() => {\n if (!running || sessionId === null) return;\n let live = true;\n let follow: RunnerLogFollow | undefined;\n const controller = new AbortController();\n void fetchRunnerLog(sessionId, runId, { lines: TAIL_REQUEST_LINES }, controller.signal).then((result) => {\n // A log the hub cannot serve leaves the row on its command, which is\n // still true; the log tab is where an unreachable runner is diagnosed.\n if (!live || 'error' in result) return;\n const initial = plainTailLine(result.slice.text.split('\\n'));\n if (initial !== undefined) setTail({ runId, text: initial });\n if (!result.slice.running) return;\n follow = followRunnerLog(sessionId, runId, result.slice.completeBytes, {\n onEvent: (event) => {\n const next = plainTailLine(event.lines);\n if (live && next !== undefined) setTail({ runId, text: next });\n },\n // A stream that stays down past its retries freezes the line at the\n // last one that arrived. The row is a glance, and saying so would cost\n // more room than the fact is worth.\n onLost: () => undefined,\n });\n });\n return () => {\n live = false;\n controller.abort();\n follow?.close();\n };\n }, [sessionId, runId, running]);\n\n return running && tail?.runId === runId ? tail.text : undefined;\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport {\n Button,\n Dot,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EmptyState,\n PlusIcon,\n} from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../types/webRunners';\nimport { formatRunnerUptime } from '../_lib/format';\nimport { RUNNER_SHELL_REQUEST } from '../_lib/launchLine';\nimport { requestRunnerStart, requestRunnerStop, runners } from '../_lib/runnersStore';\nimport { LaunchRunnerDialog } from './LaunchRunnerDialog';\nimport { runnerLogTab } from './RunnerLogPanel';\nimport { runnerShellTab } from './RunnerShellPanel';\nimport { useRunnerTail } from './runnerTail';\n\nconst TICK_MS = 10_000;\nconst SHELL_LAUNCH_TIMEOUT_MS = 30_000;\nconst RUNNERS_TAB_ID = 'runner-runs';\n\n/** The temporary tab the dock's runners group opens from its own name. */\nexport function runnersTab(): TransientTab {\n return { id: RUNNERS_TAB_ID, label: 'runners', panel: RunnersPanel };\n}\n\n/**\n * Everything this session has up, one card each.\n *\n * The dock's rail gives a runner one line, which is enough to notice it and\n * not enough to work with it. This is the same set with room: the command in\n * full, where it runs, and the last thing it said.\n *\n * Running runners only, the same rule the rail follows. A finished run leaves\n * the moment it exits, because this answers \"what is happening\"; its log stays\n * readable from its own tab, and `doom-runner list` is where history lives.\n */\nexport function RunnersPanel({ sessionId, sendSessionFrame, openTransientTab }: WebPluginSlotProps) {\n const session = useStore(runners.store, (state) => runners.select(state, sessionId));\n const running = session.runs.filter((run) => run.state === 'running');\n const [now, setNow] = useState(() => Date.now());\n const [launching, setLaunching] = useState(false);\n const shellBaseline = useRef<ReadonlySet<string> | null>(null);\n const shellLaunchTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Somewhere to start from, borrowed from whatever is already up.\n const defaultCwd = running[0]?.cwd;\n\n const startShell = (): void => {\n if (sessionId === null) return;\n shellBaseline.current = new Set(session.runs.map((run) => run.id));\n clearTimeout(shellLaunchTimeout.current);\n shellLaunchTimeout.current = setTimeout(() => {\n shellBaseline.current = null;\n }, SHELL_LAUNCH_TIMEOUT_MS);\n requestRunnerStart(sendSessionFrame, sessionId, RUNNER_SHELL_REQUEST);\n };\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n useEffect(() => {\n const baseline = shellBaseline.current;\n if (baseline === null) return;\n const shell = session.runs.find((run) => run.state === 'running' && run.interactive && !baseline.has(run.id));\n if (!shell) return;\n shellBaseline.current = null;\n clearTimeout(shellLaunchTimeout.current);\n openTransientTab(runnerShellTab(shell));\n }, [openTransientTab, session.runs]);\n\n useEffect(() => () => clearTimeout(shellLaunchTimeout.current), []);\n\n return (\n <div data-testid=\"runners-panel\" className=\"flex min-h-0 flex-1 flex-col overflow-auto\">\n <div className=\"flex min-h-11 shrink-0 items-center gap-2.5 border-b border-doom-border-soft px-3 sm:h-11 sm:px-[26px]\">\n <span className=\"text-sm font-bold text-doom-hi\">runners</span>\n <span data-testid=\"runners-panel-count\" className=\"text-xs text-doom-faint\">\n {running.length === 0 ? 'nothing running' : `${String(running.length)} running`}\n </span>\n <span className=\"min-w-0 flex-1\" />\n {sessionId === null ? null : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid=\"runners-panel-launch\"\n title=\"launch a runner\"\n aria-label=\"launch a runner\"\n className=\"text-doom-faint hover:text-doom-hi\"\n >\n <PlusIcon className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" data-testid=\"runners-panel-launch-menu\">\n <DropdownMenuItem data-testid=\"runners-panel-start-shell\" onSelect={startShell}>\n start shell\n </DropdownMenuItem>\n <DropdownMenuItem data-testid=\"runners-panel-run-command\" onSelect={() => setLaunching(true)}>\n run command\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {running.length === 0 ? (\n <EmptyState\n data-testid=\"runners-panel-empty\"\n className=\"px-4 py-8\"\n title=\"nothing running\"\n description=\"a command that outlives its turn moves to a runner and appears here while it works.\"\n >\n {sessionId === null ? null : (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"runners-panel-launch-empty\"\n onClick={() => setLaunching(true)}\n className=\"px-2 text-2xs font-bold\"\n >\n launch a runner\n </Button>\n )}\n </EmptyState>\n ) : (\n <div\n data-testid=\"runners-panel-grid\"\n className=\"grid grid-cols-1 gap-2.5 px-3 py-3 sm:grid-cols-2 sm:px-[26px] xl:grid-cols-3\"\n >\n {running.map((run) => (\n <RunnerCard\n key={run.id}\n run={run}\n now={now}\n stopRequested={session.stopRequested.includes(run.id)}\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n openTransientTab={openTransientTab}\n />\n ))}\n </div>\n )}\n\n {launching && sessionId !== null ? (\n <LaunchRunnerDialog\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n {...(defaultCwd === undefined ? {} : { defaultCwd })}\n onClose={() => setLaunching(false)}\n />\n ) : null}\n </div>\n );\n}\n\n/**\n * One running runner. Its own component because the tail line is a\n * subscription per card, and a card that leaves the grid closes it.\n */\nfunction RunnerCard({\n run,\n now,\n stopRequested,\n sessionId,\n sendSessionFrame,\n openTransientTab,\n}: {\n run: RunnerRunView;\n now: number;\n stopRequested: boolean;\n} & Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame' | 'openTransientTab'>) {\n const tail = useRunnerTail(sessionId, run.id, run.state === 'running');\n return (\n <div\n data-testid={`runners-card-${run.id}`}\n className=\"flex min-w-0 flex-col gap-1.5 rounded-md border border-doom-border-soft bg-doom-panel p-2.5\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Dot tone=\"yellow\" pulse />\n <span className=\"min-w-0 flex-1 truncate text-sm font-bold text-doom-hi\">{run.name}</span>\n <span className=\"shrink-0 text-2xs text-doom-faint\">\n {formatRunnerUptime(run.startedAt, now)}\n {run.interactive ? ' · tty' : ''}\n </span>\n </div>\n\n <span className=\"min-w-0 break-all text-2xs leading-normal text-doom-dim\">{run.command}</span>\n <span className=\"min-w-0 truncate text-2xs text-doom-faint\">{run.cwd}</span>\n\n {/* What it is doing, falling back to what it was asked to do until the\n first line of output arrives. */}\n <span\n data-testid={`runners-card-detail-${run.id}`}\n data-detail={tail === undefined ? 'command' : 'tail'}\n className=\"min-w-0 truncate text-2xs text-doom-faint\"\n >\n {tail ?? '…'}\n </span>\n\n <div className=\"flex items-center gap-1.5 pt-0.5\">\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid={`runners-card-log-${run.id}`}\n title=\"open this runner's log\"\n onClick={() => openTransientTab(runnerLogTab(run))}\n className=\"px-2 text-2xs font-bold\"\n >\n log\n </Button>\n {/* Only an interactive run has a pane to attach to. A plain one has\n nothing to type into, so it is not offered. */}\n {run.interactive && sessionId !== null ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid={`runners-card-shell-${run.id}`}\n title=\"attach to this runner's terminal\"\n onClick={() => openTransientTab(runnerShellTab(run))}\n className=\"px-2 text-2xs font-bold\"\n >\n shell\n </Button>\n ) : null}\n <span className=\"min-w-0 flex-1\" />\n {sessionId !== null ? (\n <Button\n variant={stopRequested ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid={`runners-card-stop-${run.id}`}\n disabled={stopRequested}\n title={\n stopRequested ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n onClick={() => requestRunnerStop(sendSessionFrame, sessionId, run.id)}\n className=\"px-2 text-2xs font-bold\"\n >\n {stopRequested ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n","import { defineActivityGroup } from '@agimon-ai/doompi-core/web';\n\nimport { runnersTab } from '../_components/RunnersPanel';\nimport { runnerActivitySource } from '../_lib/runnersStore';\nexport default defineActivityGroup({\n keys: 'r l',\n statusKey: 'doom-runner-runners',\n activeSource: runnerActivitySource,\n transientTab: runnersTab,\n order: 20,\n});\n","import { defineChannelFile } from '@agimon-ai/doompi-core/web';\n\nimport { runnerRunsChannel } from '../_lib/runnersStore';\nexport default defineChannelFile(runnerRunsChannel);\n","import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button, Dot } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useState } from 'react';\n\nimport type { RunnerRunView } from '../../../../../../types/webRunners';\nimport { runnerLogTab } from '../../_components/RunnerLogPanel';\nimport { runnersTab } from '../../_components/RunnersPanel';\nimport { useRunnerTail } from '../../_components/runnerTail';\nimport { formatRunnerUptime } from '../../_lib/format';\nimport { requestRunnerStop, runners } from '../../_lib/runnersStore';\n\nconst TICK_MS = 10_000;\n\n/**\n * The runners group's body in the activity dock: what this session has up\n * right now.\n *\n * Only running runners are listed. A finished one leaves the moment it exits,\n * because the dock answers \"what is happening\", and a rail that keeps ten\n * completed playwright calls around buries the one command still working. The\n * runtime keeps every record; `doom-runner list` and the run's own log tab are\n * where a finished run is read.\n */\nexport function RunnersActivitySection({ sessionId, sendSessionFrame, openTransientTab }: WebPluginSlotProps) {\n const session = useStore(runners.store, (state) => runners.select(state, sessionId));\n const running = session.runs.filter((run) => run.state === 'running');\n const [now, setNow] = useState(() => Date.now());\n\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), TICK_MS);\n return () => clearInterval(timer);\n }, []);\n\n if (running.length === 0) {\n return (\n <div className=\"flex items-center gap-2 px-1\">\n <p data-testid=\"activity-summary-runners\" className=\"px-1 text-xs text-doom-faint\">\n idle\n </p>\n {sessionId === null ? null : (\n <Button\n variant=\"link\"\n size=\"xs\"\n data-testid=\"activity-runner-launch\"\n className=\"px-0\"\n onClick={() => openTransientTab(runnersTab())}\n >\n launch a runner\n </Button>\n )}\n </div>\n );\n }\n\n return (\n <div data-testid=\"activity-runner-runs\" className=\"flex flex-col gap-0.5\">\n {running.map((run: RunnerRunView) => (\n <RunnerRow\n key={run.id}\n run={run}\n now={now}\n stopRequested={session.stopRequested.includes(run.id)}\n sessionId={sessionId}\n sendSessionFrame={sendSessionFrame}\n openTransientTab={openTransientTab}\n />\n ))}\n </div>\n );\n}\n\n/**\n * One running runner: its name, how long it has been up, a stop control, and\n * one line of what it is doing. Its own component because the tail line is a\n * subscription per row, and a row that scrolls out of the dock closes it.\n */\nfunction RunnerRow({\n run,\n now,\n stopRequested,\n sessionId,\n sendSessionFrame,\n openTransientTab,\n}: {\n run: RunnerRunView;\n now: number;\n stopRequested: boolean;\n} & Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame' | 'openTransientTab'>) {\n const tail = useRunnerTail(sessionId, run.id, run.state === 'running');\n return (\n <Button\n variant=\"ghost\"\n size=\"card\"\n data-testid={`activity-runner-${run.id}`}\n data-runner-tone=\"running\"\n title=\"open this runner's log\"\n onClick={() => {\n if (sessionId === null) return;\n openTransientTab(runnerLogTab(run));\n }}\n className=\"min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel\"\n >\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <Dot tone=\"yellow\" pulse />\n <span className=\"min-w-0 flex-1 truncate text-left text-xs font-bold text-doom-hi\">{run.name}</span>\n <span className=\"shrink-0 text-2xs text-doom-faint\">\n {formatRunnerUptime(run.startedAt, now)}\n {run.interactive ? ' · tty' : ''}\n </span>\n {sessionId !== null ? (\n <Button\n asChild\n variant={stopRequested ? 'outline' : 'danger-outline'}\n size=\"xs\"\n data-testid={`activity-runner-stop-${run.id}`}\n data-stopping={stopRequested}\n title={\n stopRequested ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n className=\"px-1.5 text-2xs font-bold\"\n >\n {/* The row is already a button, so the stop control lends it\n the primitive's styling rather than nesting a second one. */}\n <span\n role=\"button\"\n tabIndex={stopRequested ? -1 : 0}\n aria-disabled={stopRequested}\n onClick={(event) => {\n event.stopPropagation();\n if (!stopRequested) requestRunnerStop(sendSessionFrame, sessionId, run.id);\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n event.stopPropagation();\n if (!stopRequested) requestRunnerStop(sendSessionFrame, sessionId, run.id);\n }}\n >\n {stopRequested ? 'stopping…' : 'stop'}\n </span>\n </Button>\n ) : null}\n </span>\n {/* What it is doing, falling back to what it was asked to do until the\n first line of output arrives. */}\n <span\n data-testid={`activity-runner-detail-${run.id}`}\n data-detail={tail === undefined ? 'command' : 'tail'}\n className=\"truncate pl-3 text-left text-2xs text-doom-faint\"\n >\n {tail ?? run.command}\n </span>\n </Button>\n );\n}\n","import { defineFill } from '@agimon-ai/doompi-core/web';\n\nimport { RunnersActivitySection } from './_components/RunnersActivitySection';\nexport default defineFill({ component: RunnersActivitySection });\n","/**\n * Pure view logic for the bash tool card, the browser counterpart of\n * src/tui/bashRender.ts. The plugin may reach only its own files and\n * src/types, so the few TUI helpers it mirrors are copied here rather than\n * imported, and every input is the wire JSON of a tool_execution frame.\n */\n\nconst ELLIPSIS = '…';\n/** Spaced so the join never glues two words into one unreadable token. */\nconst GAP = ' … ';\n/** Wide enough to keep a realistic command intact, short enough to stay on a few rows. */\nconst MAX_COMMAND_LENGTH = 160;\n/** Lines of log kept in the collapsed result, before the card is expanded. */\nconst COLLAPSED_TAIL_LINES = 12;\n/** Live output is unbounded while a command runs, so the running state keeps only a window. */\nconst STREAM_TAIL_LINES = 12;\nconst BYTES_PER_KB = 1024;\nconst BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;\n/** The user's home on the two platforms the runner ships for; the browser cannot ask the OS. */\nconst HOME_PATH = /(?<![\\w.-])(?:\\/Users|\\/home)\\/[^/\\s]+(?=\\/|\\s|$)/g;\n\n/** The bash tool's arguments as the frame carries them (src/schemas/bashTool.ts). */\nexport interface BashCallArgs {\n command?: unknown;\n timeout?: unknown;\n background?: unknown;\n interactive?: unknown;\n name?: unknown;\n}\n\n/** What formatRunResult reports, read only by the renderers. */\nexport interface BashResultDetails {\n id?: string;\n runner?: string;\n exitCode?: number | null;\n logPath?: string;\n fileSize?: number;\n lines?: number;\n timedOut?: boolean;\n promoted?: boolean;\n reason?: string;\n tail?: string;\n tailLines?: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Narrows the result details to the fields the card reads; anything else is ignored. */\nexport function bashResultDetails(details: unknown): BashResultDetails {\n if (!isRecord(details)) return {};\n const out: BashResultDetails = {};\n if (typeof details.id === 'string') out.id = details.id;\n if (typeof details.runner === 'string') out.runner = details.runner;\n if (typeof details.exitCode === 'number' || details.exitCode === null) out.exitCode = details.exitCode;\n if (typeof details.logPath === 'string') out.logPath = details.logPath;\n if (typeof details.fileSize === 'number') out.fileSize = details.fileSize;\n if (typeof details.lines === 'number') out.lines = details.lines;\n if (details.timedOut === true) out.timedOut = true;\n if (details.promoted === true) out.promoted = true;\n if (typeof details.reason === 'string') out.reason = details.reason;\n if (typeof details.tail === 'string') out.tail = details.tail;\n if (typeof details.tailLines === 'number') out.tailLines = details.tailLines;\n return out;\n}\n\n/** `/Users/me/x` becomes `~/x`. Absolute home paths are what push these lines onto extra rows. */\nexport function abbreviateHome(text: string): string {\n return text.replace(HOME_PATH, '~');\n}\n\n/** Collapse a heredoc or multi-line script to one scannable line plus a count. */\nexport function collapseCommand(command: string): string {\n const lines = command\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n const first = lines[0] ?? '';\n return lines.length > 1 ? `${first} ${ELLIPSIS} +${lines.length - 1} lines` : first;\n}\n\n/** Drop the middle rather than the end, so a long pipeline keeps both its source and its sink. */\nexport function truncateMiddle(text: string, max: number = MAX_COMMAND_LENGTH): string {\n if (text.length <= max) return text;\n const keep = max - GAP.length;\n const head = Math.ceil(keep / 2);\n let start = text.slice(0, head);\n let end = text.slice(text.length - (keep - head));\n\n // Snap both cuts to a token boundary, so neither side ends mid-word. Each is\n // only moved when a boundary exists in the half nearest the cut, otherwise a\n // single long argument would swallow the whole budget.\n const lastSpace = start.lastIndexOf(' ');\n if (lastSpace > start.length / 2) start = start.slice(0, lastSpace);\n const firstSpace = end.indexOf(' ');\n if (firstSpace >= 0 && firstSpace < end.length / 2) end = end.slice(firstSpace + 1);\n\n return `${start}${GAP}${end}`;\n}\n\nexport function formatBashCommand(command: string): string {\n return truncateMiddle(abbreviateHome(collapseCommand(command)));\n}\n\n/** Modifiers worth seeing at a glance, in the widget's `·` idiom. */\nexport function formatBashFlags(args: BashCallArgs): string[] {\n const flags: string[] = [];\n if (args.background === true) flags.push('bg');\n if (args.interactive === true) flags.push('tty');\n if (typeof args.timeout === 'number') flags.push(`${args.timeout}s`);\n if (typeof args.name === 'string' && args.name.length > 0) flags.push(args.name);\n return flags;\n}\n\n/** Human-readable byte count, matching how pi reports output sizes. */\nexport function formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\n/** The metadata footer, collapsed from five lines to one. */\nexport function formatResultSummary(details: BashResultDetails): string {\n const parts: string[] = [];\n if (details.lines !== undefined) parts.push(`${details.lines.toLocaleString('en-US')} lines`);\n if (details.fileSize !== undefined) parts.push(formatSize(details.fileSize));\n if (details.runner !== undefined) parts.push(details.runner);\n return parts.join(' · ');\n}\n\n/** Last `max` lines, so a chatty command cannot push the transcript off screen. */\nfunction lastLines(text: string, max: number): string[] {\n const all = text.split('\\n');\n while (all.length > 0 && all[all.length - 1]?.trim() === '') all.pop();\n return all.length <= max ? all : all.slice(-max);\n}\n\nexport type BashStatusTone = 'running' | 'ok' | 'error' | 'background';\n\nexport interface BashStatusLine {\n glyph: string;\n tone: BashStatusTone;\n text: string;\n}\n\n/** What the result body shows: the log lines, then the one-line status under them. */\nexport interface BashResultView {\n lines: string[];\n status: BashStatusLine | null;\n /** Lines the collapsed view is hiding, so the card can say so. */\n hidden: number;\n}\n\nexport interface BashResultInput {\n details: unknown;\n /** The result's text blocks joined, all a thrown failure leaves behind. */\n output: string;\n expanded: boolean;\n isPartial: boolean;\n isError: boolean;\n}\n\nexport function bashResultView(input: BashResultInput): BashResultView {\n const details = bashResultDetails(input.details);\n\n // Running. Details do not exist yet, so the streamed text is the only source,\n // and it is bounded here because it grows without limit while the command runs.\n if (input.isPartial) {\n return {\n lines: lastLines(input.output, STREAM_TAIL_LINES),\n status: { glyph: '◐', tone: 'running', text: 'running' },\n hidden: 0,\n };\n }\n\n // Failed. formatRunResult throws on a bad exit, so Pi hands back the error\n // text with no details at all; without this the block would show a bare tick.\n if (input.isError) {\n const all = lastLines(input.output, Number.MAX_SAFE_INTEGER);\n const lines = input.expanded ? all : all.slice(-COLLAPSED_TAIL_LINES);\n const summary = formatResultSummary(details);\n return {\n lines,\n status: { glyph: '✗', tone: 'error', text: summary.length > 0 ? summary : 'failed' },\n hidden: all.length - lines.length,\n };\n }\n\n if (details.promoted === true) {\n const label = details.runner ?? details.id ?? 'runner';\n const suffix = details.id === undefined ? '' : ` · ${details.id}`;\n return { lines: [], status: { glyph: '●', tone: 'background', text: `${label}${suffix} · background` }, hidden: 0 };\n }\n\n const all = lastLines(details.tail ?? '', Number.MAX_SAFE_INTEGER);\n const lines = input.expanded ? all : all.slice(-COLLAPSED_TAIL_LINES);\n // An absent exit code means the runner never reported one, which is not a failure.\n const exited = details.exitCode !== undefined && details.exitCode !== null;\n const failed = details.timedOut === true || (exited && details.exitCode !== 0);\n\n const meta: string[] = [];\n if (details.timedOut === true) meta.push('timed out');\n else if (failed) meta.push(`exit ${details.exitCode}`);\n const summary = formatResultSummary(details);\n if (summary.length > 0) meta.push(summary);\n\n const text = meta.length > 0 ? meta.join(' · ') : failed ? 'failed' : lines.length === 0 ? 'done' : '';\n return {\n lines,\n status: text.length > 0 ? { glyph: failed ? '✗' : '✓', tone: failed ? 'error' : 'ok', text } : null,\n hidden: all.length - lines.length,\n };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/doompi-core/web';\nimport {\n AnsiText,\n Button,\n FileIcon,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n type StatusTone,\n SyntaxText,\n toolTone,\n} from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { runnerLogTab } from '../../_components/RunnerLogPanel';\nimport { requestRunnerStop, runners } from '../../_lib/runnersStore';\nimport {\n type BashStatusTone,\n bashResultDetails,\n bashResultView,\n formatBashCommand,\n formatBashFlags,\n} from '../_lib/bashToolFormat';\n\nconst STATUS_TONE: Record<BashStatusTone, StatusTone> = {\n running: 'running',\n ok: 'ok',\n error: 'error',\n background: 'info',\n};\n\n/**\n * The bash tool's timeline item, the web half of renderBashCall and\n * renderBashResult. Collapsed it is a single header row: the tool name, the\n * command shortened to one line, its modifiers, and the outcome badge, so a\n * transcript of many calls stays scannable. Expanding it opens the body: the\n * command exactly as it was run, then the whole tail the frame carries, each\n * in its own scroll box so one chatty command cannot own the viewport. A\n * command promoted to a background runner offers the same stop the activity\n * dock does, for as long as the runners channel reports it running.\n */\nexport function BashToolMessage({\n sessionId,\n args,\n result,\n output,\n running,\n isError,\n sendSessionFrame,\n openTransientTab,\n}: ToolMessageRenderProps) {\n const details = bashResultDetails(result?.details);\n const runnerId = details.promoted === true ? details.id : undefined;\n // Every bash call is a runner and writes a log, promoted or not, so the log\n // control looks the run up by its own id; only stopping needs a promoted one.\n const run = useStore(runners.store, (state) =>\n details.id === undefined\n ? undefined\n : runners.select(state, sessionId).runs.find((entry) => entry.id === details.id),\n );\n const stopping = useStore(\n runners.store,\n (state) => runnerId !== undefined && runners.select(state, sessionId).stopRequested.includes(runnerId),\n );\n const rawCommand = typeof args.command === 'string' ? args.command : '';\n const command = formatBashCommand(rawCommand);\n const flags = formatBashFlags(args);\n const stoppable = sessionId !== null && runnerId !== undefined && run !== undefined && run.exit === undefined;\n\n return (\n <MessageItem tone={toolTone({ running, isError })} expandable>\n {({ expanded }) => {\n // Nothing is shown until the card is open, so the body always reads the\n // unclipped view; the collapsed state has no lines to bound.\n const view = bashResultView({\n details: result?.details,\n output,\n expanded: true,\n isPartial: running,\n isError,\n });\n return (\n <>\n <MessageItemHeader title=\"bash\">\n <span data-testid=\"tool-call-bash\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"min-w-0 flex-1 truncate font-mono text-doom-text\">{command}</span>\n {flags.length > 0 ? (\n <span className=\"hidden shrink-0 text-doom-faint sm:inline\">· {flags.join(' · ')}</span>\n ) : null}\n </span>\n {/* The log and the stop live in the header, not the body: a\n collapsed row still has to reach the whole log and still has\n to be able to stop a run it promoted to the background. */}\n {run !== undefined && sessionId !== null ? (\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"tool-result-bash-open-log\"\n title=\"open this runner's full log\"\n onClick={(event) => {\n event.stopPropagation();\n openTransientTab(runnerLogTab(run));\n }}\n className=\"shrink-0 gap-1 px-1.5 text-2xs font-bold\"\n >\n <FileIcon className=\"h-2.5 w-2.5\" />\n log\n </Button>\n ) : null}\n {stoppable ? (\n <Button\n size=\"xs\"\n variant={stopping ? 'outline' : 'danger-outline'}\n disabled={stopping}\n data-testid=\"tool-result-bash-stop\"\n data-stopping={stopping}\n title={\n stopping ? 'stop requested; the runner reports its own exit' : 'ask the runtime to stop this runner'\n }\n onClick={(event) => {\n event.stopPropagation();\n if (sessionId !== null && runnerId !== undefined)\n requestRunnerStop(sendSessionFrame, sessionId, runnerId);\n }}\n className=\"shrink-0 px-1.5 text-2xs font-bold\"\n >\n {stopping ? 'stopping…' : 'stop'}\n </Button>\n ) : null}\n </MessageItemHeader>\n {expanded ? (\n <MessageItemBody data-testid=\"tool-result-bash\" className=\"flex flex-col gap-2\">\n {/* The header had to shorten the command to one row; this is\n the command as it was actually run, wrapped rather than\n clipped and coloured as the shell it is. */}\n {rawCommand.length > 0 ? (\n <SyntaxText\n data-testid=\"tool-result-bash-command\"\n grammar=\"shell\"\n text={rawCommand}\n className=\"max-h-40 overflow-auto text-doom-text\"\n />\n ) : null}\n {/* The tail arrives with the colours the command wrote: the\n log keeps them, and only the model's copy is flattened. */}\n {view.lines.length > 0 ? (\n <AnsiText\n data-testid=\"tool-result-bash-output\"\n text={view.lines.join('\\n')}\n className=\"max-h-96 overflow-auto whitespace-pre-wrap break-words [overflow-wrap:anywhere] border-doom-border-soft border-t pt-2 font-mono text-doom-dim\"\n />\n ) : null}\n {view.status ? (\n <MessageItemStatus\n data-testid=\"tool-result-bash-status\"\n tone={STATUS_TONE[view.status.tone]}\n glyph={view.status.glyph}\n className=\"min-w-0\"\n >\n <span className=\"break-words\">{view.status.text}</span>\n </MessageItemStatus>\n ) : null}\n {details.logPath !== undefined ? (\n // The tail is bounded by the runner; the whole log only ever exists on\n // disk, so the item names the file rather than pretending this is all.\n <span data-testid=\"tool-result-bash-log\" className=\"min-w-0 break-all text-doom-faint\">\n full log · <span className=\"text-doom-dim\">{details.logPath}</span>\n </span>\n ) : null}\n </MessageItemBody>\n ) : null}\n </>\n );\n }}\n </MessageItem>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/doompi-core/web';\n\nimport { BashToolMessage } from './_components/BashToolMessage';\nexport default defineToolRenderer({ message: BashToolMessage });\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport activityGroupRunners from '../src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web';\nimport channelRunnerRuns from '../src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web';\nimport fillRunners from '../src/extensions/workspaces/sessions/(frontend)/fill/Runners.activity.runners.web';\nimport toolBash from '../src/extensions/workspaces/sessions/(frontend)/tool/bash.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'runner',\n session: {\n get activityGroups(): WebPluginContributions['activityGroups'] { return [via({ name: 'runners' }, activityGroupRunners)]; },\n get channels(): WebPluginContributions['channels'] { return [via({ channel: 'runner_runs' }, channelRunnerRuns)]; },\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'activity.runners', id: 'runners' }, fillRunners)]; },\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['bash'] }, toolBash)]; },\n },\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-runner",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.87",
|
|
4
4
|
"description": "Supervised shell execution, background process control, and run logs for Pi coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -228,11 +228,11 @@
|
|
|
228
228
|
"registry": "https://registry.npmjs.org/"
|
|
229
229
|
},
|
|
230
230
|
"dependencies": {
|
|
231
|
-
"@agimon-ai/doompi-core": "0.0.1-alpha.
|
|
232
|
-
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.
|
|
233
|
-
"@agimon-ai/doompi-ui": "0.0.1-alpha.
|
|
234
|
-
"@agimon-ai/doompi-web-components": "0.0.1-alpha.
|
|
235
|
-
"@agimon-ai/doompi-web-security": "0.0.1-alpha.
|
|
231
|
+
"@agimon-ai/doompi-core": "0.0.1-alpha.87",
|
|
232
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.81",
|
|
233
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.87",
|
|
234
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.45",
|
|
235
|
+
"@agimon-ai/doompi-web-security": "0.0.1-alpha.44",
|
|
236
236
|
"@agimon-ai/foundation-process-registry": "0.29.27",
|
|
237
237
|
"@deepseek-ai/cordis": "4.0.2",
|
|
238
238
|
"@rmux/sdk": "0.6.5",
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
"typebox": "1.3.34"
|
|
242
242
|
},
|
|
243
243
|
"devDependencies": {
|
|
244
|
-
"@agimon-ai/doompi-build": "0.0.1-alpha.
|
|
244
|
+
"@agimon-ai/doompi-build": "0.0.1-alpha.13",
|
|
245
245
|
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
246
246
|
"@earendil-works/pi-tui": "0.87.1",
|
|
247
247
|
"@tanstack/react-store": "0.11.1",
|
|
@@ -268,14 +268,14 @@
|
|
|
268
268
|
}
|
|
269
269
|
},
|
|
270
270
|
"optionalDependencies": {
|
|
271
|
-
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.
|
|
272
|
-
"@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.
|
|
273
|
-
"@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.
|
|
274
|
-
"@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.
|
|
275
|
-
"@agimon-ai/doompi-runner-rtk-darwin-arm64": "0.0.1-alpha.
|
|
276
|
-
"@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.
|
|
277
|
-
"@agimon-ai/doompi-runner-rtk-linux-arm64": "0.0.1-alpha.
|
|
278
|
-
"@agimon-ai/doompi-runner-rtk-linux-x64": "0.0.1-alpha.
|
|
271
|
+
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.79",
|
|
272
|
+
"@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.79",
|
|
273
|
+
"@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.79",
|
|
274
|
+
"@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.79",
|
|
275
|
+
"@agimon-ai/doompi-runner-rtk-darwin-arm64": "0.0.1-alpha.79",
|
|
276
|
+
"@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.79",
|
|
277
|
+
"@agimon-ai/doompi-runner-rtk-linux-arm64": "0.0.1-alpha.79",
|
|
278
|
+
"@agimon-ai/doompi-runner-rtk-linux-x64": "0.0.1-alpha.79"
|
|
279
279
|
},
|
|
280
280
|
"engines": {
|
|
281
281
|
"node": ">=22.19.0"
|