pi-ui-extend 1.0.41 → 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/app/app.d.ts +3 -0
- package/dist/app/app.js +26 -0
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +6 -1
- package/dist/app/commands/command-registry.js +1 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +59 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +19 -1
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/types.d.ts +5 -0
- package/dist/default-pix-config.js +10 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +80 -58
- package/dist/schemas/pi-tools-suite-schema.js +43 -78
- package/dist/schemas/pix-schema.d.ts +7 -0
- package/dist/schemas/pix-schema.js +14 -0
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +6 -0
- package/docs/desktop-mvp.md +12 -1
- package/docs/desktop-task-manager.md +93 -82
- package/external/pi-tools-suite/README.md +37 -50
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +19 -0
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +40 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +38 -27
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +14 -70
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +122 -312
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +15 -2
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +7 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -4
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/config.ts +41 -1
- package/external/pi-tools-suite/src/context-gateway/config.ts +11 -2
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +73 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +3 -3
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +9 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +96 -44
- package/external/pi-tools-suite/src/dcp/commands.ts +18 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +170 -45
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/config.ts +56 -13
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +1 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +13 -9
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -4
- package/package.json +4 -2
- package/schemas/pi-tools-suite.json +427 -280
- package/schemas/pix.json +24 -0
- package/schemas/tasks.json +132 -0
- package/skills/spec-wiki/SKILL.md +55 -0
- package/skills/spec-wiki/evals/evals.json +132 -0
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
- package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
- package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
- package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
- package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
- package/skills/spec-wiki/evals/trigger-evals.json +94 -0
- package/skills/spec-wiki/references/bootstrap.md +100 -0
- package/skills/spec-wiki/references/focused-spec.md +117 -0
- package/skills/spec-wiki/references/index-schema.md +330 -0
- package/skills/spec-wiki/references/maintenance.md +134 -0
- package/skills/spec-wiki/references/query.md +74 -0
- package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/spec-lite/SKILL.md +0 -140
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ On startup, Pix tries to link the bundled suite into the active Pi agent directo
|
|
|
95
95
|
| Context control | `dcp` | Explicit compression and context pruning for long sessions, including sidecar state. |
|
|
96
96
|
| Local web access | `web-search` | Web search and page extraction through a local Ollama instance. |
|
|
97
97
|
| Providers and quotas | `antigravity-auth`, `opencode-import`, `usage` | Antigravity OAuth/account failover, OpenCode credential import, and multi-provider usage views. |
|
|
98
|
-
| Reusable workflows | `prompt-commands`, `
|
|
98
|
+
| Reusable workflows | `prompt-commands`, `resource-registry`, `session-name` | Prompt-command CRUD, private Git-backed skill/agent install/update/push/remote-remove/local-uninstall with reload-on-change, project-scoped `.pi/tasks.jsonc` + `.pi/plans/` + `.pi/TODO.md` sync, a Desktop Registry sidebar for status/actions/configuration, and session naming. |
|
|
99
99
|
| Model compatibility | `coding-discipline`, `model-tools`, `codex-reasoning-fix` | Model-specific discipline and vision lookup, compatibility aliases, and a Codex reasoning payload workaround. |
|
|
100
100
|
|
|
101
101
|
Every module can be disabled. Optional integrations only activate when their requirements are available — for example, repository tools require an index, web tools require local Ollama web search, and LSP servers must be configured and trusted.
|
|
@@ -317,7 +317,7 @@ Type `/` for the live, searchable list. Extensions can add more commands than th
|
|
|
317
317
|
| Status | `/usage` |
|
|
318
318
|
| Process | `/quit`, `/exit` |
|
|
319
319
|
|
|
320
|
-
Useful suite commands include `/todos`, `/todos-persist`, `/todos-scope`, `/sub-status`, `/sub-stop`, `/ultrawork`, `/hyperplan`, `/subagent-preset`, `/usage`, `/dcp`, `/idx-init`, `/idx-update`, `/opencode-import`, `/antigravity-add-account`,
|
|
320
|
+
Useful suite commands include `/todos`, `/todos-persist`, `/todos-scope`, `/sub-status`, `/sub-stop`, `/ultrawork`, `/hyperplan`, `/subagent-preset`, `/usage`, `/dcp`, `/idx-init`, `/idx-update`, `/opencode-import`, `/antigravity-add-account`, and `/registry`.
|
|
321
321
|
|
|
322
322
|
### Keyboard and mouse
|
|
323
323
|
|
|
@@ -500,8 +500,16 @@ npm run test:tools-suite
|
|
|
500
500
|
|
|
501
501
|
# Build the publishable renderer
|
|
502
502
|
npm run build:pix
|
|
503
|
+
|
|
504
|
+
# Watch Pix, ACP, the bundled suite, and Desktop
|
|
505
|
+
npm run watch:all
|
|
503
506
|
```
|
|
504
507
|
|
|
508
|
+
`watch:all` rebuilds only the affected project parts. It keeps the last working
|
|
509
|
+
Desktop process alive while changes compile, then replaces it only after the
|
|
510
|
+
entire queued build succeeds. A failed build is reported without replacing the
|
|
511
|
+
running Desktop.
|
|
512
|
+
|
|
505
513
|
Regenerate the README screenshots without accounts or live model traffic:
|
|
506
514
|
|
|
507
515
|
```bash
|
package/dist/app/app.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare class PiUiExtendApp {
|
|
|
51
51
|
* for one tab never reaches another tab's extensions.
|
|
52
52
|
*/
|
|
53
53
|
private readonly extensionEventBusByRuntime;
|
|
54
|
+
private readonly subagentCatalogBySessionId;
|
|
54
55
|
private readonly inputEditor;
|
|
55
56
|
private lastInputEditorContentVersion;
|
|
56
57
|
private readonly requestHistory;
|
|
@@ -78,6 +79,8 @@ export declare class PiUiExtendApp {
|
|
|
78
79
|
private awaitCurrentSessionExtensions;
|
|
79
80
|
private activateRuntime;
|
|
80
81
|
private createExtensionEventBus;
|
|
82
|
+
private subagentTypesForRuntime;
|
|
83
|
+
private contextInventoryText;
|
|
81
84
|
private handleTerminalBellAttention;
|
|
82
85
|
private afterSessionReplacement;
|
|
83
86
|
private requireRuntime;
|
package/dist/app/app.js
CHANGED
|
@@ -47,6 +47,8 @@ import { checkPiUpdate, checkPixUpdate, formatPixStartupUpdateDialog, formatPiSt
|
|
|
47
47
|
import { checkAndUpdateIdxOnStartup, formatIdxStartupUpdateNotice } from "./cli/startup-checks.js";
|
|
48
48
|
import { AppVoiceController } from "./input/voice-controller.js";
|
|
49
49
|
import { createIsolatedExtensionEventBus } from "./extensions/extension-event-bus.js";
|
|
50
|
+
import { parseSubagentCatalogState, SUBAGENTS_CATALOG_STATE_EVENT, } from "./extensions/subagent-catalog-state.js";
|
|
51
|
+
import { createReloadContextInventory, formatReloadContextInventory } from "./commands/reload-context-inventory.js";
|
|
50
52
|
import { setAppIconTheme } from "./icons.js";
|
|
51
53
|
const TERMINAL_BELL_ATTENTION_EVENT = "pix:terminal-bell:attention";
|
|
52
54
|
function normalizeJumpTargetText(text) {
|
|
@@ -123,6 +125,7 @@ export class PiUiExtendApp {
|
|
|
123
125
|
* for one tab never reaches another tab's extensions.
|
|
124
126
|
*/
|
|
125
127
|
extensionEventBusByRuntime = new WeakMap();
|
|
128
|
+
subagentCatalogBySessionId = new Map();
|
|
126
129
|
inputEditor = new InputEditor();
|
|
127
130
|
lastInputEditorContentVersion = this.inputEditor.contentVersion;
|
|
128
131
|
requestHistory;
|
|
@@ -221,6 +224,7 @@ export class PiUiExtendApp {
|
|
|
221
224
|
restoreAutoUserMessages: (messages) => this.queuedMessages.restoreAutoUserMessages(messages),
|
|
222
225
|
captureDeferredUserMessages: () => this.queuedMessages.captureDeferredUserMessages(),
|
|
223
226
|
restoreDeferredUserMessages: (messages) => this.queuedMessages.restoreDeferredUserMessages(messages),
|
|
227
|
+
contextInventoryText: (runtime, heading) => this.contextInventoryText(runtime, heading),
|
|
224
228
|
addEntry: (entry) => this.addEntry(entry),
|
|
225
229
|
showToast: (message, kind) => this.showToast(message, kind),
|
|
226
230
|
render: () => this.render(),
|
|
@@ -358,6 +362,7 @@ export class PiUiExtendApp {
|
|
|
358
362
|
this.extensionActions = new AppExtensionActionsController({
|
|
359
363
|
isRunning: () => this.running,
|
|
360
364
|
runtime: () => this.runtime,
|
|
365
|
+
subagentTypes: (runtime) => this.subagentTypesForRuntime(runtime),
|
|
361
366
|
getInput: () => this.input,
|
|
362
367
|
setInput: (value) => this.setInput(value),
|
|
363
368
|
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
@@ -508,6 +513,7 @@ export class PiUiExtendApp {
|
|
|
508
513
|
this.commandController = new AppCommandController({
|
|
509
514
|
options: this.options,
|
|
510
515
|
runtime: () => this.runtime,
|
|
516
|
+
subagentTypes: (runtime) => this.subagentTypesForRuntime(runtime),
|
|
511
517
|
requestHistory: () => this.requestHistory,
|
|
512
518
|
getInput: () => this.input,
|
|
513
519
|
setInput: (value) => this.setInput(value),
|
|
@@ -800,6 +806,7 @@ export class PiUiExtendApp {
|
|
|
800
806
|
handleExtensionError: (error) => this.extensionActions.handleExtensionError(error),
|
|
801
807
|
handleSessionEvent: (event) => this.handleSessionEvent(event),
|
|
802
808
|
bindAgentPause: (session) => this.agentPauseController.bind(session),
|
|
809
|
+
contextInventoryText: (runtime, heading) => this.contextInventoryText(runtime, heading),
|
|
803
810
|
addEntry: (entry) => this.addEntry(entry),
|
|
804
811
|
setStatus: (status) => this.setStatus(status),
|
|
805
812
|
showToast: (message, kind) => this.showToast(message, kind),
|
|
@@ -945,10 +952,29 @@ export class PiUiExtendApp {
|
|
|
945
952
|
this.handleTerminalBellAttention(data);
|
|
946
953
|
if (channel === SUBAGENTS_LIVE_STATE_EVENT)
|
|
947
954
|
this.subagentsWidgetController.observeLiveState(data);
|
|
955
|
+
if (channel === SUBAGENTS_CATALOG_STATE_EVENT) {
|
|
956
|
+
const catalog = parseSubagentCatalogState(data);
|
|
957
|
+
if (catalog?.sessionId)
|
|
958
|
+
this.subagentCatalogBySessionId.set(catalog.sessionId, catalog);
|
|
959
|
+
}
|
|
948
960
|
if (channel === TODO_STATE_EVENT)
|
|
949
961
|
this.todoWidgetController.observeLiveState(data);
|
|
950
962
|
});
|
|
951
963
|
}
|
|
964
|
+
subagentTypesForRuntime(runtime) {
|
|
965
|
+
const sessionId = runtime.session.sessionManager.getSessionId();
|
|
966
|
+
const catalog = this.subagentCatalogBySessionId.get(sessionId);
|
|
967
|
+
if (!catalog)
|
|
968
|
+
return undefined;
|
|
969
|
+
const model = runtime.session.model;
|
|
970
|
+
const currentModelRef = model ? `${model.provider}/${model.id}` : undefined;
|
|
971
|
+
if (catalog.model && currentModelRef && catalog.model !== currentModelRef)
|
|
972
|
+
return undefined;
|
|
973
|
+
return catalog.types;
|
|
974
|
+
}
|
|
975
|
+
contextInventoryText(runtime, heading) {
|
|
976
|
+
return formatReloadContextInventory(createReloadContextInventory(runtime, this.subagentTypesForRuntime(runtime)), heading);
|
|
977
|
+
}
|
|
952
978
|
handleTerminalBellAttention(data) {
|
|
953
979
|
const sessionFile = isRecord(data) && typeof data.sessionFile === "string" ? data.sessionFile : undefined;
|
|
954
980
|
this.tabsController.markTerminalBellAttention(sessionFile);
|
|
@@ -7,6 +7,7 @@ export type DirectPopupMenu = Exclude<ActivePopupMenu, "slash">;
|
|
|
7
7
|
export type CommandControllerHost = {
|
|
8
8
|
readonly options: AppOptions;
|
|
9
9
|
runtime(): AgentSessionRuntime | undefined;
|
|
10
|
+
subagentTypes?(runtime: AgentSessionRuntime): readonly string[] | undefined;
|
|
10
11
|
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
11
12
|
requestHistory(): AppRequestHistory;
|
|
12
13
|
getInput(): string;
|
|
@@ -4,6 +4,7 @@ import { getProjectPixConfigPath, savePixAutocompleteModel, savePixDefaultModel,
|
|
|
4
4
|
import { createId } from "../id.js";
|
|
5
5
|
import { isThinkingLevel, parseScopedModelRef } from "../model/model-ref.js";
|
|
6
6
|
import { appendPixSystemDisplayEntry } from "../session/pix-system-message.js";
|
|
7
|
+
import { createReloadContextInventory, formatReloadContextInventory } from "./reload-context-inventory.js";
|
|
7
8
|
export class ModelCommandActions {
|
|
8
9
|
host;
|
|
9
10
|
constructor(host) {
|
|
@@ -333,10 +334,14 @@ export class ModelCommandActions {
|
|
|
333
334
|
await session.reload();
|
|
334
335
|
if (!isCommandScopeActive(this.host, scope))
|
|
335
336
|
return;
|
|
337
|
+
const runtime = scope.runtime;
|
|
338
|
+
const text = runtime
|
|
339
|
+
? formatReloadContextInventory(createReloadContextInventory(runtime, this.host.subagentTypes?.(runtime)), `Reloaded resources after model change to ${ref}`)
|
|
340
|
+
: `Reloaded resources after model change to ${ref}`;
|
|
336
341
|
this.host.addEntry({
|
|
337
342
|
id: createId("system"),
|
|
338
343
|
kind: "system",
|
|
339
|
-
text
|
|
344
|
+
text,
|
|
340
345
|
});
|
|
341
346
|
this.host.toast.success("Model changed and resources reloaded");
|
|
342
347
|
}
|
|
@@ -217,7 +217,7 @@ export function createSlashCommands(actions, host) {
|
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
name: "reload",
|
|
220
|
-
description: "Reload
|
|
220
|
+
description: "Reload resources and show model-available skills, tools, and agents",
|
|
221
221
|
kind: "builtin",
|
|
222
222
|
keywords: ["restart", "refresh", "resources", "extensions", "skills", "prompts"],
|
|
223
223
|
run: () => actions.runReloadCommand(),
|
|
@@ -11,6 +11,7 @@ import { copyTextToClipboard } from "../screen/clipboard.js";
|
|
|
11
11
|
import { formatAccountUsageReport, queryAccountUsageReport } from "../model/model-usage-status.js";
|
|
12
12
|
import { checkGlobalPiInstall, checkPixUpdate, formatGlobalPiCheck, formatPixUpdateCheck, parsePixUpdateArgs, pixUpdateUsage, } from "../cli/update.js";
|
|
13
13
|
import { createStartupInfoMessage } from "../cli/startup-info.js";
|
|
14
|
+
import { createReloadContextInventory, formatReloadContextInventory } from "./reload-context-inventory.js";
|
|
14
15
|
import { getCompleteSessionStats } from "../session/session-stats.js";
|
|
15
16
|
import { loadSessionTitleConfig } from "../../bundled-extensions/session-title/config.js";
|
|
16
17
|
import { fallbackSessionTitleFromInput, firstUserMessageText, generateSessionTitleWithRuntime, sessionTitleModelRefs, } from "../../bundled-extensions/session-title/title-generation.js";
|
|
@@ -309,7 +310,8 @@ export class SessionCommandActions {
|
|
|
309
310
|
if (!isCommandScopeActive(this.host, scope))
|
|
310
311
|
return;
|
|
311
312
|
this.host.setSessionStatus(runtime.session);
|
|
312
|
-
this.host.
|
|
313
|
+
const inventory = createReloadContextInventory(runtime, this.host.subagentTypes?.(runtime));
|
|
314
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: formatReloadContextInventory(inventory) });
|
|
313
315
|
this.host.toast.success("Reloaded resources");
|
|
314
316
|
}
|
|
315
317
|
catch (error) {
|
|
@@ -391,7 +393,7 @@ export class SessionCommandActions {
|
|
|
391
393
|
this.host.addEntry({
|
|
392
394
|
id: createId("system"),
|
|
393
395
|
kind: "system",
|
|
394
|
-
text: `Deleted session ${targetSessionId}. Started a new session. cwd=${runtime.cwd}
|
|
396
|
+
text: formatReloadContextInventory(createReloadContextInventory(runtime, this.host.subagentTypes?.(runtime)), `Deleted session ${targetSessionId}. Started a new session. cwd=${runtime.cwd}`),
|
|
395
397
|
});
|
|
396
398
|
if (runtime.modelFallbackMessage)
|
|
397
399
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
@@ -427,7 +429,11 @@ export class SessionCommandActions {
|
|
|
427
429
|
return;
|
|
428
430
|
}
|
|
429
431
|
this.host.resetSessionView();
|
|
430
|
-
this.host.addEntry({
|
|
432
|
+
this.host.addEntry({
|
|
433
|
+
id: createId("system"),
|
|
434
|
+
kind: "system",
|
|
435
|
+
text: formatReloadContextInventory(createReloadContextInventory(runtime, this.host.subagentTypes?.(runtime)), `Started a new session. cwd=${runtime.cwd}`),
|
|
436
|
+
});
|
|
431
437
|
if (runtime.modelFallbackMessage)
|
|
432
438
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
433
439
|
for (const diag of runtime.diagnostics ?? []) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export interface ReloadContextInventory {
|
|
3
|
+
readonly model: string;
|
|
4
|
+
readonly skills: readonly string[];
|
|
5
|
+
readonly tools: readonly string[];
|
|
6
|
+
readonly agents: readonly string[] | undefined;
|
|
7
|
+
readonly skillsReadable: boolean;
|
|
8
|
+
readonly subagentsActive: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function createReloadContextInventory(runtime: AgentSessionRuntime, agentTypes: readonly string[] | undefined): ReloadContextInventory;
|
|
11
|
+
export declare function formatReloadContextInventory(inventory: ReloadContextInventory, heading?: string): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function createReloadContextInventory(runtime, agentTypes) {
|
|
2
|
+
const session = runtime.session;
|
|
3
|
+
const tools = unique(session.getActiveToolNames());
|
|
4
|
+
const skillsReadable = tools.includes("read") || tools.includes("bash");
|
|
5
|
+
const subagentsActive = tools.includes("subagents");
|
|
6
|
+
const skills = skillsReadable
|
|
7
|
+
? unique(session.resourceLoader.getSkills().skills.map((skill) => skill.name)).sort(compareText)
|
|
8
|
+
: [];
|
|
9
|
+
const agents = subagentsActive && agentTypes !== undefined
|
|
10
|
+
? unique(agentTypes).sort(compareText)
|
|
11
|
+
: subagentsActive
|
|
12
|
+
? undefined
|
|
13
|
+
: [];
|
|
14
|
+
return {
|
|
15
|
+
model: currentModelLabel(runtime),
|
|
16
|
+
skills,
|
|
17
|
+
tools,
|
|
18
|
+
agents,
|
|
19
|
+
skillsReadable,
|
|
20
|
+
subagentsActive,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function formatReloadContextInventory(inventory, heading = "Reloaded keybindings, extensions, skills, prompts, themes") {
|
|
24
|
+
return [
|
|
25
|
+
heading,
|
|
26
|
+
"",
|
|
27
|
+
`Model: ${inventory.model}`,
|
|
28
|
+
"",
|
|
29
|
+
`Skills (in context): ${inventory.skillsReadable ? listOrNone(inventory.skills) : "(none; read/bash inactive)"}`,
|
|
30
|
+
"",
|
|
31
|
+
`Tools (active): ${listOrNone(inventory.tools)}`,
|
|
32
|
+
"",
|
|
33
|
+
`Agents (available): ${formatAgents(inventory)}`,
|
|
34
|
+
].join("\n");
|
|
35
|
+
}
|
|
36
|
+
function currentModelLabel(runtime) {
|
|
37
|
+
const model = runtime.session.model;
|
|
38
|
+
if (!model)
|
|
39
|
+
return "unavailable";
|
|
40
|
+
const thinking = runtime.session.thinkingLevel;
|
|
41
|
+
const modelRef = `${model.provider}/${model.id}`;
|
|
42
|
+
return !thinking || thinking === "off" ? modelRef : `${modelRef}:${thinking}`;
|
|
43
|
+
}
|
|
44
|
+
function formatAgents(inventory) {
|
|
45
|
+
if (!inventory.subagentsActive)
|
|
46
|
+
return "(none; subagents tool inactive)";
|
|
47
|
+
if (inventory.agents === undefined)
|
|
48
|
+
return "(catalog unavailable)";
|
|
49
|
+
return listOrNone(inventory.agents);
|
|
50
|
+
}
|
|
51
|
+
function listOrNone(values) {
|
|
52
|
+
return values.length > 0 ? values.join(", ") : "(none)";
|
|
53
|
+
}
|
|
54
|
+
function unique(values) {
|
|
55
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
56
|
+
}
|
|
57
|
+
function compareText(left, right) {
|
|
58
|
+
return left.localeCompare(right);
|
|
59
|
+
}
|
|
@@ -4,6 +4,7 @@ import type { Entry } from "../types.js";
|
|
|
4
4
|
export type AppExtensionActionsHost = {
|
|
5
5
|
isRunning(): boolean;
|
|
6
6
|
runtime(): AgentSessionRuntime | undefined;
|
|
7
|
+
subagentTypes?(runtime: AgentSessionRuntime): readonly string[] | undefined;
|
|
7
8
|
getInput(): string;
|
|
8
9
|
setInput(value: string): void;
|
|
9
10
|
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
2
|
import { createId } from "../id.js";
|
|
3
3
|
import { logPixEvent } from "../logger.js";
|
|
4
|
+
import { createReloadContextInventory, formatReloadContextInventory } from "../commands/reload-context-inventory.js";
|
|
4
5
|
export class AppExtensionActionsController {
|
|
5
6
|
host;
|
|
6
7
|
logExtensionError;
|
|
@@ -60,6 +61,11 @@ export class AppExtensionActionsController {
|
|
|
60
61
|
if (!this.isSessionActive(runtime, session))
|
|
61
62
|
return;
|
|
62
63
|
this.host.setSessionStatus(session);
|
|
64
|
+
this.host.addEntry({
|
|
65
|
+
id: createId("system"),
|
|
66
|
+
kind: "system",
|
|
67
|
+
text: formatReloadContextInventory(createReloadContextInventory(runtime, this.host.subagentTypes?.(runtime)), "Reloaded resources"),
|
|
68
|
+
});
|
|
63
69
|
this.host.showToast("Reloaded resources", "success");
|
|
64
70
|
this.host.render();
|
|
65
71
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const SUBAGENTS_CATALOG_STATE_EVENT = "pi-tools-suite:async-subagents:catalog";
|
|
2
|
+
export interface SubagentCatalogState {
|
|
3
|
+
readonly version: 1;
|
|
4
|
+
readonly sessionId?: string;
|
|
5
|
+
readonly sessionFile?: string;
|
|
6
|
+
readonly model?: string;
|
|
7
|
+
readonly types: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function parseSubagentCatalogState(value: unknown): SubagentCatalogState | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const SUBAGENTS_CATALOG_STATE_EVENT = "pi-tools-suite:async-subagents:catalog";
|
|
2
|
+
export function parseSubagentCatalogState(value) {
|
|
3
|
+
if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.types))
|
|
4
|
+
return undefined;
|
|
5
|
+
if (!value.types.every((type) => typeof type === "string" && type.trim().length > 0))
|
|
6
|
+
return undefined;
|
|
7
|
+
if (value.sessionId !== undefined && typeof value.sessionId !== "string")
|
|
8
|
+
return undefined;
|
|
9
|
+
if (value.sessionFile !== undefined && typeof value.sessionFile !== "string")
|
|
10
|
+
return undefined;
|
|
11
|
+
if (value.model !== undefined && typeof value.model !== "string")
|
|
12
|
+
return undefined;
|
|
13
|
+
return {
|
|
14
|
+
version: 1,
|
|
15
|
+
...(value.sessionId ? { sessionId: value.sessionId } : {}),
|
|
16
|
+
...(value.sessionFile ? { sessionFile: value.sessionFile } : {}),
|
|
17
|
+
...(value.model ? { model: value.model } : {}),
|
|
18
|
+
types: [...new Set(value.types.map((type) => type.trim()))],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
@@ -2,7 +2,7 @@ import { stringDisplayWidth } from "../../terminal-width.js";
|
|
|
2
2
|
import { SUBAGENTS_WIDGET_MAX_ROWS } from "../constants.js";
|
|
3
3
|
import { ellipsizeDisplay, padOrTrimPlain, sanitizeText, wrapLine } from "./render-text.js";
|
|
4
4
|
import { thinkingLevelThemeColor } from "./status-line-renderer.js";
|
|
5
|
-
import { activeSubagentStates, formatElapsedSince, formatSubagentsPanelStats, subagentIcon, subagentModelThinkingLabel, subagentRunName, taskPreviewMap, } from "../subagents/subagents-model.js";
|
|
5
|
+
import { activeSubagentStates, formatElapsedSince, formatSubagentLastActivity, formatSubagentsPanelStats, subagentIcon, subagentModelThinkingLabel, subagentRunName, taskPreviewMap, } from "../subagents/subagents-model.js";
|
|
6
6
|
import { formatTodoPanelStats, formatTodoTaskLine, hasOpenTodoTasks, shiftSegmentsToSlice, todoTaskLineSegments, visibleTodoTaskRows, visibleTodoTasks, } from "../todo/todo-model.js";
|
|
7
7
|
export function renderTodoPanel(details, expanded, width, colors) {
|
|
8
8
|
if (!details)
|
|
@@ -110,14 +110,16 @@ export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
|
110
110
|
const icon = subagentIcon(preview);
|
|
111
111
|
const runLabel = visibleRun.showRunLabel ? `${subagentRunName(visibleRun.runDir)} ` : "";
|
|
112
112
|
const prefix = `${runLabel}${icon} ${agent.id} ${model} `;
|
|
113
|
-
const
|
|
113
|
+
const activity = formatSubagentLastActivity(agent.lastActivity);
|
|
114
|
+
const elapsed = formatElapsedSince(agent.startedAt, now);
|
|
115
|
+
const suffix = activity ? ` ${activity} ${elapsed}` : ` ${elapsed}`;
|
|
114
116
|
const taskWidth = Math.max(8, rowWidth - stringDisplayWidth(prefix) - stringDisplayWidth(suffix));
|
|
115
117
|
const taskText = ellipsizeDisplay(task, taskWidth);
|
|
116
118
|
const text = `${prefix}${taskText}${suffix}`;
|
|
117
119
|
lines.push({
|
|
118
120
|
text: padOrTrimPlain(text, width),
|
|
119
121
|
colorOverride: colors.muted,
|
|
120
|
-
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, runLabel, status: agent.status }, colors),
|
|
122
|
+
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, runLabel, activity, elapsed, status: agent.status }, colors),
|
|
121
123
|
target,
|
|
122
124
|
});
|
|
123
125
|
}
|
|
@@ -136,13 +138,17 @@ function subagentPanelLineSegments(input, colors) {
|
|
|
136
138
|
const modelStart = input.text.indexOf(input.model, nameStart + input.agentId.length);
|
|
137
139
|
const taskStart = input.prefix.length;
|
|
138
140
|
const suffixStart = taskStart + input.taskText.length;
|
|
141
|
+
const elapsedStart = input.text.length - input.elapsed.length;
|
|
142
|
+
const activityStart = input.activity ? elapsedStart - input.activity.length - 1 : -1;
|
|
139
143
|
return [
|
|
140
144
|
...(runLabelStart >= 0 ? [{ start: runLabelStart, end: runLabelStart + input.runLabel.length, foreground: colors.warning }] : []),
|
|
141
145
|
{ start: iconStart, end: iconStart + input.icon.length, foreground: subagentStatusColor(input.status, colors), bold: true },
|
|
142
146
|
{ start: nameStart, end: nameStart + input.agentId.length, foreground: colors.accent, bold: true },
|
|
143
147
|
{ start: modelStart, end: modelStart + input.model.length, foreground: colors.info },
|
|
144
148
|
{ start: taskStart, end: suffixStart, foreground: colors.muted },
|
|
145
|
-
{ start: suffixStart, end: input.text.length, foreground: colors.muted },
|
|
149
|
+
{ start: suffixStart, end: input.activity ? activityStart : input.text.length, foreground: colors.muted },
|
|
150
|
+
...(input.activity ? [{ start: activityStart, end: activityStart + input.activity.length, foreground: colors.info }] : []),
|
|
151
|
+
...(input.activity ? [{ start: activityStart + input.activity.length, end: input.text.length, foreground: colors.muted }] : []),
|
|
146
152
|
].filter((segment) => segment.start >= 0 && segment.end > segment.start);
|
|
147
153
|
}
|
|
148
154
|
function subagentStatusColor(status, colors) {
|
|
@@ -27,6 +27,7 @@ export type AppSessionLifecycleHost = {
|
|
|
27
27
|
handleExtensionError(error: ExtensionError): void;
|
|
28
28
|
handleSessionEvent(event: AgentSessionEvent): void;
|
|
29
29
|
bindAgentPause(session: AgentSession): void;
|
|
30
|
+
contextInventoryText?(runtime: AgentSessionRuntime, heading: string): string;
|
|
30
31
|
addEntry(entry: Entry): void;
|
|
31
32
|
setStatus(status: string): void;
|
|
32
33
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
@@ -44,6 +44,12 @@ export class AppSessionLifecycleController {
|
|
|
44
44
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
45
45
|
});
|
|
46
46
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
47
|
+
if (runtime.session.messages.length === 0) {
|
|
48
|
+
await this.awaitCurrentSessionExtensions(runtime);
|
|
49
|
+
const text = this.host.contextInventoryText?.(runtime, `Empty tab ready. cwd=${runtime.cwd}`);
|
|
50
|
+
if (text)
|
|
51
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
52
|
+
}
|
|
47
53
|
if (runtime.modelFallbackMessage) {
|
|
48
54
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
49
55
|
}
|
|
@@ -43,6 +43,7 @@ export type AppTabsControllerHost = {
|
|
|
43
43
|
restoreAutoUserMessages?(messages: readonly SubmittedUserMessage[]): void;
|
|
44
44
|
captureDeferredUserMessages?(): readonly SubmittedUserMessage[];
|
|
45
45
|
restoreDeferredUserMessages?(messages: readonly SubmittedUserMessage[]): void;
|
|
46
|
+
contextInventoryText?(runtime: AgentSessionRuntime, heading: string): string;
|
|
46
47
|
addEntry(entry: Entry): void;
|
|
47
48
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
48
49
|
render(): void;
|
|
@@ -350,6 +350,12 @@ export class AppTabsController {
|
|
|
350
350
|
this.scheduleProjectSessionRetention();
|
|
351
351
|
this.scheduleTabPrewarm();
|
|
352
352
|
await this.loadActiveSessionHistory(restoredRuntime, generation);
|
|
353
|
+
if (restoredRuntime.session.messages.length === 0) {
|
|
354
|
+
await this.host.awaitCurrentSessionExtensions?.(restoredRuntime);
|
|
355
|
+
const text = this.host.contextInventoryText?.(restoredRuntime, `Opened an empty tab. cwd=${restoredRuntime.cwd}`);
|
|
356
|
+
if (text)
|
|
357
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
358
|
+
}
|
|
353
359
|
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths, generation);
|
|
354
360
|
}
|
|
355
361
|
openNewTab() {
|
|
@@ -436,6 +442,7 @@ export class AppTabsController {
|
|
|
436
442
|
this.host.render();
|
|
437
443
|
try {
|
|
438
444
|
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
445
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
439
446
|
}
|
|
440
447
|
catch (error) {
|
|
441
448
|
if (this.pendingActiveTabId === targetTab.id)
|
|
@@ -477,7 +484,12 @@ export class AppTabsController {
|
|
|
477
484
|
this.scheduleProjectSessionRetention();
|
|
478
485
|
this.host.resetSessionView();
|
|
479
486
|
this.restoreDeferredUserMessages(targetTab.id);
|
|
480
|
-
this.host.addEntry({
|
|
487
|
+
this.host.addEntry({
|
|
488
|
+
id: createId("system"),
|
|
489
|
+
kind: "system",
|
|
490
|
+
text: this.host.contextInventoryText?.(newRuntime, `Opened a new tab. cwd=${newRuntime.cwd}`)
|
|
491
|
+
?? `Opened a new tab. cwd=${newRuntime.cwd}`,
|
|
492
|
+
});
|
|
481
493
|
if (newRuntime.modelFallbackMessage)
|
|
482
494
|
this.host.addEntry({ id: createId("system"), kind: "system", text: newRuntime.modelFallbackMessage });
|
|
483
495
|
for (const diag of newRuntime.diagnostics ?? []) {
|
|
@@ -571,6 +583,7 @@ export class AppTabsController {
|
|
|
571
583
|
this.host.render();
|
|
572
584
|
try {
|
|
573
585
|
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
586
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
574
587
|
}
|
|
575
588
|
catch {
|
|
576
589
|
this.pendingActiveTabId = undefined;
|
|
@@ -615,6 +628,11 @@ export class AppTabsController {
|
|
|
615
628
|
void this.saveTabs();
|
|
616
629
|
this.scheduleTabPrewarm();
|
|
617
630
|
await this.loadActiveSessionHistory(newRuntime, generation);
|
|
631
|
+
if (newRuntime.session.messages.length === 0) {
|
|
632
|
+
const text = this.host.contextInventoryText?.(newRuntime, `Opened an empty tab. cwd=${newRuntime.cwd}`);
|
|
633
|
+
if (text)
|
|
634
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
635
|
+
}
|
|
618
636
|
return true;
|
|
619
637
|
}
|
|
620
638
|
forkSessionEntryInNewTab(entryId) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
1
|
+
import { open, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { SUBAGENT_PARENT_SESSION_FILE, SUBAGENTS_REGISTRY_FILE, SUBAGENTS_RUN_ROOT } from "../constants.js";
|
|
4
4
|
import { isSubagentRegistry } from "./subagents-model.js";
|
|
5
|
+
const LAST_ACTIVITY_TAIL_BYTES = 64 * 1024;
|
|
5
6
|
export function subagentsRegistryPath(cwd) {
|
|
6
7
|
return join(cwd, SUBAGENTS_RUN_ROOT, SUBAGENTS_REGISTRY_FILE);
|
|
7
8
|
}
|
|
@@ -110,6 +111,9 @@ async function readSubagentAgentState(runDir, agentId, options) {
|
|
|
110
111
|
const finishedAt = await readTrimmedFile(join(agentDir, "finished_at"));
|
|
111
112
|
if (finishedAt)
|
|
112
113
|
state.finishedAt = finishedAt;
|
|
114
|
+
const lastActivity = await readLastActivity(join(agentDir, "progress.jsonl"));
|
|
115
|
+
if (lastActivity)
|
|
116
|
+
state.lastActivity = lastActivity;
|
|
113
117
|
const retryPending = await fileExists(join(agentDir, "retry_pending"));
|
|
114
118
|
const stopRequested = await fileExists(join(agentDir, "stop_requested"));
|
|
115
119
|
if (retryPending && !stopRequested && state.status !== "running" && state.status !== "done") {
|
|
@@ -179,6 +183,61 @@ async function readNumberFile(filePath) {
|
|
|
179
183
|
const value = Number.parseInt(raw, 10);
|
|
180
184
|
return Number.isFinite(value) ? value : undefined;
|
|
181
185
|
}
|
|
186
|
+
async function readLastActivity(filePath) {
|
|
187
|
+
let handle;
|
|
188
|
+
try {
|
|
189
|
+
handle = await open(filePath, "r");
|
|
190
|
+
const size = (await handle.stat()).size;
|
|
191
|
+
if (size <= 0)
|
|
192
|
+
return undefined;
|
|
193
|
+
const start = Math.max(0, size - LAST_ACTIVITY_TAIL_BYTES);
|
|
194
|
+
const buffer = Buffer.allocUnsafe(size - start);
|
|
195
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
|
|
196
|
+
if (bytesRead <= 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
const lines = buffer.toString("utf8", 0, bytesRead).split(/\r?\n/);
|
|
199
|
+
if (start > 0) {
|
|
200
|
+
const previousByte = Buffer.allocUnsafe(1);
|
|
201
|
+
const previousRead = await handle.read(previousByte, 0, 1, start - 1);
|
|
202
|
+
if (previousRead.bytesRead === 1 && previousByte[0] !== 0x0a)
|
|
203
|
+
lines.shift();
|
|
204
|
+
}
|
|
205
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
206
|
+
const line = lines[index]?.trim();
|
|
207
|
+
if (!line)
|
|
208
|
+
continue;
|
|
209
|
+
try {
|
|
210
|
+
const value = JSON.parse(line);
|
|
211
|
+
if (!isProgressActivityRecord(value))
|
|
212
|
+
continue;
|
|
213
|
+
const toolName = typeof value.toolName === "string" ? value.toolName.trim() : "";
|
|
214
|
+
if (toolName)
|
|
215
|
+
return { label: toolName, at: value.at };
|
|
216
|
+
if (value.type === "message_start" || value.type === "message_end")
|
|
217
|
+
return { label: "Thinking", at: value.at };
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Ignore malformed or partially written tail lines and keep scanning.
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
await handle?.close().catch(() => undefined);
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
function isProgressActivityRecord(value) {
|
|
233
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
234
|
+
return false;
|
|
235
|
+
const record = value;
|
|
236
|
+
return record.stage === "rpc_event"
|
|
237
|
+
&& typeof record.at === "string"
|
|
238
|
+
&& record.at.trim().length > 0
|
|
239
|
+
&& typeof record.type === "string";
|
|
240
|
+
}
|
|
182
241
|
async function countFileLines(filePath) {
|
|
183
242
|
try {
|
|
184
243
|
const content = await readFile(filePath, "utf8");
|
|
@@ -32,5 +32,6 @@ export declare function allSubagentStatesTerminal(agents: readonly SubagentAgent
|
|
|
32
32
|
export declare function taskPreviewMap(tasks: readonly SubagentTaskPreview[] | undefined): Map<string, SubagentTaskPreview>;
|
|
33
33
|
export declare function formatSubagentTimestamp(value: string | undefined): string | undefined;
|
|
34
34
|
export declare function formatElapsedSince(value: string | undefined, now?: number): string;
|
|
35
|
+
export declare function formatSubagentLastActivity(activity: SubagentAgentState["lastActivity"]): string | undefined;
|
|
35
36
|
export declare function subagentRunName(runDir: string): string;
|
|
36
37
|
export declare function resolveSubagentRunDir(cwd: string, runDir: string): string;
|
|
@@ -43,6 +43,14 @@ export function isSubagentAgentState(value) {
|
|
|
43
43
|
return false;
|
|
44
44
|
if (value.retryCount !== undefined && typeof value.retryCount !== "number")
|
|
45
45
|
return false;
|
|
46
|
+
if (value.lastActivity !== undefined) {
|
|
47
|
+
if (!isRecord(value.lastActivity))
|
|
48
|
+
return false;
|
|
49
|
+
if (typeof value.lastActivity.label !== "string" || !value.lastActivity.label.trim())
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof value.lastActivity.at !== "string" || !value.lastActivity.at.trim())
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
46
54
|
return true;
|
|
47
55
|
}
|
|
48
56
|
export function isSubagentTaskPreview(value) {
|
|
@@ -219,11 +227,19 @@ export function formatSubagentTimestamp(value) {
|
|
|
219
227
|
return new Date(time).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
220
228
|
}
|
|
221
229
|
export function formatElapsedSince(value, now = Date.now()) {
|
|
230
|
+
return formatDurationSince(value, now) ?? "elapsed:—";
|
|
231
|
+
}
|
|
232
|
+
export function formatSubagentLastActivity(activity) {
|
|
233
|
+
if (!activity)
|
|
234
|
+
return undefined;
|
|
235
|
+
return activity.label.trim() || undefined;
|
|
236
|
+
}
|
|
237
|
+
function formatDurationSince(value, now) {
|
|
222
238
|
if (!value)
|
|
223
|
-
return
|
|
239
|
+
return undefined;
|
|
224
240
|
const started = Date.parse(value);
|
|
225
241
|
if (!Number.isFinite(started))
|
|
226
|
-
return
|
|
242
|
+
return undefined;
|
|
227
243
|
const seconds = Math.max(0, Math.floor((now - started) / 1000));
|
|
228
244
|
if (seconds < 60)
|
|
229
245
|
return `${seconds}s`;
|
package/dist/app/types.d.ts
CHANGED
|
@@ -139,6 +139,10 @@ export type SubagentStatus = (typeof SUBAGENT_STATUSES)[number];
|
|
|
139
139
|
export type SubagentActiveStatus = (typeof SUBAGENT_ACTIVE_STATUSES)[number];
|
|
140
140
|
export type SubagentTerminalStatus = (typeof SUBAGENT_TERMINAL_STATUSES)[number];
|
|
141
141
|
export type SubagentRenderMode = (typeof SUBAGENT_RENDER_MODES)[number];
|
|
142
|
+
export type SubagentActivity = {
|
|
143
|
+
label: string;
|
|
144
|
+
at: string;
|
|
145
|
+
};
|
|
142
146
|
export type SubagentAgentState = {
|
|
143
147
|
id: string;
|
|
144
148
|
status: SubagentStatus;
|
|
@@ -151,6 +155,7 @@ export type SubagentAgentState = {
|
|
|
151
155
|
stderrLines?: number;
|
|
152
156
|
eventLines?: number;
|
|
153
157
|
retryCount?: number;
|
|
158
|
+
lastActivity?: SubagentActivity;
|
|
154
159
|
};
|
|
155
160
|
export type SubagentTaskPreview = {
|
|
156
161
|
id: string;
|