@marcoscale98/pi-extensible-workflows 5.19.1-fork.1 → 6.2.1-fork.1
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/CHANGELOG.md +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -327
|
@@ -13,10 +13,10 @@ export declare const SUBAGENTS_RUN_PARAMETERS: Type.TObject<{
|
|
|
13
13
|
label: Type.TOptional<Type.TString>;
|
|
14
14
|
model: Type.TOptional<Type.TString>;
|
|
15
15
|
tools: Type.TOptional<Type.TArray<Type.TString>>;
|
|
16
|
+
excludeTools: Type.TOptional<Type.TArray<Type.TString>>;
|
|
16
17
|
skills: Type.TOptional<Type.TArray<Type.TString>>;
|
|
17
18
|
extensions: Type.TOptional<Type.TArray<Type.TString>>;
|
|
18
19
|
contextFiles: Type.TOptional<Type.TArray<Type.TString>>;
|
|
19
|
-
role: Type.TOptional<Type.TString>;
|
|
20
20
|
worktree: Type.TOptional<Type.TString>;
|
|
21
21
|
outputSchema: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
22
22
|
retries: Type.TOptional<Type.TInteger>;
|
|
@@ -84,7 +84,9 @@ export declare const SUBAGENTS_STEER_OUTPUT: Type.TObject<{
|
|
|
84
84
|
id: Type.TString;
|
|
85
85
|
accepted: Type.TBoolean;
|
|
86
86
|
}>;
|
|
87
|
-
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS
|
|
87
|
+
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS> & {
|
|
88
|
+
readonly [key: string]: unknown;
|
|
89
|
+
};
|
|
88
90
|
export declare function normalizeSubagentRunRequest(value: unknown): SubagentRunRequest;
|
|
89
91
|
export type SubagentInspectRequest = Static<typeof SUBAGENTS_INSPECT_PARAMETERS>;
|
|
90
92
|
export type SubagentIdRequest = Static<typeof SUBAGENTS_ID_PARAMETERS>;
|
|
@@ -117,7 +119,6 @@ export interface SubagentStatus {
|
|
|
117
119
|
export interface SubagentNotification {
|
|
118
120
|
readonly id: string;
|
|
119
121
|
readonly label?: string;
|
|
120
|
-
readonly role?: string;
|
|
121
122
|
readonly state: "completed" | "failed";
|
|
122
123
|
readonly error?: {
|
|
123
124
|
readonly code: string;
|
|
@@ -22,15 +22,15 @@ export const SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
|
22
22
|
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
23
23
|
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
24
24
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
25
|
+
excludeTools: Type.Optional(Type.Array(Type.String(), { description: "Optional exact tool names removed after preparation; no patterns, cannot remove workflow_result" })),
|
|
25
26
|
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
26
27
|
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
27
28
|
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
28
|
-
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
29
29
|
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
30
30
|
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
31
31
|
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
32
32
|
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()])),
|
|
33
|
-
}, { additionalProperties:
|
|
33
|
+
}, { additionalProperties: true });
|
|
34
34
|
export const SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
35
35
|
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
36
36
|
scope: Type.Optional(Type.Union([Type.Literal("session"), Type.Literal("all")], { description: "List scope when id is omitted: session (default) lists runs from the current Pi session; all lists every stored run" })),
|
|
@@ -5,7 +5,7 @@ export { attemptValue, statusValue, subagentErrorValue } from "./decode.js";
|
|
|
5
5
|
export { createSubagentManager, createUnavailableSubagentManager } from "./manager.js";
|
|
6
6
|
export { createRunStoreWorktreeAdapter, defaultWorktreeHome } from "./worktree.js";
|
|
7
7
|
export type { SubagentWorktreeAdapter, SubagentWorktreeContext, SubagentWorktreeHandle, SubagentWorktreeRunStore } from "./worktree.js";
|
|
8
|
-
type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "getActiveTools" | "getAllTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
|
|
8
|
+
type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "events" | "getActiveTools" | "getAllTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
|
|
9
9
|
export declare function createSubagentTools(manager: SubagentManager): readonly ToolDefinition[];
|
|
10
10
|
export declare function createSubagentsExtension(options?: SubagentsExtensionOptions, activeTools?: () => readonly string[], notify?: (notification: SubagentNotification) => void | Promise<void>, onStatus?: (status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>) => void, onResourceWarning?: (message: string) => void): SubagentsExtension;
|
|
11
11
|
export declare function registerSubagentsExtension(pi: SubagentsExtensionAPI, options?: SubagentsExtensionOptions): SubagentsExtension;
|
|
@@ -122,8 +122,7 @@ export function createSubagentTools(manager) {
|
|
|
122
122
|
}
|
|
123
123
|
function notificationContent(notification) {
|
|
124
124
|
const label = notification.label?.trim();
|
|
125
|
-
const
|
|
126
|
-
const identity = label ? `${label} role=${role} (${notification.id})` : `${notification.id} role=${role}`;
|
|
125
|
+
const identity = label ? `${label} (${notification.id})` : notification.id;
|
|
127
126
|
if (notification.state === "completed")
|
|
128
127
|
return `Subagent ${identity} completed. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
129
128
|
return `Subagent ${identity} failed: ${notification.error?.message ?? "unknown error"}. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
@@ -2,12 +2,11 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { chmod, link, mkdir, open, readFile, readdir, rename, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { addAccounting,
|
|
6
|
-
import {
|
|
7
|
-
import { loadAgentDefinitions } from "../../src/roles.js";
|
|
5
|
+
import { addAccounting, errorCode, errorText, finiteNumber, isNodeError, isThinkingLevel, jsonValue, object, loadingRegistry, localAgentTransport, resolveAgentResourcePolicy, resolveWorkflowSettings, sanitizeDisplayText, sumAccounting, structuralPath, validateModelAliasAvailability, WorkflowAgentExecutor, WorkflowError, workflowSettingsPath, SerialLane } from "../../src/index.js";
|
|
6
|
+
import { decodePreparedAgentConfiguration } from "../../src/decoders.js";
|
|
8
7
|
import { atomicJson, json as readJson, processAlive } from "../../src/persistence.js";
|
|
9
8
|
import { accountingValue, activityValue, legacyAccountingValue, worktreeValue } from "./decode.js";
|
|
10
|
-
import { SUBAGENT_ATTEMPT_DETAILS_LIMIT, SUBAGENT_MAX_RETRIES, SUBAGENT_SYSTEM_PROMPT_LIMIT, SUBAGENTS_TOOL_NAMES, normalizeSubagentRunRequest
|
|
9
|
+
import { SUBAGENT_ATTEMPT_DETAILS_LIMIT, SUBAGENT_MAX_RETRIES, SUBAGENT_SYSTEM_PROMPT_LIMIT, SUBAGENTS_TOOL_NAMES, normalizeSubagentRunRequest } from "./contracts.js";
|
|
11
10
|
import { createRunStoreWorktreeAdapter, defaultWorktreeHome } from "./worktree.js";
|
|
12
11
|
const WORKFLOW_NAME = "subagents";
|
|
13
12
|
const STORAGE_DIRECTORY = "subagents";
|
|
@@ -44,25 +43,11 @@ function rootModel(context) {
|
|
|
44
43
|
const thinking = context.extensionContext.thinkingLevel;
|
|
45
44
|
return { provider: model.provider, model: model.id, ...(thinking === undefined ? {} : { thinking }) };
|
|
46
45
|
}
|
|
47
|
-
function
|
|
48
|
-
return trustedProject && external.projectExtensionSettings !== undefined ? workflowProjectSettingsPath(cwd) : workflowSettingsPath(agentDir);
|
|
49
|
-
}
|
|
50
|
-
function validateStandaloneExtensionSources(context, external, registry, agentDir) {
|
|
51
|
-
const cwd = context.extensionContext.cwd;
|
|
52
|
-
const globalSettingsPath = workflowSettingsPath(agentDir);
|
|
53
|
-
const trustedProject = context.extensionContext.isProjectTrusted();
|
|
54
|
-
if (external.globalExtensionSettings !== undefined)
|
|
55
|
-
registry.validateExtensionSettings(external.globalExtensionSettings, { source: "global", cwd, projectTrusted: trustedProject, settingsPath: globalSettingsPath });
|
|
56
|
-
if (trustedProject && external.projectExtensionSettings !== undefined)
|
|
57
|
-
registry.validateExtensionSettings(external.projectExtensionSettings, { source: "project", cwd, projectTrusted: true, settingsPath: workflowProjectSettingsPath(cwd) });
|
|
58
|
-
}
|
|
59
|
-
function executionRoot(context, dependencies, signal, runId, worktree, external) {
|
|
46
|
+
function executionRoot(context, dependencies, signal, runId, worktree) {
|
|
60
47
|
const extensionContext = context.extensionContext;
|
|
61
48
|
const model = rootModel(context);
|
|
62
49
|
const agentDir = dependencies.agentDir ?? getAgentDir();
|
|
63
50
|
const currentTrustedProject = extensionContext.isProjectTrusted();
|
|
64
|
-
if (external.projectTrusted && !currentTrustedProject)
|
|
65
|
-
throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
|
|
66
51
|
const trustedProject = currentTrustedProject;
|
|
67
52
|
const settingsPath = workflowSettingsPath(agentDir);
|
|
68
53
|
const settings = resolveWorkflowSettings(extensionContext.cwd, trustedProject, settingsPath);
|
|
@@ -85,11 +70,15 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
|
|
|
85
70
|
signal,
|
|
86
71
|
};
|
|
87
72
|
const registry = loadingRegistry();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
if (settings.global.extensionSettings !== undefined)
|
|
74
|
+
registry.validateExtensionSettings(settings.global.extensionSettings, { source: "global", cwd: extensionContext.cwd, projectTrusted: trustedProject, settingsPath: settings.globalSettingsPath });
|
|
75
|
+
if (trustedProject && settings.project.extensionSettings !== undefined)
|
|
76
|
+
registry.validateExtensionSettings(settings.project.extensionSettings, { source: "project", cwd: extensionContext.cwd, projectTrusted: true, settingsPath: settings.projectSettingsPath });
|
|
77
|
+
const extensionSettings = settings.effective.extensionSettings;
|
|
78
|
+
const extensionSettingsPath = settings.sources.extensionSettings ?? settingsPath;
|
|
91
79
|
return {
|
|
92
80
|
cwd: extensionContext.cwd,
|
|
81
|
+
projectTrusted: trustedProject,
|
|
93
82
|
model,
|
|
94
83
|
tools,
|
|
95
84
|
resourceSelectors: resourcePolicy.effective,
|
|
@@ -99,9 +88,10 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
|
|
|
99
88
|
availableModels,
|
|
100
89
|
knownModels,
|
|
101
90
|
...(Object.keys(staticAliases).length ? { modelAliases: staticAliases } : {}),
|
|
91
|
+
dynamicModelAliasNames: [],
|
|
102
92
|
settingsPath: settings.sources.modelAliases,
|
|
103
93
|
extensionSettingsPath,
|
|
104
|
-
|
|
94
|
+
agentPreparationHooks: registry.agentPreparationHooks(),
|
|
105
95
|
agentSetupHooks: registry.agentSetupHooks(),
|
|
106
96
|
validateExtensionSettings: registry.validateExtensionSettings,
|
|
107
97
|
agentResourcePolicy: () => structuredClone(resourcePolicy),
|
|
@@ -118,11 +108,10 @@ async function addDynamicAliases(context, signal, root) {
|
|
|
118
108
|
const trustedProject = resourcePolicy?.projectTrusted ?? context.extensionContext.isProjectTrusted();
|
|
119
109
|
const dynamicAliases = await registry.resolveModelAliases({ cwd: context.extensionContext.cwd, projectTrusted: trustedProject, rootModel: root.model, knownModels: root.knownModels ?? new Set(), availableModels: root.availableModels ?? new Set(), signal }, new Set(Object.keys(staticAliases)));
|
|
120
110
|
validateModelAliasAvailability(dynamicAliases, Object.keys(dynamicAliases), root.availableModels ?? new Set(), root.knownModels ?? new Set(), root.settingsPath);
|
|
121
|
-
return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases } };
|
|
111
|
+
return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases }, dynamicModelAliasNames: Object.keys(dynamicAliases) };
|
|
122
112
|
}
|
|
123
113
|
function executionOptions(request, onAttempt, onProgress) {
|
|
124
|
-
const
|
|
125
|
-
const label = request.label ?? roleNameOf(role) ?? "subagent";
|
|
114
|
+
const label = request.label ?? "subagent";
|
|
126
115
|
return {
|
|
127
116
|
label,
|
|
128
117
|
workflowName: WORKFLOW_NAME,
|
|
@@ -131,11 +120,11 @@ function executionOptions(request, onAttempt, onProgress) {
|
|
|
131
120
|
...(request.skills === undefined ? {} : { skills: request.skills }),
|
|
132
121
|
...(request.extensions === undefined ? {} : { extensions: request.extensions }),
|
|
133
122
|
...(request.contextFiles === undefined ? {} : { contextFiles: request.contextFiles }),
|
|
134
|
-
...(role === undefined ? {} : { role }),
|
|
135
123
|
...(request.worktree === undefined ? {} : { worktreeOwner: structuralPath("worktree", "named", request.worktree) }),
|
|
136
124
|
...(request.outputSchema === undefined ? {} : { schema: request.outputSchema }),
|
|
137
125
|
...(request.retries === undefined ? {} : { retries: request.retries }),
|
|
138
126
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
127
|
+
agentOptions: Object.fromEntries(Object.entries(request).filter(([key]) => !["prompt", "mode", "worktree"].includes(key))),
|
|
139
128
|
onAttempt,
|
|
140
129
|
onProgress,
|
|
141
130
|
};
|
|
@@ -326,36 +315,9 @@ function checkedRequest(request) {
|
|
|
326
315
|
return normalizeSubagentRunRequest(request);
|
|
327
316
|
}
|
|
328
317
|
function decodePersistedStandaloneExternalConfiguration(value) {
|
|
329
|
-
if (!object(value) ||
|
|
330
|
-
return undefined;
|
|
331
|
-
const globalExtensionSettings = value.globalExtensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.globalExtensionSettings);
|
|
332
|
-
const projectExtensionSettings = value.projectExtensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.projectExtensionSettings);
|
|
333
|
-
if (value.globalExtensionSettings !== undefined && globalExtensionSettings === undefined || value.projectExtensionSettings !== undefined && projectExtensionSettings === undefined)
|
|
334
|
-
return undefined;
|
|
335
|
-
if (!object(value.agentDefinitions))
|
|
318
|
+
if (!object(value) || value.version !== 1)
|
|
336
319
|
return undefined;
|
|
337
|
-
|
|
338
|
-
for (const [name, definition] of Object.entries(value.agentDefinitions)) {
|
|
339
|
-
const decoded = decodeAgentDefinition(definition);
|
|
340
|
-
if (decoded === undefined)
|
|
341
|
-
return undefined;
|
|
342
|
-
agentDefinitions[name] = decoded;
|
|
343
|
-
}
|
|
344
|
-
return deepFreeze({ projectTrusted: value.projectTrusted, ...(globalExtensionSettings === undefined ? {} : { globalExtensionSettings }), ...(projectExtensionSettings === undefined ? {} : { projectExtensionSettings }), agentDefinitions });
|
|
345
|
-
}
|
|
346
|
-
function standaloneExternalConfiguration(context, dependencies) {
|
|
347
|
-
const cwd = context.extensionContext.cwd;
|
|
348
|
-
const trustedProject = context.extensionContext.isProjectTrusted();
|
|
349
|
-
const agentDir = dependencies.agentDir ?? getAgentDir();
|
|
350
|
-
const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
|
|
351
|
-
const globalExtensionSettings = resolution.global.extensionSettings;
|
|
352
|
-
const projectExtensionSettings = resolution.project.extensionSettings;
|
|
353
|
-
return deepFreeze({
|
|
354
|
-
projectTrusted: trustedProject,
|
|
355
|
-
...(globalExtensionSettings === undefined ? {} : { globalExtensionSettings: structuredClone(globalExtensionSettings) }),
|
|
356
|
-
...(projectExtensionSettings === undefined ? {} : { projectExtensionSettings: structuredClone(projectExtensionSettings) }),
|
|
357
|
-
agentDefinitions: structuredClone(loadAgentDefinitions(cwd, agentDir, trustedProject)),
|
|
358
|
-
});
|
|
320
|
+
return decodePreparedAgentConfiguration(value.configuration);
|
|
359
321
|
}
|
|
360
322
|
function checkedId(request) {
|
|
361
323
|
if (typeof request.id !== "string" || !validSubagentId(request.id))
|
|
@@ -715,7 +677,8 @@ async function createRunStorage(root, id, request, status, external) {
|
|
|
715
677
|
created = true;
|
|
716
678
|
await chmod(directory, 0o700);
|
|
717
679
|
await atomicJson(requestPath(directory), request);
|
|
718
|
-
|
|
680
|
+
if (external !== undefined)
|
|
681
|
+
await atomicJson(configurationPath(directory), { version: 1, configuration: external });
|
|
719
682
|
await atomicJson(statusPath(directory), status);
|
|
720
683
|
return directory;
|
|
721
684
|
}
|
|
@@ -935,6 +898,7 @@ function unavailable(operation) {
|
|
|
935
898
|
class PersistentSubagentManager {
|
|
936
899
|
dependencies;
|
|
937
900
|
activeRuns = new Map();
|
|
901
|
+
pendingStarts = new Set();
|
|
938
902
|
activeRunCount = 0;
|
|
939
903
|
terminalSummaries = new Map();
|
|
940
904
|
reconciliationErrors = new Map();
|
|
@@ -991,8 +955,8 @@ class PersistentSubagentManager {
|
|
|
991
955
|
throw new WorkflowError("CANCELLED", "Subagent manager is disposed");
|
|
992
956
|
if (context.signal?.aborted)
|
|
993
957
|
throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
994
|
-
const external = frozenExternal
|
|
995
|
-
if (external
|
|
958
|
+
const external = frozenExternal;
|
|
959
|
+
if (external?.projectTrusted && !context.extensionContext.isProjectTrusted())
|
|
996
960
|
throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
|
|
997
961
|
const id = randomUUID();
|
|
998
962
|
const startedAt = Date.now();
|
|
@@ -1009,9 +973,16 @@ class PersistentSubagentManager {
|
|
|
1009
973
|
this.activeRunCount += 1;
|
|
1010
974
|
const controller = new AbortController();
|
|
1011
975
|
const initialStatus = { id, sessionId, state: "running", startedAt, owner: { ...owner } };
|
|
976
|
+
let finishStart;
|
|
977
|
+
const pendingStart = new Promise((resolve) => { finishStart = resolve; });
|
|
978
|
+
this.pendingStarts.add(pendingStart);
|
|
1012
979
|
let directory;
|
|
1013
980
|
try {
|
|
1014
981
|
directory = await createRunStorage(storageDirectory(this.dependencies), id, snapshot, initialStatus, external);
|
|
982
|
+
if (this.disposePromise !== undefined || context.signal?.aborted) {
|
|
983
|
+
await atomicJson(statusPath(directory), reconciledStatus(initialStatus, "stopped", Date.now()));
|
|
984
|
+
throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
985
|
+
}
|
|
1015
986
|
}
|
|
1016
987
|
catch (error) {
|
|
1017
988
|
this.activeRunCount -= 1;
|
|
@@ -1019,6 +990,10 @@ class PersistentSubagentManager {
|
|
|
1019
990
|
throw error;
|
|
1020
991
|
throw internalStorageError(error, `Unable to start subagent ${id}`);
|
|
1021
992
|
}
|
|
993
|
+
finally {
|
|
994
|
+
this.pendingStarts.delete(pendingStart);
|
|
995
|
+
finishStart();
|
|
996
|
+
}
|
|
1022
997
|
const current = {};
|
|
1023
998
|
let resolveTerminal;
|
|
1024
999
|
const terminal = new Promise((resolve) => { resolveTerminal = resolve; });
|
|
@@ -1049,13 +1024,9 @@ class PersistentSubagentManager {
|
|
|
1049
1024
|
throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
1050
1025
|
}
|
|
1051
1026
|
}
|
|
1052
|
-
const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree
|
|
1053
|
-
const root = loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
|
|
1027
|
+
const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree);
|
|
1028
|
+
const root = external !== undefined || loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
|
|
1054
1029
|
const transport = this.dependencies.transport ?? localAgentTransport;
|
|
1055
|
-
const injectedExecutor = this.dependencies.createExecutor?.(root, transport);
|
|
1056
|
-
executorOwnership.default = injectedExecutor === undefined;
|
|
1057
|
-
if (current.run)
|
|
1058
|
-
current.run.executorOwnsSession = executorOwnership.default;
|
|
1059
1030
|
const setSteer = (handler) => {
|
|
1060
1031
|
const run = current.run;
|
|
1061
1032
|
if (run)
|
|
@@ -1106,6 +1077,15 @@ class PersistentSubagentManager {
|
|
|
1106
1077
|
return;
|
|
1107
1078
|
emitUpdate(run);
|
|
1108
1079
|
}, (progress) => this.recordProgress(current.run, progress));
|
|
1080
|
+
if (external !== undefined)
|
|
1081
|
+
options.configuration = structuredClone(external);
|
|
1082
|
+
options.onConfiguration = (configuration) => atomicJson(configurationPath(directory), { version: 1, configuration });
|
|
1083
|
+
const cwd = worktree?.cwd ?? root.cwd;
|
|
1084
|
+
options.configuration = await new WorkflowAgentExecutor(root, transport).prepare(options, cwd, controller.signal);
|
|
1085
|
+
const injectedExecutor = this.dependencies.createExecutor?.({ ...root, extensionSettings: options.configuration.settings }, transport);
|
|
1086
|
+
executorOwnership.default = injectedExecutor === undefined;
|
|
1087
|
+
if (current.run)
|
|
1088
|
+
current.run.executorOwnsSession = executorOwnership.default;
|
|
1109
1089
|
if (injectedExecutor)
|
|
1110
1090
|
return injectedExecutor.execute(snapshot.prompt, options, controller.signal, setSteer);
|
|
1111
1091
|
return new WorkflowAgentExecutor(root, transport).execute(snapshot.prompt, options, controller.signal, [], setSteer);
|
|
@@ -1228,11 +1208,11 @@ class PersistentSubagentManager {
|
|
|
1228
1208
|
async dispose() {
|
|
1229
1209
|
if (this.disposePromise)
|
|
1230
1210
|
return this.disposePromise;
|
|
1211
|
+
this.disposed = true;
|
|
1231
1212
|
this.disposePromise = (async () => {
|
|
1232
1213
|
await this.initialization;
|
|
1233
|
-
this.disposed = true;
|
|
1234
1214
|
const runs = [...this.activeRuns.values()];
|
|
1235
|
-
await Promise.allSettled(runs.map((run) => this.stopRun(run, true)));
|
|
1215
|
+
await Promise.allSettled([...this.pendingStarts, ...runs.map((run) => this.stopRun(run, true))]);
|
|
1236
1216
|
await Promise.allSettled([...this.notificationPromises]);
|
|
1237
1217
|
const owner = this.storageOwner;
|
|
1238
1218
|
this.storageOwner = undefined;
|
|
@@ -1610,9 +1590,8 @@ class PersistentSubagentManager {
|
|
|
1610
1590
|
if (!notify || run.notificationSent || run.disposed)
|
|
1611
1591
|
return;
|
|
1612
1592
|
run.notificationSent = true;
|
|
1613
|
-
const
|
|
1614
|
-
const
|
|
1615
|
-
const notification = { id: run.id, label, role, state: run.state, ...(run.error === undefined ? {} : { error: run.error }) };
|
|
1593
|
+
const label = run.request.label?.trim() || "subagent";
|
|
1594
|
+
const notification = { id: run.id, label, state: run.state, ...(run.error === undefined ? {} : { error: run.error }) };
|
|
1616
1595
|
const pending = Promise.resolve().then(() => notify(notification));
|
|
1617
1596
|
this.notificationPromises.add(pending);
|
|
1618
1597
|
void pending.then(() => { this.notificationPromises.delete(pending); }, () => { this.notificationPromises.delete(pending); });
|
|
@@ -60,17 +60,9 @@ async function inspectEntry(manager, storageDirectory, entry, context) {
|
|
|
60
60
|
entry: { status: inspected.status, ...(request.request === undefined ? {} : { request: request.request }), ...(request.error === undefined ? {} : { requestError: request.error }) },
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
-
function requestRole(request) {
|
|
64
|
-
const role = request?.role;
|
|
65
|
-
if (typeof role === "string" && role.trim())
|
|
66
|
-
return role.trim();
|
|
67
|
-
const override = objectValue(role);
|
|
68
|
-
return typeof override?.name === "string" && override.name.trim() ? override.name.trim() : "none";
|
|
69
|
-
}
|
|
70
63
|
function shortId(id) { return id.length > 12 ? id.slice(0, 8) : id; }
|
|
71
64
|
function entryName(entry) {
|
|
72
|
-
|
|
73
|
-
return boundedText(entry.request?.label?.trim() || (role === "none" ? shortId(entry.status.id) : role), 256);
|
|
65
|
+
return boundedText(entry.request?.label?.trim() || shortId(entry.status.id), 256);
|
|
74
66
|
}
|
|
75
67
|
/** Names for a list of runs; a repeated name carries its short ID, as in the `/workflow` picker, so every row stays unique. */
|
|
76
68
|
function listNames(entries) {
|
|
@@ -108,7 +100,7 @@ function usageAccounting(status) {
|
|
|
108
100
|
return status.progress?.accounting;
|
|
109
101
|
}
|
|
110
102
|
function detailPresentation(inspection) {
|
|
111
|
-
const { status
|
|
103
|
+
const { status } = inspection.entry;
|
|
112
104
|
const attempt = latestAttempt(status);
|
|
113
105
|
const state = status.progress?.state;
|
|
114
106
|
const activity = status.progress?.activity;
|
|
@@ -117,14 +109,12 @@ function detailPresentation(inspection) {
|
|
|
117
109
|
const lastEventAt = status.progress?.lastEventAt;
|
|
118
110
|
const attempts = status.attempts ?? attempt?.attempt;
|
|
119
111
|
const accounting = usageAccounting(status);
|
|
120
|
-
const role = requestRole(request);
|
|
121
112
|
const error = status.error ?? attempt?.error;
|
|
122
113
|
return {
|
|
123
114
|
state: status.state,
|
|
124
115
|
...(activity === undefined ? {} : { activity: { kind: activity.kind, text: boundedText(activity.text) } }),
|
|
125
116
|
...(lastEventAt === undefined ? {} : { lastEventAt }),
|
|
126
117
|
...(model === undefined ? {} : { model: { provider: boundedText(model.provider, 256), model: boundedText(model.model, 256), ...(model.thinking === undefined ? {} : { thinking: model.thinking }) } }),
|
|
127
|
-
...(role === "none" ? {} : { role: boundedText(role, 256) }),
|
|
128
118
|
...(tools === undefined ? {} : { tools: tools.slice(0, 256).map((tool) => boundedText(tool, 256)) }),
|
|
129
119
|
...(attempts === undefined ? {} : { attempts }),
|
|
130
120
|
...(status.startedAt === undefined ? {} : { startedAt: status.startedAt }),
|
|
@@ -178,8 +168,7 @@ function standaloneActionContext(manager, inspection, context) {
|
|
|
178
168
|
const session = attempt.session;
|
|
179
169
|
const live = liveData?.liveSession && session && liveData.liveSession.reference.transport === session.transport && liveData.liveSession.reference.sessionId === session.sessionId ? liveData.liveSession : undefined;
|
|
180
170
|
const label = request?.label?.trim();
|
|
181
|
-
const
|
|
182
|
-
const name = label || (role === "none" ? "subagent" : role);
|
|
171
|
+
const name = label || "subagent";
|
|
183
172
|
const ui = {
|
|
184
173
|
notify: (message, level = "info") => { context.ui.notify(message, level); },
|
|
185
174
|
confirm: (title, message) => context.ui.confirm(title, message),
|
|
@@ -7,28 +7,16 @@ const TERMINAL_STATES = new Set(["completed", "failed", "stopped"]);
|
|
|
7
7
|
const MAX_INSPECT_VALUE_CHARS = 4000;
|
|
8
8
|
const MAX_INSPECT_TOOL_CALLS = 32;
|
|
9
9
|
const MAX_BACKGROUND_WIDGET_ROWS = 10;
|
|
10
|
-
function roleName(value) {
|
|
11
|
-
if (typeof value === "string")
|
|
12
|
-
return value.trim() || undefined;
|
|
13
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
14
|
-
return undefined;
|
|
15
|
-
const name = value.name;
|
|
16
|
-
return typeof name === "string" ? name.trim() || undefined : undefined;
|
|
17
|
-
}
|
|
18
10
|
function modeName(value) {
|
|
19
11
|
return value === "background" || value === "foreground" ? value : undefined;
|
|
20
12
|
}
|
|
21
13
|
function requestMetadata(args, metadataAvailable) {
|
|
22
14
|
const mode = modeName(args.mode) ?? (metadataAvailable ? "background" : undefined);
|
|
23
|
-
|
|
24
|
-
return [mode === undefined ? undefined : `mode=${mode}`, role === undefined ? undefined : `role=${role}`].filter((value) => value !== undefined).join(" ");
|
|
15
|
+
return [mode === undefined ? undefined : `mode=${mode}`].filter((value) => value !== undefined).join(" ");
|
|
25
16
|
}
|
|
26
17
|
function label(args) {
|
|
27
18
|
if (typeof args.label === "string" && args.label.trim())
|
|
28
19
|
return args.label.trim();
|
|
29
|
-
const role = roleName(args.role);
|
|
30
|
-
if (role)
|
|
31
|
-
return role;
|
|
32
20
|
if (typeof args.id === "string" && args.id)
|
|
33
21
|
return args.id.slice(0, 8);
|
|
34
22
|
return "subagent";
|
|
@@ -247,14 +235,12 @@ const RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
|
|
|
247
235
|
function receiptFor(status, request) {
|
|
248
236
|
const accounting = status.progress?.accounting;
|
|
249
237
|
const model = modelName(status);
|
|
250
|
-
const role = roleName(request.role);
|
|
251
238
|
const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
|
|
252
239
|
return {
|
|
253
240
|
id: status.id,
|
|
254
241
|
label: label({ ...request, id: status.id }),
|
|
255
242
|
state: status.state,
|
|
256
243
|
...(model ? { model } : {}),
|
|
257
|
-
...(role === undefined ? {} : { role }),
|
|
258
244
|
...(tools === undefined ? {} : { tools: [...tools] }),
|
|
259
245
|
input: accounting?.input ?? 0,
|
|
260
246
|
output: accounting?.output ?? 0,
|
|
@@ -272,7 +258,7 @@ export function renderSubagentReceipt(data, expanded, theme) {
|
|
|
272
258
|
const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
|
|
273
259
|
if (!expanded)
|
|
274
260
|
return lines;
|
|
275
|
-
const meta = [data.model ?? "", data.
|
|
261
|
+
const meta = [data.model ?? "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
|
|
276
262
|
if (meta.length > 0)
|
|
277
263
|
lines.push(theme.fg("muted", ` ${meta.join(" · ")}`));
|
|
278
264
|
const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
|
|
@@ -22,7 +22,7 @@ function syntheticSnapshot(context) {
|
|
|
22
22
|
...(context.worktreePostCreateCommandSource === undefined ? {} : { settingsSources: { concurrency: "subagents", modelAliases: "subagents", worktreePostCreateCommand: context.worktreePostCreateCommandSource } }),
|
|
23
23
|
models: [],
|
|
24
24
|
tools: [],
|
|
25
|
-
|
|
25
|
+
agentConfigurations: {},
|
|
26
26
|
schemas: [],
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
.workflow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 16px/1.2 var(--serif); }
|
|
143
143
|
.workflow-meta { color: var(--dim); font: 11px ui-monospace, Menlo, Consolas, monospace; white-space: nowrap; }
|
|
144
144
|
.agent-grid { min-width: 900px; padding: 0 12px 8px 28px; }
|
|
145
|
-
.agent-grid-head, .agent-grid-row { display: grid; grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.2fr)
|
|
145
|
+
.agent-grid-head, .agent-grid-row { display: grid; grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.2fr) minmax(150px, 1.5fr) 80px 72px 64px; gap: 8px; align-items: center; }
|
|
146
146
|
.agent-grid-head { padding: 4px 8px; color: var(--mute); font-size: 10px; letter-spacing: .05em; }
|
|
147
147
|
.agent-grid-row { min-height: 24px; padding: 3px 8px; cursor: pointer; font-size: 11px; border-radius: 8px; }
|
|
148
148
|
.agent-grid-row:hover { background: var(--bg-hover); }
|
|
@@ -732,9 +732,9 @@
|
|
|
732
732
|
const phaseAgents = phase.agents.flatMap((id) => { const agent = byId.get(id); return agent ? [agent] : []; });
|
|
733
733
|
const groups = agentGridGroups(phaseAgents);
|
|
734
734
|
const showScopes = groups.length > 1 || groups.some(({ label }) => label !== "Agents");
|
|
735
|
-
return `<div class="phaseband">PHASE ${esc(phase.name)}</div>${groups.map((group) => `${showScopes ? `<div class="agent-grid-scope">${esc(group.label)}</div>` : ""}${group.agents.map((agent) => { const depth = agentDepth(agent, byId); const label = agent.label || agent.name; const settled = depth ? "•" : "▾"; const indent = (showScopes ? 12 : 0) + depth * 12; const toolSummary = toolTimingSummary(timingsByAgent.get(agent.id) || []); return `<div class="agent-grid-row${agent.id === state.currentAgent ? " on" : ""}" data-agent="${esc(agent.id)}"><div class="agent-cell" style="padding-left:${indent}px">${settled} ${esc(label)}</div><div class="agent-cell">${esc(agentModel(agent))}</div><div class="agent-cell"
|
|
735
|
+
return `<div class="phaseband">PHASE ${esc(phase.name)}</div>${groups.map((group) => `${showScopes ? `<div class="agent-grid-scope">${esc(group.label)}</div>` : ""}${group.agents.map((agent) => { const depth = agentDepth(agent, byId); const label = agent.label || agent.name; const settled = depth ? "•" : "▾"; const indent = (showScopes ? 12 : 0) + depth * 12; const toolSummary = toolTimingSummary(timingsByAgent.get(agent.id) || []); return `<div class="agent-grid-row${agent.id === state.currentAgent ? " on" : ""}" data-agent="${esc(agent.id)}"><div class="agent-cell" style="padding-left:${indent}px">${settled} ${esc(label)}</div><div class="agent-cell">${esc(agentModel(agent))}</div><div class="agent-cell" title="${esc(toolSummary)}">${esc(toolSummary)}</div><div class="agent-cell num">${fmtRuntime(agent.durationMs || 0)}</div><div class="agent-cell num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</div><div class="agent-cell num">${fmtCost(accounting(agent).cost)}</div></div>`; }).join("")}`).join("")}`;
|
|
736
736
|
}).join("");
|
|
737
|
-
return `<div class="agent-grid"><div class="agent-grid-head"><span>AGENT</span><span>MODEL</span><span>
|
|
737
|
+
return `<div class="agent-grid"><div class="agent-grid-head"><span>AGENT</span><span>MODEL</span><span>TOOL TIMING</span><span>DURATION</span><span>TOKENS</span><span>COST</span></div>${body}</div>`;
|
|
738
738
|
}
|
|
739
739
|
function renderLogs(record) { const events = record.run.events || []; const logs = events.filter((event) => event.type === "log"); const omitted = events.find((event) => event.type === "trajectory:truncated"); if (!logs.length && !omitted) return ""; const collapsed = state.runLayout.logsCollapsed; return `<section class="logs"><button type="button" class="sec section-toggle" data-toggle="logs" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} LOGS</button>${collapsed ? "" : `<div class="logs-list">${logs.map((event) => `<div class="log-row"><span class="log-time">${esc(fmtClock(event.timestamp))}</span><span class="log-message">${esc(event.message)}</span></div>`).join("")}${omitted ? `<div class="log-row"><span class="log-time">—</span><span class="log-message">${esc(omitted.omitted)} events omitted from live state</span></div>` : ""}</div>`}</section>`; }
|
|
740
740
|
const scriptCalls = (record) => (record.run.scriptCalls || []).filter((call) => call?.type !== "trajectory:truncated");
|
|
@@ -751,7 +751,7 @@
|
|
|
751
751
|
function renderAgentSection(record, timingsByAgent) { const collapsed = state.runLayout.agentsCollapsed; return `<button type="button" class="sec section-toggle" data-toggle="agents" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} AGENTS</button>${collapsed ? "" : renderAgentGrid(record, timingsByAgent)}`; }
|
|
752
752
|
function renderList(record, timingsByAgent) { const run = record.run; const completed = run.agents.filter((agent) => agent.state === "completed").length; return `<section class="workflow-card"><button type="button" class="workflow-head on" data-run="${esc(state.currentRun)}">${glyph(run.state)}<span class="workflow-name">${esc(run.workflowName)}</span><span class="workflow-meta">${completed}/${run.agents.length} agents · ${fmtCost(runCost(record))} · <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></span></span></button>${renderAgentSection(record, timingsByAgent)}${renderCalls(record)}${renderLogs(record)}</section>`; }
|
|
753
753
|
function actionButton(label, action, disabled) { return `<button class="btn-alt" data-act="${action}"${disabled ? " disabled" : ""}>${label}</button>`; }
|
|
754
|
-
function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent),
|
|
754
|
+
function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent), state: subagent.state, model: subagent.model, requestedModel: subagent.request.model, tools: subagent.tools || [], toolDefinitions: subagent.toolDefinitions || [], skills: subagent.request.skills || [], extensions: subagent.request.extensions || [], contextFiles: subagent.request.contextFiles || [], prompt: subagent.request.prompt, systemPrompt: subagent.attempt?.setup?.systemPrompt || subagent.request.prompt, attempts: subagent.attempts || 0, mode: subagent.mode, cwd: subagent.cwd, worktree: subagent.worktree, output: subagent.output || subagentOutputFallback(subagent), outcome: subagent.result !== undefined ? { kind: "result", value: subagent.result } : subagent.failure !== undefined ? { kind: "failure", value: subagent.failure } : subagent.attempt?.error !== undefined ? { kind: "failure", value: subagent.attempt.error } : undefined, finishedAt: subagent.finishedAt, attemptDetails: subagent.attempt ? [subagent.attempt] : [] }; }
|
|
755
755
|
function subagentOutputFallback(subagent) { if (subagent.result !== undefined) return { status: "available", value: subagent.result, bytes: 0 }; if (subagent.failure !== undefined) return { status: "failed", code: subagent.failure.code || "AGENT_FAILED", message: subagent.failure.message || "Subagent failed" }; if (subagent.state === "running") return { status: "pending" }; if (subagent.state === "completed") return { status: "unavailable" }; if (subagent.state === "stopped") return { status: "cancelled", code: subagent.error?.code || "CANCELLED", message: subagent.error?.message || "Subagent stopped" }; if (subagent.state === "failed") return { status: "failed", code: subagent.error?.code || "AGENT_FAILED", message: subagent.error?.message || "Subagent failed" }; return { status: "unavailable" }; }
|
|
756
756
|
function renderAgentStats(found, agent, isSubagent) {
|
|
757
757
|
const offline = !found.publisher.connected; const stateText = agent.state || "—"; const value = isSubagent ? subagentAccounting(found.record) : accounting(agent);
|
|
@@ -761,7 +761,7 @@
|
|
|
761
761
|
return `${glyph(stateText)} <button type="button" class="btn-alt agent-details" data-agent-details="1">Agent details</button> ${fmtCost(value.cost)} · ${fmtTokens(value.input + value.output)} · runtime ${clock}${controls ? `<span class="actions">${controls}</span>` : ""}${isSubagent && offline ? '<span class="offline">publisher offline — actions disabled</span>' : ""}`;
|
|
762
762
|
}
|
|
763
763
|
function renderWorkflowArguments(record, open = false, key = "") { const args = record.snapshot?.args; const incomplete = record.snapshotArgsTruncated === true; if (args === null || args === undefined && !incomplete) return ""; const status = incomplete ? ' <span class="ins-sub">incomplete</span>' : ""; const body = args === undefined ? '<div class="hint">Arguments unavailable in the live transport.</div>' : `<div class="code args">${highlightCode(json(args), "json")}</div>`; const warning = incomplete ? '<div class="workflow-args-warning">Arguments were truncated by the live transport; this value is incomplete.</div>' : ""; return `<details class="workflow-args"${open ? " open" : ""} data-workflow-args="${esc(key)}"><summary class="sec">ARGUMENTS${status}</summary>${body}${warning}</details>`; }
|
|
764
|
-
function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b
|
|
764
|
+
function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b></td><td class="num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</td><td class="num">${fmtCost(accounting(agent).cost)}</td></tr>`).join(""); const script = record.snapshot?.script ? `<div class="sec">SCRIPT <span class="ins-sub">click to expand</span></div><div class="code script" data-script="1">${highlightCode(record.snapshot.script, "javascript")}</div>` : ""; const argumentsSection = renderWorkflowArguments(record, state.workflowArgumentsOpen.has(state.currentRun), state.currentRun); return `<div class="ins-head" data-run-panel="1">${glyph(run.state)}<b>${esc(run.workflowName)}</b><span class="ins-sub">${esc(run.state)}</span></div><div class="ins-body">${pending ? '<div class="sec">NEEDS ATTENTION</div>' + pending : ""}<div class="sec">STATE</div><div class="kv"><div class="k">Phase</div><div>${esc(run.phase || "—")}</div><div class="k">Active</div><div>${run.agents.filter((agent) => agent.state === "running").length} running · ${run.agents.filter((agent) => agent.state === "queued").length} queued · ${run.agents.filter((agent) => agent.state === "failed").length} failed</div><div class="k">Outcome</div><div>${esc(run.error ? `${run.error.code}: ${run.error.message}` : run.state === "completed" ? "completed" : attention(record) ? `${attention(record)} agent(s) need a human` : "—")}</div><div class="k">Cost</div><div>${fmtCost(runCost(record))}${run.budget?.costUsd?.hard !== undefined ? ` / ${fmtCost(run.budget.costUsd.hard)} hard` : ""}</div><div class="k">Tokens</div><div>${fmtTokens(runTokens(record))}${run.budget?.tokens?.hard !== undefined ? ` / ${fmtTokens(run.budget.tokens.hard)} hard` : ""}</div><div class="k">Started</div><div>${fmtClock(startOf(record))}</div><div class="k">Runtime</div><div data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></div></div><div class="actions">${controls}</div>${state.shareUrls[run.id] ? `<div class="share-url"><input readonly value="${esc(state.shareUrls[run.id])}" /><button class="btn-alt" data-copy-share="${esc(state.shareUrls[run.id])}">Copy</button></div><div class="ins-sub">secret gist: anyone with the link can read it</div>` : ""}${script}${argumentsSection}<div class="sec">AGENTS</div><table class="acct"><thead><tr><th></th><th>agent</th><th class="num">tok</th><th class="num">cost</th></tr></thead><tbody>${rows}</tbody></table></div>`; }
|
|
765
765
|
function renderRunLayout() { const ganttCollapsed = state.runLayout.ganttCollapsed; const splitSwim = $("split-swim"); $("swim").classList.toggle("hidden", ganttCollapsed); splitSwim.classList.toggle("hidden", ganttCollapsed); const heading = $("toggle-gantt"); heading.textContent = `${ganttCollapsed ? "▸" : "▾"} GANTT`; heading.setAttribute("aria-expanded", String(!ganttCollapsed)); const swimHeight = Math.min(state.runLayout.swimHeight, maxSwimHeight()); document.documentElement.style.setProperty("--swim", `${swimHeight}px`); }
|
|
766
766
|
function clampSwimHeight() { renderRunLayout(); }
|
|
767
767
|
function renderHome() { const homePublisher = livePublishers().find((publisher) => publisher.id === state.currentPub); const hasSubagents = Boolean(homePublisher?.subagents?.length); $("canvas-pub").textContent = homePublisher ? homePublisher.id.slice(0, 8) : "—"; $("canvas-run").textContent = hasSubagents ? "SUBAGENTS" : "—"; renderedRunHtml.clear(); ganttOrder = ""; patch($("swim-content"), hasSubagents ? renderSubagentGantt(homePublisher) : ""); $("swim").dataset.timelineHasTime = hasSubagents ? "true" : "false"; $("swim").dataset.timelineRunning = String(Boolean(hasSubagents && homePublisher.subagents.some((subagent) => subagent.state === "running"))); if (hasSubagents) { const subagents = homePublisher.subagents || []; const start = Math.min(...subagents.map((subagent) => subagentStartOf(subagent))); const end = Math.max(start + 1, ...subagents.map((subagent) => subagentEndOf(subagent))); $("swim").dataset.timelineStart = String(start); $("swim").dataset.timelineSpan = String(Math.max(1, end - start)); $("swim").querySelector(":scope > .now")?.classList.remove("hidden"); } else { $("swim").querySelector(":scope > .now")?.classList.add("hidden"); } patch($("list"), hasSubagents ? '<div class="hint">Select a subagent to inspect its transcript.</div>' : ""); patch($("run-stats"), hasSubagents ? `${homePublisher.subagents.length} subagents` : ""); $("run-crumb").textContent = "—"; $("agent-crumb").textContent = "—"; patch($("agent-ticks"), ""); $("agent-timeline").dataset.timelineHasTime = "false"; $("agent-timeline").querySelector(":scope > .now")?.classList.add("hidden"); $("agent-timeline").querySelector(":scope > .brush")?.classList.add("hidden"); patch($("events"), ""); $("q").value = ""; $("reset-agent-range").classList.add("hidden"); patch($("insp"), hasSubagents ? '<div class="ins-body hint">Select a subagent.</div>' : '<div class="ins-body hint">Select a run.</div>'); state.inspSig = null; renderRunLayout(); }
|
|
@@ -786,9 +786,9 @@
|
|
|
786
786
|
const { skillFilters, skillResolved, extensionFilters, extensionResolved } = sessionResources(agent);
|
|
787
787
|
const attempt = agent.attemptDetails?.at(-1);
|
|
788
788
|
const attempts = (agent.attemptDetails || []).map((detail) => ({ attempt: detail.attempt, transport: detail.transport, cwd: detail.setup?.cwd, model: detail.setup?.model, tools: detail.setup?.tools, ...(detail.error ? { error: detail.error } : {}) }));
|
|
789
|
-
const environment = {
|
|
789
|
+
const environment = { requestedModel: agent.requestedModel, model: agent.model, cwd: agent.cwd || attempt?.setup?.cwd || selected()?.record?.run?.cwd, mode: agent.mode, contextFiles: agent.contextFiles, worktree: agent.worktree || (agent.worktreeOwner ? { owner: agent.worktreeOwner } : undefined), structuralPath: agent.structuralPath, parentId: agent.parentId, parentBreadcrumb: agent.parentBreadcrumb, handle: agent.handle, turn: agent.turn, continuity: agent.continuity, state: agent.state, attempts: agent.attempts, attempt: attempt?.attempt, attemptDetails: attempts };
|
|
790
790
|
const panes = {
|
|
791
|
-
prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="
|
|
791
|
+
prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="code">${promptAvailable ? esc(prompt) : "Prompt unavailable"}</div>`,
|
|
792
792
|
tools: () => (agent.tools || []).map((tool) => { const description = toolDescriptionOf(agent, tool); return `<div class="tool-item"><b>${esc(tool)}</b>${description ? `<span>${esc(description)}</span>` : ""}</div>`; }).join("") || '<div class="hint">No tools recorded.</div>',
|
|
793
793
|
skills: () => `<div class="sec">FILTERS</div>${nameList(skillFilters)}<div class="sec">RESOLVED</div>${resourceList(skillResolved)}`,
|
|
794
794
|
extensions: () => `<div class="sec">FILTERS</div>${nameList(extensionFilters)}<div class="sec">RESOLVED</div>${resourceList(extensionResolved)}`,
|
|
@@ -797,7 +797,7 @@
|
|
|
797
797
|
};
|
|
798
798
|
patch($("sys-pane"), (panes[state.sysPane] || panes.prompt)()); $("sys-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.sysPane));
|
|
799
799
|
}
|
|
800
|
-
function renderSystemPromptPane(agent, entry) { const prompt = typeof entry?.prompt === "string" ? entry.prompt : typeof agent.systemPrompt === "string" ? agent.systemPrompt : ""; const available = typeof entry?.prompt === "string" || typeof agent.systemPrompt === "string"; patch($("sys-pane"), `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="
|
|
800
|
+
function renderSystemPromptPane(agent, entry) { const prompt = typeof entry?.prompt === "string" ? entry.prompt : typeof agent.systemPrompt === "string" ? agent.systemPrompt : ""; const available = typeof entry?.prompt === "string" || typeof agent.systemPrompt === "string"; patch($("sys-pane"), `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="code">${available ? esc(prompt) : "System prompt unavailable"}</div>`); }
|
|
801
801
|
function renderAssistantToolCalls(entry, entries) { const toolCalls = toolCallsOf(entry); return toolCalls.length ? `<div class="assistant-tools">${toolCalls.map((call) => `<div class="assistant-tool"><span class="pill tool">${esc(call.name)}</span><div class="code">${highlightCode(json(toolArgsOf(call, entries)), "json")}</div></div>`).join("")}</div>` : ""; }
|
|
802
802
|
function renderAgentDetails(agent, root = $("insp")) { patch(root, `<div class="ins-head"><span class="pill context">AGENT</span><b class="tool-name">${esc(agent.label || agent.name)}</b><span class="ins-sub">${esc(agent.state || "")}</span></div><div class="itabs" id="sys-tabs"><span data-pane="prompt">Prompt</span><span data-pane="tools">Tools</span><span data-pane="skills">Skills</span><span data-pane="extensions">Extensions</span><span data-pane="env">Environment</span><span data-pane="output">Output</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPane(agent); }
|
|
803
803
|
function renderInspector(detail, agent, entries, root = $("insp")) { if (!detail) { patch(root, '<div class="ins-body hint">Select an event.</div>'); return; } if (detail.kind === "tool" || detail.kind === "skill") { patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span><b class="tool-name">${esc(toolNameOf(detail.entry))}</b><span class="ins-sub">${esc(detail.entry.timestamp || "")}</span></div><div class="itabs" id="tool-tabs"><span data-pane="summary">Summary</span><span data-pane="payload">Input</span><span data-pane="result">Output</span><span data-pane="schema">Schema</span><span data-pane="timing">Timing</span></div><div class="ins-body"><div id="tool-pane"></div></div>`); renderToolPane(detail, entries); return; } if (detail.kind === "system") { const prompt = typeof detail.entry.prompt === "string" ? detail.entry.prompt : agent.systemPrompt; patch(root, `<div class="ins-head"><span class="pill system">SYSTEM</span><span class="ins-sub">est. ${estTokens(prompt || "")} tok</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPromptPane(agent, detail.entry); return; } const message = detail.entry.message || detail.entry; const body = detail.kind === "assistant" ? assistantBodyText(message.content ?? detail.entry.content) : contentText(message.content ?? detail.entry.content); const tokenRows = messageTokenRows(detail.kind, message, body); const bodyHtml = detail.kind === "assistant" ? sanitizeMarkdown(marked.parse(body)) : esc(body); const tokenHtml = tokenRows ? `<div class="kv">${tokenRows}</div>` : ""; const assistantTools = detail.kind === "assistant" ? renderAssistantToolCalls(detail.entry, entries) : ""; patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span></div><div class="ins-body">${tokenHtml}<div class="${detail.kind === "assistant" ? "md" : "code"}">${bodyHtml}</div>${assistantTools}</div>`); }
|