@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +365 -127
  5. package/dist/src/background-widget.d.ts +0 -1
  6. package/dist/src/background-widget.js +4 -9
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +33 -37
  9. package/dist/src/execution.d.ts +2 -0
  10. package/dist/src/execution.js +5 -5
  11. package/dist/src/host-delivery.js +4 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +6 -6
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +1 -1
  18. package/dist/src/host-view.js +17 -15
  19. package/dist/src/host.js +62 -87
  20. package/dist/src/index.d.ts +1 -2
  21. package/dist/src/index.js +1523 -1630
  22. package/dist/src/index.js.map +3 -3
  23. package/dist/src/paths.d.ts +7 -6
  24. package/dist/src/paths.js +24 -15
  25. package/dist/src/persistence.d.ts +1 -0
  26. package/dist/src/persistence.js +1 -0
  27. package/dist/src/pi-runtime-runner.js +4 -1
  28. package/dist/src/registry.d.ts +9 -7
  29. package/dist/src/registry.js +28 -70
  30. package/dist/src/runtime/agent-runner.d.ts +2 -0
  31. package/dist/src/session-lease.d.ts +1 -1
  32. package/dist/src/session-lease.js +45 -11
  33. package/dist/src/settings.d.ts +6 -3
  34. package/dist/src/settings.js +36 -47
  35. package/dist/src/store.d.ts +3 -1
  36. package/dist/src/store.js +17 -1
  37. package/dist/src/trajectory.d.ts +0 -1
  38. package/dist/src/trajectory.js +2 -3
  39. package/dist/src/types.d.ts +100 -44
  40. package/dist/src/types.js +4 -7
  41. package/dist/src/utils.d.ts +3 -2
  42. package/dist/src/utils.js +20 -10
  43. package/dist/src/validation.d.ts +8 -2
  44. package/dist/src/validation.js +122 -86
  45. package/dist/starter/index.js +8 -9
  46. package/dist/starter/index.js.map +1 -1
  47. package/dist/starter/review-loop.js +8 -7
  48. package/dist/subagents/index.js +654 -814
  49. package/dist/subagents/index.js.map +3 -3
  50. package/dist/subagents/src/contracts.d.ts +4 -3
  51. package/dist/subagents/src/contracts.js +2 -2
  52. package/dist/subagents/src/index.d.ts +1 -1
  53. package/dist/subagents/src/index.js +1 -2
  54. package/dist/subagents/src/manager.js +49 -70
  55. package/dist/subagents/src/navigator.js +3 -14
  56. package/dist/subagents/src/view.d.ts +0 -1
  57. package/dist/subagents/src/view.js +2 -16
  58. package/dist/subagents/src/worktree.js +1 -1
  59. package/dist/trajectory/assets/index.html +8 -8
  60. package/dist/trajectory/index.js +487 -891
  61. package/dist/trajectory/index.js.map +3 -3
  62. package/dist/trajectory/src/assets/index.html +8 -8
  63. package/dist/trajectory/src/index.js +126 -86
  64. package/dist/trajectory/src/server.js +264 -25
  65. package/dist/trajectory/src/server.js.map +3 -3
  66. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  67. package/dist/trajectory/src/startup-mutex.js +172 -0
  68. package/examples/workflow-extension-template/README.md +4 -7
  69. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  70. package/examples/workflow-extension-template/index.js +0 -3
  71. package/package.json +5 -5
  72. package/skills/pi-extensible-workflows/SKILL.md +10 -8
  73. package/src/agent-execution.ts +339 -127
  74. package/src/background-widget.ts +4 -10
  75. package/src/decoders.ts +31 -37
  76. package/src/execution.ts +5 -5
  77. package/src/host-delivery.ts +4 -8
  78. package/src/host-navigator.ts +4 -2
  79. package/src/host-phases.ts +1 -1
  80. package/src/host-recovery.ts +20 -26
  81. package/src/host-runtime.ts +44 -6
  82. package/src/host-view.ts +18 -11
  83. package/src/host.ts +62 -113
  84. package/src/index.ts +1 -2
  85. package/src/paths.ts +18 -12
  86. package/src/persistence.ts +1 -0
  87. package/src/pi-runtime-runner.ts +3 -1
  88. package/src/registry.ts +27 -63
  89. package/src/runtime/agent-runner.ts +2 -0
  90. package/src/session-lease.ts +34 -6
  91. package/src/settings.ts +35 -47
  92. package/src/store.ts +18 -2
  93. package/src/trajectory.ts +2 -4
  94. package/src/types.ts +71 -30
  95. package/src/utils.ts +18 -9
  96. package/src/validation.ts +102 -79
  97. package/starter/index.ts +0 -1
  98. package/starter/review-loop.ts +8 -7
  99. package/starter/roles/developer.md +0 -1
  100. package/starter/roles/oracle.md +0 -1
  101. package/starter/roles/researcher.md +0 -1
  102. package/starter/roles/reviewer.md +0 -1
  103. package/starter/roles/scout.md +0 -1
  104. package/subagents/README.md +8 -14
  105. package/subagents/src/contracts.ts +3 -4
  106. package/subagents/src/index.ts +2 -3
  107. package/subagents/src/manager.ts +45 -125
  108. package/subagents/src/navigator.ts +4 -13
  109. package/subagents/src/view.ts +4 -14
  110. package/subagents/src/worktree.ts +1 -1
  111. package/trajectory/src/assets/index.html +8 -8
  112. package/trajectory/src/index.ts +106 -82
  113. package/trajectory/src/server.ts +44 -8
  114. package/trajectory/src/startup-mutex.ts +127 -0
  115. package/dist/src/roles.d.ts +0 -70
  116. package/dist/src/roles.js +0 -326
  117. package/dist/starter/roles/developer.md +0 -6
  118. package/dist/starter/roles/oracle.md +0 -18
  119. package/dist/starter/roles/researcher.md +0 -20
  120. package/dist/starter/roles/reviewer.md +0 -19
  121. package/dist/starter/roles/scout.md +0 -18
  122. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  123. 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: false });
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 role = notification.role?.trim() || "none";
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, deepFreeze, errorCode, errorText, finiteNumber, isNodeError, isThinkingLevel, jsonValue, object, loadingRegistry, localAgentTransport, resolveAgentResourcePolicy, resolveWorkflowSettings, roleNameOf, sanitizeDisplayText, sumAccounting, structuralPath, validateModelAliasAvailability, WorkflowAgentExecutor, WorkflowError, workflowProjectSettingsPath, workflowSettingsPath, SerialLane, } from "../../src/index.js";
6
- import { decodeAgentDefinition, decodeWorkflowExtensions } from "../../src/decoders.js";
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, } from "./contracts.js";
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 standaloneExtensionSettingsPath(cwd, agentDir, trustedProject, external) {
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
- validateStandaloneExtensionSources(context, external, registry, agentDir);
89
- const extensionSettings = trustedProject && external.projectExtensionSettings !== undefined ? external.projectExtensionSettings : external.globalExtensionSettings;
90
- const extensionSettingsPath = standaloneExtensionSettingsPath(extensionContext.cwd, agentDir, trustedProject, external);
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
- agentDefinitions: external.agentDefinitions,
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 role = request.role;
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) || !jsonValue(value) || typeof value.projectTrusted !== "boolean")
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
- const agentDefinitions = Object.create(null);
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
- await atomicJson(configurationPath(directory), external);
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 ?? standaloneExternalConfiguration(context, this.dependencies);
995
- if (external.projectTrusted && !context.extensionContext.isProjectTrusted())
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, external);
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 role = roleNameOf(run.request.role) ?? "none";
1614
- const label = run.request.label?.trim() || (role === "none" ? "subagent" : role);
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
- const role = requestRole(entry.request);
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, request } = inspection.entry;
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 role = requestRole(request);
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),
@@ -42,7 +42,6 @@ type SubagentReceipt = {
42
42
  readonly label: string;
43
43
  readonly state: string;
44
44
  readonly model?: string;
45
- readonly role?: string;
46
45
  readonly tools?: readonly string[];
47
46
  readonly input: number;
48
47
  readonly output: number;
@@ -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
- const role = metadataAvailable ? roleName(args.role) ?? "none" : undefined;
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.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
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
- agentTypes: [],
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) 80px minmax(150px, 1.5fr) 80px 72px 64px; gap: 8px; align-items: center; }
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">${esc(agent.role || "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("")}`;
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>ROLE</span><span>TOOL TIMING</span><span>DURATION</span><span>TOKENS</span><span>COST</span></div>${body}</div>`;
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), role: subagent.role || "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] : [] }; }
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><div class="ins-sub">${esc(agent.role || "agent")}</div></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>`; }
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 = { role: agent.role, 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 };
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="md">${promptAvailable ? sanitizeMarkdown(marked.parse(prompt, { mangle: false, headerIds: false })) : "Prompt unavailable"}</div>`,
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="md">${available ? sanitizeMarkdown(marked.parse(prompt, { mangle: false, headerIds: false })) : "System prompt unavailable"}</div>`); }
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>`); }