@agimon-ai/doompi-workflow 0.0.1-alpha.67 → 0.0.1-alpha.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/pi/workflow/piTools.cjs +3 -3
- package/dist/adapters/pi/workflow/piTools.cjs.map +1 -1
- package/dist/adapters/pi/workflow/piTools.mjs +3 -3
- package/dist/adapters/pi/workflow/piTools.mjs.map +1 -1
- package/package.json +9 -8
- package/src/web/components/ArtifactsPane.tsx +17 -23
- package/src/web/components/LaunchWorkflowDialog.tsx +18 -23
- package/src/web/components/StepTerminalPanel.tsx +10 -12
- package/src/web/components/WorkflowCatalogDrawer.tsx +10 -10
- package/src/web/components/WorkflowsActivitySection.tsx +9 -9
- package/src/web/components/WorkflowsPanel.tsx +30 -34
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const e=require("./piToolBridge.cjs"),t=require("../../../schemas/workflowPi.cjs"),n=require("../../../tui/workflow/workflowToolRender.cjs");let r=require("@agimon-ai/workflow-mcp"),i=require("@agimon-ai/doompi-extension-contracts/child-process"),a=require("zod");const o={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},s=[`list_workflows`,`launch_workflow`,`workflow_run`],[c,l,u]=s;function d(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function f(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function p(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function m(e,t){let n=p(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const h=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function g(e){return e instanceof Error?e:Error(String(e),{cause:e})}function _(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function v(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,_(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function y(t,n,r){t.then(t=>{if(t.kind===`error`){n.onLateFailure?.(g(t.error),r);return}t.result.isError&&n.onLateFailure?.(Error(e.toolResultText(t.result)||`Workflow launch failed.`),r)})}function b(t){return{async execute(n,r,a){let o=(0,i.resolveRootSessionId)(r.sessionManager.getSessionId());t.observeSession?.(o);let s=n.env?.AGIFLOW_JOB_KIND?.trim(),c=n.env?.AGIFLOW_JOB_ID?.trim();if(!!s!=!!c)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(s&&!n.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);d(a,l,`Checking workflow capacity...`);let u=e.resolveMaxConcurrent(),f=await t.activeRunCount?.()??0;if(f>=u)throw Error(e.withOptions(`This session is at capacity: ${f}/${u} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(n.runner){let e=t.rejectRunner?.(n.workflowPath,n.runner);if(e)throw Error(e)}d(a,l,`Launching workflow ${n.workflowPath}...`);let p={...n.env};if(process.env.AGIFLOW_DISPATCH_CONTEXT_FILE)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(p[e]=t)}let m=Date.now(),_=t.trackPendingRun(t.runTool.execute({...n,env:{...p,[e.PI_SESSION_ENV]:o}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),b=await v(_,t,{sessionId:o,since:m,workflowPath:n.workflowPath});if(b.kind===`settled`){let n=b.outcome;if(n.kind===`error`)throw g(n.error);let i=n.result;if(i.isError)throw Error(e.withOptions(e.toolResultText(i)||`Workflow launch failed.`,h));return await t.onLaunch?.(r),i}return y(_,t,r),await t.onLaunch?.(r),{content:[{type:`text`,text:b.kind===`record`?e.launchedRunSummary(b.record):e.launchHandoffSummary(n.workflowPath)}]}}}}function x(s,p={}){let g=p.feature??(0,r.createEmbeddedWorkflowFeature)(),_=p.runTool??g.runTool,v=p.listWorkflowsTool??g.listWorkflowsTool,y=p.recoverTool??g.createRecoverTool(),x=p.controlTool??g.createControlTool(),S=p.trackPendingRun??(e=>e),C=p.launchExecutor??b({activeRunCount:p.activeRunCount,onLaunch:p.onLaunch,observeSession:p.observeSession,rejectRunner:p.rejectRunner,runTool:_,trackPendingRun:S});s.registerTool({name:c,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:a.z.toJSONSchema(v.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(c,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(c,i.args,e,{...t,isError:i.isError},r),async execute(t,n){return e.toAgentToolResult(c,await v.execute(n))}}),s.registerTool({name:l,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:a.z.toJSONSchema(_.getInputSchema().pick(o)),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(l,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(l,i.args,e,{...t,isError:i.isError},r),async execute(t,n,r,i,a){let o=n,s=await C.execute(o,a,i);return f(e.toAgentToolResult(l,s,h),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),s.registerTool({name:u,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:a.z.toJSONSchema(t.workflowRunInputSchema),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(u,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(u,i.args,e,{...t,isError:i.isError},r),async execute(n,r,a,o,s){let c=t.workflowRunInputSchema.parse(r),l=(0,i.resolveRootSessionId)(s.sessionManager.getSessionId());p.observeSession?.(l);let h={runKey:c.runKey,workspace:c.workspace},g=async()=>{if(!p.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return p.requireSessionRun(h,l)};if(c.action===`status`){let e=await g();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:u}}}if(c.action===`tail`){if(!p.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await g();return d(o,u,`Reading output for workflow ${c.runKey}...`),await p.tailRun(h,s),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
|
|
2
|
-
`)}],details:{tool:
|
|
1
|
+
const e=require("./piToolBridge.cjs"),t=require("../../../schemas/workflowPi.cjs"),n=require("../../../tui/workflow/workflowToolRender.cjs");let r=require("@agimon-ai/workflow-mcp"),i=require("@agimon-ai/doompi-extension-contracts/child-process"),a=require("zod");const o=`AGIFLOW_PROJECT_ID`,s={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},c=[`list_workflows`,`launch_workflow`,`workflow_run`],[l,u,d]=c;function f(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function p(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function m(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function h(e,t){let n=m(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const g=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function _(e){return e instanceof Error?e:Error(String(e),{cause:e})}function v(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function y(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,v(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function b(t,n,r){t.then(t=>{if(t.kind===`error`){n.onLateFailure?.(_(t.error),r);return}t.result.isError&&n.onLateFailure?.(Error(e.toolResultText(t.result)||`Workflow launch failed.`),r)})}function x(t){return{async execute(n,r,a){let s=(0,i.resolveRootSessionId)(r.sessionManager.getSessionId());t.observeSession?.(s);let c=n.env?.AGIFLOW_JOB_KIND?.trim(),l=n.env?.AGIFLOW_JOB_ID?.trim(),d=n.env?.[o]?.trim();if(!!c!=!!l)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(c&&c!==`task`&&c!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(c&&!d)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(c&&!n.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);f(a,u,`Checking workflow capacity...`);let p=e.resolveMaxConcurrent(),m=await t.activeRunCount?.()??0;if(m>=p)throw Error(e.withOptions(`This session is at capacity: ${m}/${p} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(n.runner){let e=t.rejectRunner?.(n.workflowPath,n.runner);if(e)throw Error(e)}f(a,u,`Launching workflow ${n.workflowPath}...`);let h={...n.env},v=process.env.AGIFLOW_DISPATCH_CONTEXT_FILE,x=process.env[o]?.trim();if(v&&c&&x&&d&&x!==d)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${d} versus ${x}).`);if(v)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(h[e]=t)}let S=Date.now(),C=t.trackPendingRun(t.runTool.execute({...n,env:{...h,[e.PI_SESSION_ENV]:s}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),w=await y(C,t,{sessionId:s,since:S,workflowPath:n.workflowPath});if(w.kind===`settled`){let n=w.outcome;if(n.kind===`error`)throw _(n.error);let i=n.result;if(i.isError)throw Error(e.withOptions(e.toolResultText(i)||`Workflow launch failed.`,g));return await t.onLaunch?.(r),i}return b(C,t,r),await t.onLaunch?.(r),{content:[{type:`text`,text:w.kind===`record`?e.launchedRunSummary(w.record):e.launchHandoffSummary(n.workflowPath)}]}}}}function S(o,c={}){let m=c.feature??(0,r.createEmbeddedWorkflowFeature)(),_=c.runTool??m.runTool,v=c.listWorkflowsTool??m.listWorkflowsTool,y=c.recoverTool??m.createRecoverTool(),b=c.controlTool??m.createControlTool(),S=c.trackPendingRun??(e=>e),C=c.launchExecutor??x({activeRunCount:c.activeRunCount,onLaunch:c.onLaunch,observeSession:c.observeSession,rejectRunner:c.rejectRunner,runTool:_,trackPendingRun:S});o.registerTool({name:l,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:a.z.toJSONSchema(v.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(l,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(l,i.args,e,{...t,isError:i.isError},r),async execute(t,n){return e.toAgentToolResult(l,await v.execute(n))}}),o.registerTool({name:u,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:a.z.toJSONSchema(_.getInputSchema().pick(s)),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(u,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(u,i.args,e,{...t,isError:i.isError},r),async execute(t,n,r,i,a){let o=n,s=await C.execute(o,a,i);return p(e.toAgentToolResult(u,s,g),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),o.registerTool({name:d,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:a.z.toJSONSchema(t.workflowRunInputSchema),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(d,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(d,i.args,e,{...t,isError:i.isError},r),async execute(n,r,a,o,s){let l=t.workflowRunInputSchema.parse(r),u=(0,i.resolveRootSessionId)(s.sessionManager.getSessionId());c.observeSession?.(u);let m={runKey:l.runKey,workspace:l.workspace},g=async()=>{if(!c.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return c.requireSessionRun(m,u)};if(l.action===`status`){let e=await g();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:d}}}if(l.action===`tail`){if(!c.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await g();return f(o,d,`Reading output for workflow ${l.runKey}...`),await c.tailRun(m,s),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
|
|
2
|
+
`)}],details:{tool:d}}}if(l.action===`recovery-evidence`){if(!c.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return f(o,d,`Reading recovery evidence for workflow ${l.runKey}...`),{content:[{type:`text`,text:await c.readRecoveryEvidence(m,s)}],details:{tool:d}}}if(l.action===`follow`){if(!c.followRun)throw Error(`Following workflow output is not available in this session.`);return await g(),f(o,d,`Following workflow ${l.runKey}...`),{content:[{type:`text`,text:await c.followRun(m,s)}],details:{tool:d}}}if(l.action===`open`){if(!c.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await g(),f(o,d,`Opening workflow ${l.runKey}...`),{content:[{type:`text`,text:await c.openRun(m,s)}],details:{tool:d}}}if(l.action===`recover`){if(!c.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let t=await c.requireRecoverableRun(m),n={runKey:t.runKey,workspace:t.workspace};if(f(o,d,`Recovering workflow ${l.runKey}...`),!l.dryRun){let e=await c.delegateRecovery?.({...n,runner:l.runner},s);if(e){let t=h(l.action,l.runKey);return await c.onLaunch?.(s),{content:[{type:`text`,text:[e,t[0]].join(`
|
|
3
3
|
|
|
4
|
-
`)}],details:{tool:
|
|
4
|
+
`)}],details:{tool:d}}}}let r={dryRun:l.dryRun,job:l.job,runKey:n.runKey,runner:l.runner,workspace:n.workspace},i=await S(y.execute(r)),a=h(l.action,l.runKey),u=e.toAgentToolResult(d,i,a);return await c.onLaunch?.(s),p(u,a[0])}let _=await g();if(!_.runId)throw Error(`Workflow ${l.runKey} has no controllable run generation.`);f(o,d,`Requesting ${l.action} for workflow ${l.runKey}...`);let v=await b.execute({action:l.action,expectedRunId:_.runId,reason:l.reason,runKey:l.runKey,workspace:l.workspace}),x=h(l.action,l.runKey);return p(e.toAgentToolResult(d,v,x),x[0])}})}exports.WORKFLOW_PI_TOOL_NAMES=c,exports.createWorkflowLaunchExecutor=x,exports.registerWorkflowPiTools=S;
|
|
5
5
|
//# sourceMappingURL=piTools.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piTools.cjs","names":["toolResultText","resolveRootSessionId","resolveMaxConcurrent","withOptions","PI_SESSION_ENV","launchedRunSummary","launchHandoffSummary","createEmbeddedWorkflowFeature","z","renderWorkflowToolCall","renderWorkflowToolResult","toAgentToolResult","workflowRunInputSchema"],"sources":["../../../../src/adapters/pi/workflow/piTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type EmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n} from '@agimon-ai/workflow-mcp';\nimport type {\n AgentToolResult,\n AgentToolUpdateCallback,\n ExtensionAPI,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { type WorkflowRunInput, workflowRunInputSchema } from '../../../schemas/workflowPi.ts';\nimport { renderWorkflowToolCall, renderWorkflowToolResult } from '../../../tui/workflow/workflowToolRender.ts';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toAgentToolResult,\n toolResultText,\n withOptions,\n} from './piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\n\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\n\nexport const WORKFLOW_PI_TOOL_NAMES = ['list_workflows', 'launch_workflow', 'workflow_run'] as const;\nconst [LIST_WORKFLOWS_TOOL_NAME, LAUNCH_WORKFLOW_TOOL_NAME, WORKFLOW_RUN_TOOL_NAME] = WORKFLOW_PI_TOOL_NAMES;\n\ntype WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nfunction reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nfunction appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nfunction actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nconst launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent();\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n if (process.env.AGIFLOW_DISPATCH_CONTEXT_FILE) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = process.env[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n\nexport function registerWorkflowPiTools(pi: ExtensionAPI, dependencies: WorkflowPiToolDependencies = {}): void {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n pi.registerTool({\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LIST_WORKFLOWS_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n });\n\n pi.registerTool({\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LAUNCH_WORKFLOW_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LAUNCH_WORKFLOW_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n });\n\n pi.registerTool({\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n renderCall: (args, theme) => renderWorkflowToolCall(WORKFLOW_RUN_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n WORKFLOW_RUN_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"piTools.cjs","names":["toolResultText","resolveRootSessionId","resolveMaxConcurrent","withOptions","PI_SESSION_ENV","launchedRunSummary","launchHandoffSummary","createEmbeddedWorkflowFeature","z","renderWorkflowToolCall","renderWorkflowToolResult","toAgentToolResult","workflowRunInputSchema"],"sources":["../../../../src/adapters/pi/workflow/piTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type EmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n} from '@agimon-ai/workflow-mcp';\nimport type {\n AgentToolResult,\n AgentToolUpdateCallback,\n ExtensionAPI,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { type WorkflowRunInput, workflowRunInputSchema } from '../../../schemas/workflowPi.ts';\nimport { renderWorkflowToolCall, renderWorkflowToolResult } from '../../../tui/workflow/workflowToolRender.ts';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toAgentToolResult,\n toolResultText,\n withOptions,\n} from './piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\n\nexport const WORKFLOW_PI_TOOL_NAMES = ['list_workflows', 'launch_workflow', 'workflow_run'] as const;\nconst [LIST_WORKFLOWS_TOOL_NAME, LAUNCH_WORKFLOW_TOOL_NAME, WORKFLOW_RUN_TOOL_NAME] = WORKFLOW_PI_TOOL_NAMES;\n\ntype WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nfunction reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nfunction appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nfunction actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nconst launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent();\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = process.env.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = process.env[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = process.env[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n\nexport function registerWorkflowPiTools(pi: ExtensionAPI, dependencies: WorkflowPiToolDependencies = {}): void {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n pi.registerTool({\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LIST_WORKFLOWS_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n });\n\n pi.registerTool({\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LAUNCH_WORKFLOW_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LAUNCH_WORKFLOW_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n });\n\n pi.registerTool({\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n renderCall: (args, theme) => renderWorkflowToolCall(WORKFLOW_RUN_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n WORKFLOW_RUN_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{PI_SESSION_ENV as e,launchHandoffSummary as t,launchedRunSummary as n,resolveMaxConcurrent as r,toAgentToolResult as i,toolResultText as a,withOptions as o}from"./piToolBridge.mjs";import{workflowRunInputSchema as s}from"../../../schemas/workflowPi.mjs";import{renderWorkflowToolCall as c,renderWorkflowToolResult as l}from"../../../tui/workflow/workflowToolRender.mjs";import{createEmbeddedWorkflowFeature as u}from"@agimon-ai/workflow-mcp";import{resolveRootSessionId as d}from"@agimon-ai/doompi-extension-contracts/child-process";import{z as f}from"zod";const p={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},
|
|
2
|
-
`)}],details:{tool:
|
|
1
|
+
import{PI_SESSION_ENV as e,launchHandoffSummary as t,launchedRunSummary as n,resolveMaxConcurrent as r,toAgentToolResult as i,toolResultText as a,withOptions as o}from"./piToolBridge.mjs";import{workflowRunInputSchema as s}from"../../../schemas/workflowPi.mjs";import{renderWorkflowToolCall as c,renderWorkflowToolResult as l}from"../../../tui/workflow/workflowToolRender.mjs";import{createEmbeddedWorkflowFeature as u}from"@agimon-ai/workflow-mcp";import{resolveRootSessionId as d}from"@agimon-ai/doompi-extension-contracts/child-process";import{z as f}from"zod";const p=`AGIFLOW_PROJECT_ID`,m={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},h=[`list_workflows`,`launch_workflow`,`workflow_run`],[g,_,v]=h;function y(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function b(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function x(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function S(e,t){let n=x(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const C=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function w(e){return e instanceof Error?e:Error(String(e),{cause:e})}function T(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function E(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,T(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function D(e,t,n){e.then(e=>{if(e.kind===`error`){t.onLateFailure?.(w(e.error),n);return}e.result.isError&&t.onLateFailure?.(Error(a(e.result)||`Workflow launch failed.`),n)})}function O(i){return{async execute(s,c,l){let u=d(c.sessionManager.getSessionId());i.observeSession?.(u);let f=s.env?.AGIFLOW_JOB_KIND?.trim(),m=s.env?.AGIFLOW_JOB_ID?.trim(),h=s.env?.[p]?.trim();if(!!f!=!!m)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(f&&f!==`task`&&f!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(f&&!h)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(f&&!s.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);y(l,_,`Checking workflow capacity...`);let g=r(),v=await i.activeRunCount?.()??0;if(v>=g)throw Error(o(`This session is at capacity: ${v}/${g} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(s.runner){let e=i.rejectRunner?.(s.workflowPath,s.runner);if(e)throw Error(e)}y(l,_,`Launching workflow ${s.workflowPath}...`);let b={...s.env},x=process.env.AGIFLOW_DISPATCH_CONTEXT_FILE,S=process.env[p]?.trim();if(x&&f&&S&&h&&S!==h)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${h} versus ${S}).`);if(x)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(b[e]=t)}let T=Date.now(),O=i.trackPendingRun(i.runTool.execute({...s,env:{...b,[e]:u}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),k=await E(O,i,{sessionId:u,since:T,workflowPath:s.workflowPath});if(k.kind===`settled`){let e=k.outcome;if(e.kind===`error`)throw w(e.error);let t=e.result;if(t.isError)throw Error(o(a(t)||`Workflow launch failed.`,C));return await i.onLaunch?.(c),t}return D(O,i,c),await i.onLaunch?.(c),{content:[{type:`text`,text:k.kind===`record`?n(k.record):t(s.workflowPath)}]}}}}function k(e,t={}){let n=t.feature??u(),r=t.runTool??n.runTool,a=t.listWorkflowsTool??n.listWorkflowsTool,o=t.recoverTool??n.createRecoverTool(),p=t.controlTool??n.createControlTool(),h=t.trackPendingRun??(e=>e),x=t.launchExecutor??O({activeRunCount:t.activeRunCount,onLaunch:t.onLaunch,observeSession:t.observeSession,rejectRunner:t.rejectRunner,runTool:r,trackPendingRun:h});e.registerTool({name:g,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:f.toJSONSchema(a.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>c(g,e,t),renderResult:(e,t,n,r)=>l(g,r.args,e,{...t,isError:r.isError},n),async execute(e,t){return i(g,await a.execute(t))}}),e.registerTool({name:_,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:f.toJSONSchema(r.getInputSchema().pick(m)),renderShell:`self`,renderCall:(e,t)=>c(_,e,t),renderResult:(e,t,n,r)=>l(_,r.args,e,{...t,isError:r.isError},n),async execute(e,t,n,r,a){let o=t,s=await x.execute(o,a,r);return b(i(_,s,C),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),e.registerTool({name:v,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:f.toJSONSchema(s),renderShell:`self`,renderCall:(e,t)=>c(v,e,t),renderResult:(e,t,n,r)=>l(v,r.args,e,{...t,isError:r.isError},n),async execute(e,n,r,a,c){let l=s.parse(n),u=d(c.sessionManager.getSessionId());t.observeSession?.(u);let f={runKey:l.runKey,workspace:l.workspace},m=async()=>{if(!t.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return t.requireSessionRun(f,u)};if(l.action===`status`){let e=await m();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:v}}}if(l.action===`tail`){if(!t.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await m();return y(a,v,`Reading output for workflow ${l.runKey}...`),await t.tailRun(f,c),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
|
|
2
|
+
`)}],details:{tool:v}}}if(l.action===`recovery-evidence`){if(!t.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return y(a,v,`Reading recovery evidence for workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.readRecoveryEvidence(f,c)}],details:{tool:v}}}if(l.action===`follow`){if(!t.followRun)throw Error(`Following workflow output is not available in this session.`);return await m(),y(a,v,`Following workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.followRun(f,c)}],details:{tool:v}}}if(l.action===`open`){if(!t.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await m(),y(a,v,`Opening workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.openRun(f,c)}],details:{tool:v}}}if(l.action===`recover`){if(!t.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let e=await t.requireRecoverableRun(f),n={runKey:e.runKey,workspace:e.workspace};if(y(a,v,`Recovering workflow ${l.runKey}...`),!l.dryRun){let e=await t.delegateRecovery?.({...n,runner:l.runner},c);if(e){let n=S(l.action,l.runKey);return await t.onLaunch?.(c),{content:[{type:`text`,text:[e,n[0]].join(`
|
|
3
3
|
|
|
4
|
-
`)}],details:{tool:
|
|
4
|
+
`)}],details:{tool:v}}}}let r={dryRun:l.dryRun,job:l.job,runKey:n.runKey,runner:l.runner,workspace:n.workspace},s=await h(o.execute(r)),u=S(l.action,l.runKey),d=i(v,s,u);return await t.onLaunch?.(c),b(d,u[0])}let g=await m();if(!g.runId)throw Error(`Workflow ${l.runKey} has no controllable run generation.`);y(a,v,`Requesting ${l.action} for workflow ${l.runKey}...`);let _=await p.execute({action:l.action,expectedRunId:g.runId,reason:l.reason,runKey:l.runKey,workspace:l.workspace}),x=S(l.action,l.runKey);return b(i(v,_,x),x[0])}})}export{h as WORKFLOW_PI_TOOL_NAMES,O as createWorkflowLaunchExecutor,k as registerWorkflowPiTools};
|
|
5
5
|
//# sourceMappingURL=piTools.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piTools.mjs","names":[],"sources":["../../../../src/adapters/pi/workflow/piTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type EmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n} from '@agimon-ai/workflow-mcp';\nimport type {\n AgentToolResult,\n AgentToolUpdateCallback,\n ExtensionAPI,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { type WorkflowRunInput, workflowRunInputSchema } from '../../../schemas/workflowPi.ts';\nimport { renderWorkflowToolCall, renderWorkflowToolResult } from '../../../tui/workflow/workflowToolRender.ts';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toAgentToolResult,\n toolResultText,\n withOptions,\n} from './piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\n\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\n\nexport const WORKFLOW_PI_TOOL_NAMES = ['list_workflows', 'launch_workflow', 'workflow_run'] as const;\nconst [LIST_WORKFLOWS_TOOL_NAME, LAUNCH_WORKFLOW_TOOL_NAME, WORKFLOW_RUN_TOOL_NAME] = WORKFLOW_PI_TOOL_NAMES;\n\ntype WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nfunction reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nfunction appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nfunction actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nconst launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent();\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n if (process.env.AGIFLOW_DISPATCH_CONTEXT_FILE) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = process.env[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n\nexport function registerWorkflowPiTools(pi: ExtensionAPI, dependencies: WorkflowPiToolDependencies = {}): void {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n pi.registerTool({\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LIST_WORKFLOWS_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n });\n\n pi.registerTool({\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LAUNCH_WORKFLOW_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LAUNCH_WORKFLOW_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n });\n\n pi.registerTool({\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n renderCall: (args, theme) => renderWorkflowToolCall(WORKFLOW_RUN_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n WORKFLOW_RUN_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"piTools.mjs","names":[],"sources":["../../../../src/adapters/pi/workflow/piTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type EmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n} from '@agimon-ai/workflow-mcp';\nimport type {\n AgentToolResult,\n AgentToolUpdateCallback,\n ExtensionAPI,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { type WorkflowRunInput, workflowRunInputSchema } from '../../../schemas/workflowPi.ts';\nimport { renderWorkflowToolCall, renderWorkflowToolResult } from '../../../tui/workflow/workflowToolRender.ts';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toAgentToolResult,\n toolResultText,\n withOptions,\n} from './piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\n\nexport const WORKFLOW_PI_TOOL_NAMES = ['list_workflows', 'launch_workflow', 'workflow_run'] as const;\nconst [LIST_WORKFLOWS_TOOL_NAME, LAUNCH_WORKFLOW_TOOL_NAME, WORKFLOW_RUN_TOOL_NAME] = WORKFLOW_PI_TOOL_NAMES;\n\ntype WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nfunction reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nfunction appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nfunction actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nconst launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent();\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = process.env.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = process.env[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = process.env[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n\nexport function registerWorkflowPiTools(pi: ExtensionAPI, dependencies: WorkflowPiToolDependencies = {}): void {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n pi.registerTool({\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LIST_WORKFLOWS_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n });\n\n pi.registerTool({\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n renderCall: (args, theme) =>\n renderWorkflowToolCall(LAUNCH_WORKFLOW_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n LAUNCH_WORKFLOW_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n });\n\n pi.registerTool({\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n renderCall: (args, theme) => renderWorkflowToolCall(WORKFLOW_RUN_TOOL_NAME, args as Record<string, unknown>, theme),\n renderResult: (result, options, theme, context) =>\n renderWorkflowToolResult(\n WORKFLOW_RUN_TOOL_NAME,\n context.args as Record<string, unknown>,\n result,\n { ...options, isError: context.isError },\n theme,\n ),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-workflow",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.69",
|
|
4
4
|
"description": "GitHub Actions-style workflow graphs, artifacts, recovery, and asynchronous runs for DoomPi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"src/web",
|
|
35
35
|
"src/exports/webClient.ts",
|
|
36
36
|
"src/types/webWorkflows.ts",
|
|
37
|
-
"src/types/webWorkflowTerminal.ts"
|
|
37
|
+
"src/types/webWorkflowTerminal.ts",
|
|
38
|
+
"!src/web/**/*.stories.tsx"
|
|
38
39
|
],
|
|
39
40
|
"type": "module",
|
|
40
41
|
"main": "./dist/index.cjs",
|
|
@@ -68,12 +69,12 @@
|
|
|
68
69
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
69
70
|
"hono": "4.13.5",
|
|
70
71
|
"zod": "4.5.4",
|
|
71
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
72
|
-
"@agimon-ai/doompi-
|
|
73
|
-
"@agimon-ai/doompi-web-
|
|
74
|
-
"@agimon-ai/doompi-
|
|
75
|
-
"@agimon-ai/doompi-web-contracts": "0.0.1-alpha.
|
|
76
|
-
"@agimon-ai/doompi-web-
|
|
72
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.67",
|
|
73
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.66",
|
|
74
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.27",
|
|
75
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.68",
|
|
76
|
+
"@agimon-ai/doompi-web-contracts": "0.0.1-alpha.30",
|
|
77
|
+
"@agimon-ai/doompi-web-security": "0.0.1-alpha.29"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
|
79
80
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
@@ -76,7 +76,7 @@ function DelimitedPreview({ text, delimiter }: { text: string; delimiter: ',' |
|
|
|
76
76
|
const [head = [], ...body] = rows;
|
|
77
77
|
return (
|
|
78
78
|
<div className="overflow-auto">
|
|
79
|
-
<table data-testid="artifact-table" className="min-w-full border-collapse font-mono text-
|
|
79
|
+
<table data-testid="artifact-table" className="min-w-full border-collapse font-mono text-sm">
|
|
80
80
|
<thead className="sticky top-0 bg-doom-panel text-doom-hi">
|
|
81
81
|
<tr>
|
|
82
82
|
{head.map((cell, index) => (
|
|
@@ -142,12 +142,12 @@ function ArtifactPreview({
|
|
|
142
142
|
return raw ? (
|
|
143
143
|
<pre
|
|
144
144
|
data-testid="artifact-raw"
|
|
145
|
-
className="whitespace-pre-wrap break-words font-mono text-
|
|
145
|
+
className="whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text"
|
|
146
146
|
>
|
|
147
147
|
{text}
|
|
148
148
|
</pre>
|
|
149
149
|
) : (
|
|
150
|
-
<div data-testid="artifact-markdown" className="max-w-5xl text-
|
|
150
|
+
<div data-testid="artifact-markdown" className="max-w-5xl text-base text-doom-text">
|
|
151
151
|
<Markdown text={text} />
|
|
152
152
|
</div>
|
|
153
153
|
);
|
|
@@ -160,9 +160,7 @@ function ArtifactPreview({
|
|
|
160
160
|
// An incomplete file remains useful as literal text while a workflow writes it.
|
|
161
161
|
}
|
|
162
162
|
return (
|
|
163
|
-
<pre className="whitespace-pre-wrap break-words font-mono text-
|
|
164
|
-
{formatted}
|
|
165
|
-
</pre>
|
|
163
|
+
<pre className="whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text">{formatted}</pre>
|
|
166
164
|
);
|
|
167
165
|
}
|
|
168
166
|
if (mimeType === 'text/csv' || mimeType === 'text/tab-separated-values') {
|
|
@@ -216,9 +214,7 @@ function ArtifactPreview({
|
|
|
216
214
|
);
|
|
217
215
|
}
|
|
218
216
|
if (content.text !== undefined) {
|
|
219
|
-
return
|
|
220
|
-
<pre className="whitespace-pre-wrap break-words font-mono text-[11px] leading-[17px] text-doom-text">{text}</pre>
|
|
221
|
-
);
|
|
217
|
+
return <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text">{text}</pre>;
|
|
222
218
|
}
|
|
223
219
|
return (
|
|
224
220
|
<EmptyState title="preview unavailable" description="Download this artifact to open it with a local application." />
|
|
@@ -278,9 +274,9 @@ export function ArtifactViewerPanel({
|
|
|
278
274
|
return (
|
|
279
275
|
<div data-testid="artifact-viewer" className="flex min-h-0 flex-1 flex-col px-[26px] py-[18px]">
|
|
280
276
|
<div className="flex items-center gap-2.5 pb-3">
|
|
281
|
-
<span className="truncate text-
|
|
277
|
+
<span className="truncate text-sm font-bold text-doom-hi">{path}</span>
|
|
282
278
|
<Badge size="xs">{mimeType ?? 'artifact'}</Badge>
|
|
283
|
-
<span className="text-
|
|
279
|
+
<span className="text-2xs text-doom-faint">
|
|
284
280
|
{content === undefined ? '' : `${formatSize(content.size)} · written ${formatWhen(content.modifiedAt)}`}
|
|
285
281
|
</span>
|
|
286
282
|
<span className="min-w-0 flex-1" />
|
|
@@ -320,17 +316,17 @@ export function ArtifactViewerPanel({
|
|
|
320
316
|
</div>
|
|
321
317
|
<div className="min-h-0 flex-1 overflow-auto rounded-md border border-doom-border bg-doom-deep p-4">
|
|
322
318
|
{error !== undefined ? (
|
|
323
|
-
<span data-testid="artifact-error" className="text-
|
|
319
|
+
<span data-testid="artifact-error" className="text-xs text-doom-yellow">
|
|
324
320
|
{error}
|
|
325
321
|
</span>
|
|
326
322
|
) : content === undefined ? (
|
|
327
|
-
<span className="text-
|
|
323
|
+
<span className="text-xs text-doom-faint">loading preview</span>
|
|
328
324
|
) : (
|
|
329
325
|
<ArtifactPreview content={content} rawUrl={rawUrl} raw={raw} />
|
|
330
326
|
)}
|
|
331
327
|
</div>
|
|
332
328
|
{content?.truncated === true ? (
|
|
333
|
-
<span className="pt-2 text-
|
|
329
|
+
<span className="pt-2 text-2xs text-doom-faint">
|
|
334
330
|
this preview shows the first {formatSize(content.text?.length)}; download the artifact for the complete file
|
|
335
331
|
</span>
|
|
336
332
|
) : null}
|
|
@@ -397,8 +393,8 @@ export function ArtifactsPane({
|
|
|
397
393
|
className={`flex flex-col gap-0.5 px-3 py-1.5 text-left ${openable ? 'cursor-pointer hover:bg-doom-panel' : 'cursor-default'}`}
|
|
398
394
|
>
|
|
399
395
|
<span className="flex min-w-0 items-center gap-2">
|
|
400
|
-
<span className={`w-3 shrink-0 text-
|
|
401
|
-
<span className={`truncate text-
|
|
396
|
+
<span className={`w-3 shrink-0 text-xs ${glyph.className}`}>{glyph.glyph}</span>
|
|
397
|
+
<span className={`truncate text-sm ${entry.state === 'pending' ? 'text-doom-dim' : 'text-doom-hi'}`}>
|
|
402
398
|
{entry.path}
|
|
403
399
|
</span>
|
|
404
400
|
{entry.producedBy.length === 0 ? null : (
|
|
@@ -407,14 +403,14 @@ export function ArtifactsPane({
|
|
|
407
403
|
</Badge>
|
|
408
404
|
)}
|
|
409
405
|
<span className="min-w-0 flex-1" />
|
|
410
|
-
<span className="shrink-0 text-
|
|
406
|
+
<span className="shrink-0 text-2xs text-doom-faint">
|
|
411
407
|
{entry.state === 'pending'
|
|
412
408
|
? 'not written yet'
|
|
413
409
|
: `${formatSize(entry.size)} · ${formatWhen(entry.modifiedAt)}`}
|
|
414
410
|
</span>
|
|
415
411
|
</span>
|
|
416
412
|
{entry.description === '' ? null : (
|
|
417
|
-
<span className="truncate pl-5 text-
|
|
413
|
+
<span className="truncate pl-5 text-2xs text-doom-faint">{entry.description}</span>
|
|
418
414
|
)}
|
|
419
415
|
</button>
|
|
420
416
|
);
|
|
@@ -430,7 +426,7 @@ export function ArtifactsPane({
|
|
|
430
426
|
) : null}
|
|
431
427
|
{declared.length === 0 ? null : (
|
|
432
428
|
<>
|
|
433
|
-
<span className="px-3 pb-1 pt-2 text-
|
|
429
|
+
<span className="px-3 pb-1 pt-2 text-2xs font-bold tracking-wider text-doom-faint">
|
|
434
430
|
DECLARED · {listing?.description || 'run-directory'}
|
|
435
431
|
</span>
|
|
436
432
|
{declared.map((entry) => (
|
|
@@ -440,16 +436,14 @@ export function ArtifactsPane({
|
|
|
440
436
|
)}
|
|
441
437
|
{found.length === 0 ? null : (
|
|
442
438
|
<>
|
|
443
|
-
<span className="px-3 pb-1 pt-3 text-
|
|
444
|
-
ALSO IN THE FOLDER
|
|
445
|
-
</span>
|
|
439
|
+
<span className="px-3 pb-1 pt-3 text-2xs font-bold tracking-wider text-doom-faint">ALSO IN THE FOLDER</span>
|
|
446
440
|
{found.map((entry) => (
|
|
447
441
|
<Row key={entry.path} entry={entry} />
|
|
448
442
|
))}
|
|
449
443
|
</>
|
|
450
444
|
)}
|
|
451
445
|
{listing === undefined ? null : (
|
|
452
|
-
<span className="truncate px-3 py-2 text-
|
|
446
|
+
<span className="truncate px-3 py-2 text-2xs text-doom-faint">{listing.runDir}</span>
|
|
453
447
|
)}
|
|
454
448
|
</div>
|
|
455
449
|
);
|
|
@@ -32,7 +32,7 @@ const BOOLEAN_TYPE = 'boolean';
|
|
|
32
32
|
const BOOLEAN_VALUES = ['true', 'false'] as const;
|
|
33
33
|
|
|
34
34
|
function FieldLabel({ children }: { children: string }) {
|
|
35
|
-
return <span className="text-
|
|
35
|
+
return <span className="text-2xs font-bold tracking-widest text-doom-faint">{children}</span>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** One `workflow_dispatch` input: a picker when the workflow constrains it, a field otherwise. */
|
|
@@ -58,7 +58,7 @@ function InputField({
|
|
|
58
58
|
className="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-2.5"
|
|
59
59
|
data-testid={`launch-input-${input.name}`}
|
|
60
60
|
>
|
|
61
|
-
<span className="min-w-0 truncate text-
|
|
61
|
+
<span className="min-w-0 truncate text-xs font-bold text-doom-text sm:w-28 sm:shrink-0">
|
|
62
62
|
{input.name}
|
|
63
63
|
{input.required === true ? <span className="text-doom-yellow"> *</span> : null}
|
|
64
64
|
</span>
|
|
@@ -71,7 +71,7 @@ function InputField({
|
|
|
71
71
|
/>
|
|
72
72
|
) : (
|
|
73
73
|
<Select value={value === '' ? undefined : value} onValueChange={onChange}>
|
|
74
|
-
<SelectTrigger className="h-7 w-full min-w-0 text-
|
|
74
|
+
<SelectTrigger className="h-7 w-full min-w-0 text-xs sm:min-w-[160px]">
|
|
75
75
|
<SelectValue placeholder="choose…" />
|
|
76
76
|
</SelectTrigger>
|
|
77
77
|
<SelectContent>
|
|
@@ -83,7 +83,7 @@ function InputField({
|
|
|
83
83
|
</SelectContent>
|
|
84
84
|
</Select>
|
|
85
85
|
)}
|
|
86
|
-
<span className="min-w-0 truncate text-
|
|
86
|
+
<span className="min-w-0 truncate text-2xs text-doom-faint sm:w-40 sm:shrink-0">{hint}</span>
|
|
87
87
|
</div>
|
|
88
88
|
);
|
|
89
89
|
}
|
|
@@ -144,7 +144,7 @@ export function LaunchWorkflowDialog({
|
|
|
144
144
|
<DialogContent width="lg" data-testid="launch-workflow-dialog" aria-describedby={undefined}>
|
|
145
145
|
<DialogHeader>
|
|
146
146
|
<div className="flex min-w-0 flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2.5">
|
|
147
|
-
<span className="text-
|
|
147
|
+
<span className="text-2xs text-doom-faint">launch workflow</span>
|
|
148
148
|
<DialogTitle data-testid="launch-workflow-name" className="max-w-full break-words">
|
|
149
149
|
{workflow.name}
|
|
150
150
|
</DialogTitle>
|
|
@@ -154,7 +154,7 @@ export function LaunchWorkflowDialog({
|
|
|
154
154
|
</div>
|
|
155
155
|
</DialogHeader>
|
|
156
156
|
<DialogBody>
|
|
157
|
-
<p className="text-
|
|
157
|
+
<p className="text-sm leading-relaxed text-doom-dim">
|
|
158
158
|
{workflow.description || 'This workflow declares no description.'}
|
|
159
159
|
</p>
|
|
160
160
|
<div className="flex flex-col gap-1.5">
|
|
@@ -168,15 +168,13 @@ export function LaunchWorkflowDialog({
|
|
|
168
168
|
onChange={(event) => setPrompt(event.target.value)}
|
|
169
169
|
onKeyDown={onPromptKeyDown}
|
|
170
170
|
/>
|
|
171
|
-
<span className="text-
|
|
171
|
+
<span className="text-2xs text-doom-faint">enter launches · shift+enter for a new line</span>
|
|
172
172
|
</div>
|
|
173
173
|
{workflow.inputs.length === 0 ? null : (
|
|
174
174
|
<div className="flex flex-col gap-2">
|
|
175
175
|
<div className="flex items-center gap-2">
|
|
176
176
|
<FieldLabel>INPUTS</FieldLabel>
|
|
177
|
-
<span className="text-
|
|
178
|
-
workflow_dispatch · {workflow.inputs.length} declared
|
|
179
|
-
</span>
|
|
177
|
+
<span className="text-2xs text-doom-faint">workflow_dispatch · {workflow.inputs.length} declared</span>
|
|
180
178
|
</div>
|
|
181
179
|
{workflow.inputs.map((input) => (
|
|
182
180
|
<InputField
|
|
@@ -192,13 +190,10 @@ export function LaunchWorkflowDialog({
|
|
|
192
190
|
<div className="flex flex-col gap-1.5">
|
|
193
191
|
<FieldLabel>RUNNER</FieldLabel>
|
|
194
192
|
{workflow.runners === undefined || workflow.runners.length === 0 ? (
|
|
195
|
-
<span className="flex h-7 items-center text-
|
|
193
|
+
<span className="flex h-7 items-center text-xs text-doom-dim">whatever the workflow resolves</span>
|
|
196
194
|
) : (
|
|
197
195
|
<Select value={runner} onValueChange={setRunner}>
|
|
198
|
-
<SelectTrigger
|
|
199
|
-
data-testid="launch-runner"
|
|
200
|
-
className="h-7 w-full min-w-0 text-[10px] sm:min-w-[160px]"
|
|
201
|
-
>
|
|
196
|
+
<SelectTrigger data-testid="launch-runner" className="h-7 w-full min-w-0 text-xs sm:min-w-[160px]">
|
|
202
197
|
<SelectValue />
|
|
203
198
|
</SelectTrigger>
|
|
204
199
|
<SelectContent>
|
|
@@ -210,36 +205,36 @@ export function LaunchWorkflowDialog({
|
|
|
210
205
|
</SelectContent>
|
|
211
206
|
</Select>
|
|
212
207
|
)}
|
|
213
|
-
<span className="text-
|
|
208
|
+
<span className="text-2xs text-doom-faint">
|
|
214
209
|
{workflow.runners === undefined ? 'no runner map declared' : 'the runners every step agrees on'}
|
|
215
210
|
</span>
|
|
216
211
|
</div>
|
|
217
212
|
<div className="flex flex-col gap-1.5">
|
|
218
213
|
<FieldLabel>JOBS</FieldLabel>
|
|
219
|
-
<span className="flex h-7 items-center text-
|
|
214
|
+
<span className="flex h-7 items-center text-xs text-doom-dim">
|
|
220
215
|
{workflow.jobs.map((job) => job.name).join(' → ') || 'none declared'}
|
|
221
216
|
</span>
|
|
222
|
-
<span className="text-
|
|
217
|
+
<span className="text-2xs text-doom-faint">in the order the file declares them</span>
|
|
223
218
|
</div>
|
|
224
219
|
<div className="flex flex-col gap-1.5">
|
|
225
220
|
<FieldLabel>CWD</FieldLabel>
|
|
226
|
-
<span className="flex h-7 items-center text-
|
|
227
|
-
<span className="text-
|
|
221
|
+
<span className="flex h-7 items-center text-xs text-doom-dim">{cwd}</span>
|
|
222
|
+
<span className="text-2xs text-doom-faint">the session's directory</span>
|
|
228
223
|
</div>
|
|
229
224
|
</div>
|
|
230
225
|
<div className="flex flex-col gap-1 rounded-md border border-doom-border bg-doom-deep px-3 py-2">
|
|
231
226
|
<FieldLabel>SENT TO THE SESSION</FieldLabel>
|
|
232
|
-
<pre data-testid="launch-line" className="whitespace-pre-wrap break-words text-
|
|
227
|
+
<pre data-testid="launch-line" className="whitespace-pre-wrap break-words text-xs text-doom-green">
|
|
233
228
|
{line}
|
|
234
229
|
</pre>
|
|
235
230
|
</div>
|
|
236
231
|
{problems.length === 0 ? null : (
|
|
237
|
-
<p data-testid="launch-problems" className="text-
|
|
232
|
+
<p data-testid="launch-problems" className="text-xs text-doom-yellow">
|
|
238
233
|
{problems.join(' ')}
|
|
239
234
|
</p>
|
|
240
235
|
)}
|
|
241
236
|
<DialogFooter className="flex-wrap sm:flex-nowrap">
|
|
242
|
-
<span className="w-full text-
|
|
237
|
+
<span className="w-full text-2xs text-doom-faint sm:w-auto">
|
|
243
238
|
the run appears on the board as soon as it starts
|
|
244
239
|
</span>
|
|
245
240
|
<span className="min-w-0 flex-1" />
|
|
@@ -160,15 +160,13 @@ export function StepTerminalPanel({ sessionId, target }: WebPluginSlotProps & {
|
|
|
160
160
|
return (
|
|
161
161
|
<div data-testid="step-terminal-panel" className="flex min-h-0 flex-1 flex-col px-[26px] py-[18px]">
|
|
162
162
|
<div data-testid="step-terminal-head" className="flex items-center gap-2.5 pb-3">
|
|
163
|
-
<span className="shrink-0 truncate text-
|
|
164
|
-
|
|
165
|
-
</span>
|
|
166
|
-
<span className="text-[10px] text-doom-faint">›</span>
|
|
167
|
-
<span className="shrink-0 truncate text-[11px] font-bold text-doom-blue">{target.job}</span>
|
|
163
|
+
<span className="shrink-0 truncate text-sm font-bold text-doom-hi">{run?.displayName ?? target.runKey}</span>
|
|
164
|
+
<span className="text-xs text-doom-faint">›</span>
|
|
165
|
+
<span className="shrink-0 truncate text-sm font-bold text-doom-blue">{target.job}</span>
|
|
168
166
|
{target.step === undefined ? null : (
|
|
169
167
|
<>
|
|
170
|
-
<span className="text-
|
|
171
|
-
<span className="min-w-0 truncate text-
|
|
168
|
+
<span className="text-xs text-doom-faint">›</span>
|
|
169
|
+
<span className="min-w-0 truncate text-sm text-doom-text">{target.step}</span>
|
|
172
170
|
</>
|
|
173
171
|
)}
|
|
174
172
|
{run === undefined ? null : (
|
|
@@ -204,12 +202,12 @@ export function StepTerminalPanel({ sessionId, target }: WebPluginSlotProps & {
|
|
|
204
202
|
data-testid="step-terminal-screen"
|
|
205
203
|
tabIndex={0}
|
|
206
204
|
onKeyDown={onKeyDown}
|
|
207
|
-
className={`min-h-0 flex-1 overflow-y-auto rounded-md border bg-doom-deep px-4 py-3 font-mono text-
|
|
205
|
+
className={`min-h-0 flex-1 overflow-y-auto rounded-md border bg-doom-deep px-4 py-3 font-mono text-sm leading-tight text-doom-text outline-none ${
|
|
208
206
|
held ? 'border-doom-blue/60' : 'border-doom-border'
|
|
209
207
|
}`}
|
|
210
208
|
>
|
|
211
209
|
{lines.length === 0 ? (
|
|
212
|
-
<span className="text-
|
|
210
|
+
<span className="text-xs text-doom-faint">
|
|
213
211
|
{capabilities?.readable === false
|
|
214
212
|
? (capabilities.reason ?? 'This run has no terminal to read.')
|
|
215
213
|
: 'waiting for the run to paint…'}
|
|
@@ -220,7 +218,7 @@ export function StepTerminalPanel({ sessionId, target }: WebPluginSlotProps & {
|
|
|
220
218
|
{held && !ended ? <StreamCursor className="mt-0.5 h-[12px] w-1.5" /> : null}
|
|
221
219
|
</div>
|
|
222
220
|
<div className="flex items-center gap-3 pt-2.5">
|
|
223
|
-
<span data-testid="step-terminal-hint" className="text-
|
|
221
|
+
<span data-testid="step-terminal-hint" className="text-2xs text-doom-faint">
|
|
224
222
|
{held
|
|
225
223
|
? 'keys go to the run · ctrl-c and ctrl-d pass through · esc releases the keyboard'
|
|
226
224
|
: capabilities?.writable === false
|
|
@@ -229,12 +227,12 @@ export function StepTerminalPanel({ sessionId, target }: WebPluginSlotProps & {
|
|
|
229
227
|
</span>
|
|
230
228
|
<span className="min-w-0 flex-1" />
|
|
231
229
|
{ended ? (
|
|
232
|
-
<span data-testid="step-terminal-ended" className="text-
|
|
230
|
+
<span data-testid="step-terminal-ended" className="text-2xs text-doom-faint">
|
|
233
231
|
the run has settled; this is its last screen
|
|
234
232
|
</span>
|
|
235
233
|
) : null}
|
|
236
234
|
{notice === undefined ? null : (
|
|
237
|
-
<span data-testid="step-terminal-notice" className="text-
|
|
235
|
+
<span data-testid="step-terminal-notice" className="text-2xs text-doom-yellow">
|
|
238
236
|
{notice}
|
|
239
237
|
</span>
|
|
240
238
|
)}
|
|
@@ -27,7 +27,7 @@ function Detail({ workflow }: { workflow: WorkflowCatalogEntryView }) {
|
|
|
27
27
|
['file', workflow.relativePath],
|
|
28
28
|
];
|
|
29
29
|
return (
|
|
30
|
-
<dl data-testid={`catalog-detail-${workflow.name}`} className="flex flex-col gap-1 pt-1 text-
|
|
30
|
+
<dl data-testid={`catalog-detail-${workflow.name}`} className="flex flex-col gap-1 pt-1 text-2xs">
|
|
31
31
|
{rows.map(([label, value]) => (
|
|
32
32
|
<div key={label} className="flex gap-2">
|
|
33
33
|
<dt className="w-16 shrink-0 text-doom-faint">{label}</dt>
|
|
@@ -69,26 +69,26 @@ function WorkflowRow({
|
|
|
69
69
|
}`}
|
|
70
70
|
>
|
|
71
71
|
<div className="flex min-w-0 items-center gap-2">
|
|
72
|
-
<span className="truncate text-
|
|
72
|
+
<span className="truncate text-sm font-bold text-doom-hi">{workflow.name}</span>
|
|
73
73
|
{workflow.tags.map((tag) => (
|
|
74
74
|
<Badge key={tag} size="xs" className="shrink-0">
|
|
75
75
|
{tag}
|
|
76
76
|
</Badge>
|
|
77
77
|
))}
|
|
78
78
|
<span className="min-w-0 flex-1" />
|
|
79
|
-
<span className={`shrink-0 text-
|
|
79
|
+
<span className={`shrink-0 text-2xs ${workflow.error === undefined ? 'text-doom-cyan' : 'text-doom-red'}`}>
|
|
80
80
|
{workflow.error === undefined ? `${jobs} job${jobs === 1 ? '' : 's'}` : 'unreadable'}
|
|
81
81
|
</span>
|
|
82
82
|
</div>
|
|
83
|
-
<span className="truncate text-
|
|
83
|
+
<span className="truncate text-xs text-doom-dim">{workflow.description || workflow.relativePath}</span>
|
|
84
84
|
{workflow.error === undefined ? (
|
|
85
|
-
<span className="truncate text-
|
|
85
|
+
<span className="truncate text-2xs text-doom-faint">
|
|
86
86
|
{workflow.inputs.length} input{workflow.inputs.length === 1 ? '' : 's'} ·{' '}
|
|
87
87
|
{workflow.runners === undefined ? 'any runner' : workflow.runners.join(', ') || 'no runner in common'}
|
|
88
88
|
{workflow.artifacts.length > 0 ? ` · ${workflow.artifacts.length} artifacts` : ''}
|
|
89
89
|
</span>
|
|
90
90
|
) : (
|
|
91
|
-
<span className="truncate text-
|
|
91
|
+
<span className="truncate text-2xs text-doom-red">{workflow.error}</span>
|
|
92
92
|
)}
|
|
93
93
|
{selected ? (
|
|
94
94
|
<div className="flex items-center gap-3 pt-0.5">
|
|
@@ -179,7 +179,7 @@ export function WorkflowCatalogDrawer({
|
|
|
179
179
|
className="flex w-[min(440px,calc(100vw-24px))] shrink-0 flex-col overflow-hidden border-l border-doom-border bg-doom-rail outline-none"
|
|
180
180
|
>
|
|
181
181
|
<div className="flex h-11 shrink-0 items-center gap-2.5 border-b border-doom-border px-4">
|
|
182
|
-
<span className="text-
|
|
182
|
+
<span className="text-base font-bold text-doom-hi">workflow catalog</span>
|
|
183
183
|
<Badge size="xs" data-testid="catalog-count">
|
|
184
184
|
{state.workflows.length} workflow{state.workflows.length === 1 ? '' : 's'}
|
|
185
185
|
</Badge>
|
|
@@ -200,7 +200,7 @@ export function WorkflowCatalogDrawer({
|
|
|
200
200
|
</div>
|
|
201
201
|
<div role="listbox" className="flex min-h-0 flex-1 flex-col overflow-y-auto pb-2">
|
|
202
202
|
{shown.length === 0 ? (
|
|
203
|
-
<p data-testid="catalog-empty" className="px-4 py-3 text-
|
|
203
|
+
<p data-testid="catalog-empty" className="px-4 py-3 text-xs text-doom-faint">
|
|
204
204
|
{state.warning ??
|
|
205
205
|
(state.workflows.length === 0
|
|
206
206
|
? 'no workflows found for this session; add one under automations/workflows'
|
|
@@ -220,8 +220,8 @@ export function WorkflowCatalogDrawer({
|
|
|
220
220
|
))}
|
|
221
221
|
</div>
|
|
222
222
|
<div className="flex h-8 shrink-0 items-center justify-between border-t border-doom-border-soft bg-doom-deep px-4">
|
|
223
|
-
<span className="text-
|
|
224
|
-
<span className="text-
|
|
223
|
+
<span className="text-2xs text-doom-faint">↑↓ choose · enter launch · i inspect · esc close</span>
|
|
224
|
+
<span className="text-2xs text-doom-faint">{state.cwd}</span>
|
|
225
225
|
</div>
|
|
226
226
|
</aside>
|
|
227
227
|
);
|
|
@@ -59,7 +59,7 @@ export function WorkflowsActivitySection({ sessionId, openTransientTab }: WebPlu
|
|
|
59
59
|
if (groups.length === 0) {
|
|
60
60
|
return (
|
|
61
61
|
<div className="flex items-center gap-2 px-1">
|
|
62
|
-
<p data-testid="activity-summary-workflows" className="text-
|
|
62
|
+
<p data-testid="activity-summary-workflows" className="text-xs text-doom-faint">
|
|
63
63
|
idle
|
|
64
64
|
</p>
|
|
65
65
|
<Button
|
|
@@ -91,20 +91,20 @@ export function WorkflowsActivitySection({ sessionId, openTransientTab }: WebPlu
|
|
|
91
91
|
focusRun(sessionId, row.identity);
|
|
92
92
|
openTransientTab(workflowsTab());
|
|
93
93
|
}}
|
|
94
|
-
className="min-w-0 gap-0.5 rounded-
|
|
94
|
+
className="min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel"
|
|
95
95
|
>
|
|
96
96
|
<span className="flex min-w-0 items-center gap-1.5">
|
|
97
97
|
<Dot tone={TONE_DOT[row.tone]} pulse={row.tone === 'running'} />
|
|
98
98
|
<span
|
|
99
|
-
className={`min-w-0 flex-1 truncate text-
|
|
99
|
+
className={`min-w-0 flex-1 truncate text-xs font-bold ${
|
|
100
100
|
row.tone === 'running' || row.tone === 'paused' ? 'text-doom-hi' : 'text-doom-dim'
|
|
101
101
|
}`}
|
|
102
102
|
>
|
|
103
103
|
{row.name}
|
|
104
104
|
</span>
|
|
105
|
-
<span className="shrink-0 text-
|
|
105
|
+
<span className="shrink-0 text-2xs text-doom-faint">{row.elapsed}</span>
|
|
106
106
|
</span>
|
|
107
|
-
<span className={`truncate pl-3 text-
|
|
107
|
+
<span className={`truncate pl-3 text-2xs ${TONE_DETAIL[row.tone]}`}>{row.detail}</span>
|
|
108
108
|
</Button>
|
|
109
109
|
);
|
|
110
110
|
|
|
@@ -121,15 +121,15 @@ export function WorkflowsActivitySection({ sessionId, openTransientTab }: WebPlu
|
|
|
121
121
|
data-open={open}
|
|
122
122
|
aria-expanded={open}
|
|
123
123
|
onClick={() => setFolded((current) => ({ ...current, [group.name]: !open }))}
|
|
124
|
-
className="justify-start gap-1.5 rounded-
|
|
124
|
+
className="justify-start gap-1.5 rounded-md px-1 py-0.5 hover:bg-doom-panel"
|
|
125
125
|
>
|
|
126
126
|
{open ? (
|
|
127
127
|
<ChevronDownIcon className="h-2.5 w-2.5 text-doom-faint" />
|
|
128
128
|
) : (
|
|
129
129
|
<ChevronRightIcon className="h-2.5 w-2.5 text-doom-faint" />
|
|
130
130
|
)}
|
|
131
|
-
<span className={`text-
|
|
132
|
-
<span className="text-
|
|
131
|
+
<span className={`text-xs font-bold ${GROUP_LABEL[group.name]}`}>{group.name}</span>
|
|
132
|
+
<span className="text-2xs text-doom-faint">{group.rows.length}</span>
|
|
133
133
|
</Button>
|
|
134
134
|
{open ? (
|
|
135
135
|
<div className="flex flex-col gap-0.5 pl-2">
|
|
@@ -138,7 +138,7 @@ export function WorkflowsActivitySection({ sessionId, openTransientTab }: WebPlu
|
|
|
138
138
|
))}
|
|
139
139
|
</div>
|
|
140
140
|
) : (
|
|
141
|
-
<span className="truncate pl-5 text-
|
|
141
|
+
<span className="truncate pl-5 text-2xs text-doom-faint">
|
|
142
142
|
{group.rows
|
|
143
143
|
.slice(0, 2)
|
|
144
144
|
.map((row) => row.name)
|
|
@@ -154,7 +154,7 @@ function WorkflowPicker({
|
|
|
154
154
|
>
|
|
155
155
|
<SearchIcon className="h-3 w-3 shrink-0 text-doom-faint" />
|
|
156
156
|
<span className="min-w-0 flex-1 truncate text-left font-bold text-doom-hi">{selected.displayName}</span>
|
|
157
|
-
<span className="shrink-0 text-
|
|
157
|
+
<span className="shrink-0 text-2xs font-normal text-doom-faint">{runs.length} workflows</span>
|
|
158
158
|
<ChevronDownIcon className="h-3 w-3 shrink-0 text-doom-dim" />
|
|
159
159
|
</Button>
|
|
160
160
|
</PopoverTrigger>
|
|
@@ -175,13 +175,11 @@ function WorkflowPicker({
|
|
|
175
175
|
</PopoverHeader>
|
|
176
176
|
<div className="grid max-h-[360px] grid-cols-1 gap-2 overflow-y-auto p-2 sm:grid-cols-2">
|
|
177
177
|
{sections.length === 0 ? (
|
|
178
|
-
<span className="px-2 py-5 text-center text-
|
|
179
|
-
no matching workflows
|
|
180
|
-
</span>
|
|
178
|
+
<span className="px-2 py-5 text-center text-xs text-doom-faint sm:col-span-2">no matching workflows</span>
|
|
181
179
|
) : (
|
|
182
180
|
sections.map((section) => (
|
|
183
181
|
<div key={section.label} className="flex min-w-0 flex-col gap-0.5">
|
|
184
|
-
<span className="px-2 py-1 text-
|
|
182
|
+
<span className="px-2 py-1 text-2xs font-bold uppercase tracking-wider text-doom-faint">
|
|
185
183
|
{section.label} · {section.runs.length}
|
|
186
184
|
</span>
|
|
187
185
|
{section.runs.map((run) => {
|
|
@@ -204,12 +202,12 @@ function WorkflowPicker({
|
|
|
204
202
|
>
|
|
205
203
|
<span className="flex min-w-0 items-center gap-2">
|
|
206
204
|
<Dot tone={runDot(run)} pulse={run.stage === 'running'} />
|
|
207
|
-
<span className={cn('min-w-0 flex-1 truncate text-
|
|
205
|
+
<span className={cn('min-w-0 flex-1 truncate text-xs', active && 'font-bold text-doom-hi')}>
|
|
208
206
|
{run.displayName}
|
|
209
207
|
</span>
|
|
210
|
-
<span className="shrink-0 text-
|
|
208
|
+
<span className="shrink-0 text-2xs text-doom-faint">{runState(run)}</span>
|
|
211
209
|
</span>
|
|
212
|
-
<span className="truncate pl-3.5 text-
|
|
210
|
+
<span className="truncate pl-3.5 text-2xs text-doom-dim">
|
|
213
211
|
{[run.position?.job, run.position?.step].filter(Boolean).join(' · ') || 'settled'}
|
|
214
212
|
</span>
|
|
215
213
|
</button>
|
|
@@ -244,10 +242,10 @@ function SelectedAttention({ run }: { run: WorkflowRunView }) {
|
|
|
244
242
|
<StatusBadge size="xs" tone={attention.kind === 'error' ? 'error' : 'running'}>
|
|
245
243
|
{attention.kind === 'error' ? 'ERROR' : 'PAUSED'}
|
|
246
244
|
</StatusBadge>
|
|
247
|
-
<span data-testid={`needs-card-${run.runKey}`} className="min-w-0 flex-1 truncate text-
|
|
245
|
+
<span data-testid={`needs-card-${run.runKey}`} className="min-w-0 flex-1 truncate text-xs text-doom-text">
|
|
248
246
|
{run.displayName}: {attention.text}
|
|
249
247
|
</span>
|
|
250
|
-
<span className="shrink-0 text-
|
|
248
|
+
<span className="shrink-0 text-2xs text-doom-faint">
|
|
251
249
|
{attention.kind === 'error' ? 'recover from the owning session' : 'resume from the owning session'}
|
|
252
250
|
</span>
|
|
253
251
|
</div>
|
|
@@ -275,11 +273,11 @@ function JobRow({
|
|
|
275
273
|
onClick={onSelect}
|
|
276
274
|
className={cn('gap-2 rounded px-2 py-1.5', !selected && 'hover:bg-doom-deep')}
|
|
277
275
|
>
|
|
278
|
-
<span className={`w-3 shrink-0 text-
|
|
279
|
-
<OptionLabel density="compact" className={cn('text-
|
|
276
|
+
<span className={`w-3 shrink-0 text-xs ${icon.className}`}>{icon.glyph}</span>
|
|
277
|
+
<OptionLabel density="compact" className={cn('text-xs', selected ? 'text-doom-hi' : 'text-doom-text')}>
|
|
280
278
|
{job.name}
|
|
281
279
|
</OptionLabel>
|
|
282
|
-
<span className="shrink-0 text-
|
|
280
|
+
<span className="shrink-0 text-2xs text-doom-faint">{spanDuration(job.startedAt, job.endedAt, now) ?? ''}</span>
|
|
283
281
|
</OptionRow>
|
|
284
282
|
);
|
|
285
283
|
}
|
|
@@ -309,23 +307,21 @@ function StepRow({
|
|
|
309
307
|
)}
|
|
310
308
|
>
|
|
311
309
|
<span className="flex items-center gap-2">
|
|
312
|
-
<span className={`w-3 shrink-0 text-
|
|
310
|
+
<span className={`w-3 shrink-0 text-xs ${icon.className}`}>{icon.glyph}</span>
|
|
313
311
|
<span
|
|
314
312
|
className={cn(
|
|
315
|
-
'min-w-0 flex-1 truncate text-
|
|
313
|
+
'min-w-0 flex-1 truncate text-2xs',
|
|
316
314
|
step.status === 'failed' ? 'text-doom-red' : 'text-doom-text',
|
|
317
315
|
)}
|
|
318
316
|
>
|
|
319
317
|
{step.name}
|
|
320
318
|
</span>
|
|
321
|
-
<span className="shrink-0 text-
|
|
319
|
+
<span className="shrink-0 text-2xs text-doom-faint">
|
|
322
320
|
{spanDuration(step.startedAt, step.endedAt, now) ?? ''}
|
|
323
321
|
</span>
|
|
324
322
|
</span>
|
|
325
323
|
{step.reason === undefined ? null : (
|
|
326
|
-
<span
|
|
327
|
-
className={cn('truncate pl-5 text-[8px]', step.status === 'failed' ? 'text-doom-red' : 'text-doom-faint')}
|
|
328
|
-
>
|
|
324
|
+
<span className={cn('truncate pl-5 text-2xs', step.status === 'failed' ? 'text-doom-red' : 'text-doom-faint')}>
|
|
329
325
|
{step.reason}
|
|
330
326
|
</span>
|
|
331
327
|
)}
|
|
@@ -385,12 +381,12 @@ function InlineStepOutput({
|
|
|
385
381
|
<div data-testid="workflow-inline-output" className="flex min-h-0 flex-1 flex-col bg-doom-deep">
|
|
386
382
|
<div className="flex h-9 shrink-0 items-center gap-2 border-b border-doom-border-soft px-3">
|
|
387
383
|
<Dot tone={ended ? 'neutral' : 'blue'} pulse={!ended && run.stage === 'running'} />
|
|
388
|
-
<span className={cn('text-
|
|
384
|
+
<span className={cn('text-2xs font-bold', ended ? 'text-doom-faint' : 'text-doom-blue')}>
|
|
389
385
|
{ended ? 'FINAL OUTPUT' : 'LIVE OUTPUT'}
|
|
390
386
|
</span>
|
|
391
|
-
<span className="min-w-0 truncate text-
|
|
387
|
+
<span className="min-w-0 truncate text-xs font-bold text-doom-hi">{step?.name ?? job.name}</span>
|
|
392
388
|
<span className="min-w-0 flex-1" />
|
|
393
|
-
<span className="text-
|
|
389
|
+
<span className="text-2xs text-doom-faint">{ended ? 'settled' : 'following · 500ms'}</span>
|
|
394
390
|
<Button variant="outline" size="xs" data-testid="workflow-open-terminal" onClick={onOpenTerminal}>
|
|
395
391
|
open terminal
|
|
396
392
|
</Button>
|
|
@@ -398,10 +394,10 @@ function InlineStepOutput({
|
|
|
398
394
|
<div
|
|
399
395
|
ref={screenRef}
|
|
400
396
|
data-testid="workflow-inline-screen"
|
|
401
|
-
className="min-h-0 flex-1 overflow-y-auto px-4 py-3 font-mono text-
|
|
397
|
+
className="min-h-0 flex-1 overflow-y-auto px-4 py-3 font-mono text-sm leading-tight text-doom-text"
|
|
402
398
|
>
|
|
403
399
|
{lines.length === 0 ? (
|
|
404
|
-
<span className="text-
|
|
400
|
+
<span className="text-xs text-doom-faint">
|
|
405
401
|
{capabilities?.readable === false
|
|
406
402
|
? (capabilities.reason ?? 'This run has no terminal to read.')
|
|
407
403
|
: 'waiting for the run to paint…'}
|
|
@@ -411,7 +407,7 @@ function InlineStepOutput({
|
|
|
411
407
|
)}
|
|
412
408
|
{!ended && run.stage === 'running' ? <StreamCursor className="mt-0.5 h-[12px] w-1.5" /> : null}
|
|
413
409
|
</div>
|
|
414
|
-
<div className="flex h-7 shrink-0 items-center gap-2 border-t border-doom-border-soft px-3 text-
|
|
410
|
+
<div className="flex h-7 shrink-0 items-center gap-2 border-t border-doom-border-soft px-3 text-2xs text-doom-faint">
|
|
415
411
|
<span>output follows automatically · select another step to inspect its current screen</span>
|
|
416
412
|
<span className="min-w-0 flex-1" />
|
|
417
413
|
<span>last 48 lines</span>
|
|
@@ -459,11 +455,11 @@ function DeleteWorkflowDialog({
|
|
|
459
455
|
<DialogTitle>Delete {run.displayName}?</DialogTitle>
|
|
460
456
|
</DialogHeader>
|
|
461
457
|
<DialogBody>
|
|
462
|
-
<p className="text-
|
|
458
|
+
<p className="text-sm leading-relaxed text-doom-dim">
|
|
463
459
|
This permanently removes the workflow run, including its logs and artifacts. This action cannot be undone.
|
|
464
460
|
</p>
|
|
465
461
|
{error === undefined ? null : (
|
|
466
|
-
<p data-testid="delete-workflow-error" className="text-
|
|
462
|
+
<p data-testid="delete-workflow-error" className="text-xs text-doom-red">
|
|
467
463
|
{error}
|
|
468
464
|
</p>
|
|
469
465
|
)}
|
|
@@ -569,13 +565,13 @@ export function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }
|
|
|
569
565
|
<StatusBadge tone={run.stage === 'error' ? 'error' : run.stage === 'running' ? 'info' : 'ok'}>
|
|
570
566
|
{runState(run)}
|
|
571
567
|
</StatusBadge>
|
|
572
|
-
<span className="truncate text-
|
|
568
|
+
<span className="truncate text-2xs text-doom-dim">
|
|
573
569
|
{[run.position?.job, run.position?.step].filter(Boolean).join(' · ') ||
|
|
574
570
|
run.workflowName ||
|
|
575
571
|
run.displayName}
|
|
576
572
|
</span>
|
|
577
573
|
<span className="min-w-0 flex-1" />
|
|
578
|
-
<span data-testid="workflows-tally" className="text-
|
|
574
|
+
<span data-testid="workflows-tally" className="text-2xs text-doom-faint">
|
|
579
575
|
{runs.filter((candidate) => candidate.stage === 'running').length} running · {runs.length} total
|
|
580
576
|
</span>
|
|
581
577
|
{run.stage === 'running' ? null : (
|
|
@@ -606,9 +602,9 @@ export function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }
|
|
|
606
602
|
className="flex max-h-44 w-full shrink-0 flex-col overflow-y-auto rounded-md border border-doom-border bg-doom-panel p-1.5 sm:max-h-none sm:w-[214px]"
|
|
607
603
|
>
|
|
608
604
|
<div className="flex items-center px-2 pb-1 pt-1">
|
|
609
|
-
<span className="text-
|
|
605
|
+
<span className="text-2xs font-bold tracking-wider text-doom-faint">JOBS</span>
|
|
610
606
|
<span className="min-w-0 flex-1" />
|
|
611
|
-
<span className="text-
|
|
607
|
+
<span className="text-2xs text-doom-faint">
|
|
612
608
|
{jobs.filter((candidate) => candidate.status === 'completed').length}/{jobs.length}
|
|
613
609
|
</span>
|
|
614
610
|
</div>
|
|
@@ -635,11 +631,11 @@ export function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }
|
|
|
635
631
|
<div className="flex items-center px-2 pb-1">
|
|
636
632
|
<span
|
|
637
633
|
data-testid="job-pane-name"
|
|
638
|
-
className="min-w-0 flex-1 truncate text-
|
|
634
|
+
className="min-w-0 flex-1 truncate text-2xs font-bold text-doom-hi"
|
|
639
635
|
>
|
|
640
636
|
{job.name}
|
|
641
637
|
</span>
|
|
642
|
-
<span className="text-
|
|
638
|
+
<span className="text-2xs text-doom-faint">
|
|
643
639
|
{spanDuration(job.startedAt, job.endedAt, now) ?? ''}
|
|
644
640
|
</span>
|
|
645
641
|
</div>
|
|
@@ -662,7 +658,7 @@ export function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }
|
|
|
662
658
|
</div>
|
|
663
659
|
<DetailPane>
|
|
664
660
|
<div className="flex h-9 shrink-0 items-center gap-2 border-b border-doom-border-soft bg-doom-panel px-3">
|
|
665
|
-
<span className="min-w-0 truncate text-
|
|
661
|
+
<span className="min-w-0 truncate text-xs font-bold text-doom-hi">
|
|
666
662
|
{job?.name ?? run.displayName}
|
|
667
663
|
</span>
|
|
668
664
|
{job === undefined ? null : (
|