@agimon-ai/doompi-task 0.0.1-alpha.11 → 0.0.1-alpha.12
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/commands/task/taskTool.cjs +1 -1
- package/dist/commands/task/taskTool.cjs.map +1 -1
- package/dist/commands/task/taskTool.mjs +1 -1
- package/dist/commands/task/taskTool.mjs.map +1 -1
- package/dist/tui/format.cjs +3 -3
- package/dist/tui/format.cjs.map +1 -1
- package/dist/tui/format.d.cts +2 -2
- package/dist/tui/format.d.cts.map +1 -1
- package/dist/tui/format.d.mts +2 -2
- package/dist/tui/format.d.mts.map +1 -1
- package/dist/tui/format.mjs +3 -3
- package/dist/tui/format.mjs.map +1 -1
- package/dist/tui/taskSpace.cjs +3 -3
- package/dist/tui/taskSpace.cjs.map +1 -1
- package/dist/tui/taskSpace.d.cts.map +1 -1
- package/dist/tui/taskSpace.d.mts.map +1 -1
- package/dist/tui/taskSpace.mjs +3 -3
- package/dist/tui/taskSpace.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
const e=require("../../adapters/telemetry/logSinkTelemetry.cjs"),t=require("../../schemas/task.cjs"),n=require("../../services/store/reducer.cjs"),r=require("../../tui/format.cjs"),i=require("./promptGuidelines.cjs"),a=require("./responseEnvelope.cjs"),o=`Manage a shared task list to track multi-step progress, and delegate tasks to subagents`,s=new Set([`upsert`,`list`,`get`,`delete`,`clear`]);var c=class extends Error{};function l(e,t){return new c([`Task ${e} failed: ${t}`,``,`Options:`,`- List the task board and inspect the current ids, states, and blockers.`,`- Correct the arguments or state conflict, then retry once.`,`- Ask the user before cancelling work or changing dependencies to recover.`].join(`
|
|
2
|
-
`))}function u(e,n){let r=Object.keys(n).filter(r=>n[r]!==void 0&&!t.taskActionAcceptsField(e,r));if(r.length===0)return;let i=e===`upsert`?` ${t.MSG_UPSERT_FIELD_MISPLACED}`:``;throw l(e,`${e} does not accept ${r.join(`, `)}.${i}`)}async function d(e,t,r){let{document:i,value:o}=await e.mutate(e=>{let i=n.applyTaskMutation(e,t,r);return{...n.isCommittingOp(i.op)?{document:i.document}:{},value:i}});if(o.op.kind===`error`)throw l(t,o.op.message);if(o.op.kind===`upsert`&&o.op.applied===0)throw l(t,a.formatUpsertFailureText(o.op));return a.buildToolResult(t,r,i,o.op)}function f(n,f){let{store:p,delegation:m}=f;n.registerTool({name:t.TOOL_NAME,label:t.TOOL_LABEL,description:`Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.`,promptSnippet:o,promptGuidelines:i.DEFAULT_PROMPT_GUIDELINES,parameters:t.TaskParamsSchema,async execute(t,n,r,i,o){let h=n.action,g=n;try{if(u(h,g),s.has(h)){let e=await d(p,h,g);return f.onChange?.(),e}let e;if(h===`assign`?(i?.(a.buildTextResult(h,g,p.snapshot,`Delegating task #${g.id??`unknown`}...`)),e=await m.assign(g.id??NaN,{agent:g.agent,inlineAgent:g.inlineAgent,instructions:g.instructions,relevantFiles:g.relevantFiles,priorFindings:g.priorFindings,model:g.model,context:g.context,signal:r})):(i?.(a.buildTextResult(h,g,p.snapshot,`Requesting cancellation for task #${g.id??`unknown`}...`)),e=await m.cancel(g.id??NaN)),f.onChange?.(),!e.ok)throw l(h,e.message);return a.buildTextResult(h,g,p.snapshot,e.message,void 0)}catch(t){throw f.report?.error(e.TASK_EVENT.toolFailed,t,{"tool.action":h,...g.id===void 0?{}:{"task.id":g.id}}),t instanceof c?t:l(h,t instanceof Error?t.message:String(t))}},renderCall(e,t,n){return r.renderTaskCall(e,t,p.snapshot.tasks)},renderResult(e,t,n,i){return r.renderTaskResult(e,t,n)}})}exports.DEFAULT_PROMPT_GUIDELINES=i.DEFAULT_PROMPT_GUIDELINES,exports.DEFAULT_PROMPT_SNIPPET=o,exports.registerTaskTool=f;
|
|
2
|
+
`))}function u(e,n){let r=Object.keys(n).filter(r=>n[r]!==void 0&&!t.taskActionAcceptsField(e,r));if(r.length===0)return;let i=e===`upsert`?` ${t.MSG_UPSERT_FIELD_MISPLACED}`:``;throw l(e,`${e} does not accept ${r.join(`, `)}.${i}`)}async function d(e,t,r){let{document:i,value:o}=await e.mutate(e=>{let i=n.applyTaskMutation(e,t,r);return{...n.isCommittingOp(i.op)?{document:i.document}:{},value:i}});if(o.op.kind===`error`)throw l(t,o.op.message);if(o.op.kind===`upsert`&&o.op.applied===0)throw l(t,a.formatUpsertFailureText(o.op));return a.buildToolResult(t,r,i,o.op)}function f(n,f){let{store:p,delegation:m}=f;n.registerTool({name:t.TOOL_NAME,label:t.TOOL_LABEL,description:`Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.`,promptSnippet:o,promptGuidelines:i.DEFAULT_PROMPT_GUIDELINES,parameters:t.TaskParamsSchema,renderShell:`self`,async execute(t,n,r,i,o){let h=n.action,g=n;try{if(u(h,g),s.has(h)){let e=await d(p,h,g);return f.onChange?.(),e}let e;if(h===`assign`?(i?.(a.buildTextResult(h,g,p.snapshot,`Delegating task #${g.id??`unknown`}...`)),e=await m.assign(g.id??NaN,{agent:g.agent,inlineAgent:g.inlineAgent,instructions:g.instructions,relevantFiles:g.relevantFiles,priorFindings:g.priorFindings,model:g.model,context:g.context,signal:r})):(i?.(a.buildTextResult(h,g,p.snapshot,`Requesting cancellation for task #${g.id??`unknown`}...`)),e=await m.cancel(g.id??NaN)),f.onChange?.(),!e.ok)throw l(h,e.message);return a.buildTextResult(h,g,p.snapshot,e.message,void 0)}catch(t){throw f.report?.error(e.TASK_EVENT.toolFailed,t,{"tool.action":h,...g.id===void 0?{}:{"task.id":g.id}}),t instanceof c?t:l(h,t instanceof Error?t.message:String(t))}},renderCall(e,t,n){return r.renderTaskCall(e,t,p.snapshot.tasks)},renderResult(e,t,n,i){return r.renderTaskResult(e,t,n)}})}exports.DEFAULT_PROMPT_GUIDELINES=i.DEFAULT_PROMPT_GUIDELINES,exports.DEFAULT_PROMPT_SNIPPET=o,exports.registerTaskTool=f;
|
|
3
3
|
//# sourceMappingURL=taskTool.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskTool.cjs","names":["taskActionAcceptsField","MSG_UPSERT_FIELD_MISPLACED","applyTaskMutation","isCommittingOp","formatUpsertFailureText","buildToolResult","TOOL_NAME","TOOL_LABEL","DEFAULT_PROMPT_GUIDELINES","TaskParamsSchema","buildTextResult","TASK_EVENT","renderTaskCall","renderTaskResult"],"sources":["../../../src/commands/task/taskTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { TASK_EVENT, type TaskFailureReporter } from '../../adapters/telemetry/logSinkTelemetry.ts';\nimport {\n MSG_UPSERT_FIELD_MISPLACED,\n TaskParamsSchema,\n TOOL_LABEL,\n TOOL_NAME,\n taskActionAcceptsField,\n} from '../../schemas/task.ts';\nimport type { DelegationManager, DelegationOutcome } from '../../services/delegation/manager.ts';\nimport { applyTaskMutation, isCommittingOp, type ReducerAction } from '../../services/store/reducer.ts';\nimport type { TaskStore } from '../../services/store/taskStore.ts';\nimport type { TaskAction, TaskMutationParams } from '../../services/store/types.ts';\nimport { renderTaskCall, renderTaskResult } from '../../tui/format.ts';\nimport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\nimport { buildTextResult, buildToolResult, formatUpsertFailureText, type ToolResult } from './responseEnvelope.ts';\n\nexport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\n\nexport const DEFAULT_PROMPT_SNIPPET =\n 'Manage a shared task list to track multi-step progress, and delegate tasks to subagents';\n\nexport interface TaskToolDependencies {\n store: TaskStore;\n delegation: DelegationManager;\n onChange?: () => void;\n report?: TaskFailureReporter;\n}\n\nconst ACTION_ATTRIBUTE = 'tool.action';\nconst TASK_ID_ATTRIBUTE = 'task.id';\n\nconst REDUCER_ACTIONS = new Set<TaskAction>(['upsert', 'list', 'get', 'delete', 'clear']);\n\nclass TaskToolExecutionError extends Error {}\n\nfunction actionableTaskError(action: TaskAction, message: string): TaskToolExecutionError {\n return new TaskToolExecutionError(\n [\n `Task ${action} failed: ${message}`,\n '',\n 'Options:',\n '- List the task board and inspect the current ids, states, and blockers.',\n '- Correct the arguments or state conflict, then retry once.',\n '- Ask the user before cancelling work or changing dependencies to recover.',\n ].join('\\n'),\n );\n}\n\n/**\n * Reject a field that belongs to a different action.\n *\n * The params schema is flat and shared across every action, so this is the only\n * place `{\"action\":\"upsert\",\"id\":3}` can be caught — and it must be, because\n * the entry it carries has no id and would silently create a duplicate task.\n */\nfunction rejectStrayFields(action: TaskAction, params: TaskMutationParams): void {\n const stray = Object.keys(params).filter((key) => params[key] !== undefined && !taskActionAcceptsField(action, key));\n if (stray.length === 0) return;\n const hint = action === 'upsert' ? ` ${MSG_UPSERT_FIELD_MISPLACED}` : '';\n throw actionableTaskError(action, `${action} does not accept ${stray.join(', ')}.${hint}`);\n}\n\n/** Run an action through the reducer, persisting only when it produced a change. */\nasync function executeReducerAction(\n store: TaskStore,\n action: ReducerAction,\n params: TaskMutationParams,\n): Promise<ToolResult> {\n const { document, value } = await store.mutate((current) => {\n const result = applyTaskMutation(current, action, params);\n return {\n ...(isCommittingOp(result.op) ? { document: result.document } : {}),\n value: result,\n };\n });\n if (value.op.kind === 'error') throw actionableTaskError(action, value.op.message);\n // An upsert that applied nothing is a failed call: it wrote nothing and left\n // `rev` alone, so it must not read to the model as a success. A batch where\n // some entries landed returns normally — that is what partial apply means.\n if (value.op.kind === 'upsert' && value.op.applied === 0) {\n throw actionableTaskError(action, formatUpsertFailureText(value.op));\n }\n // `document` is the committed document, so `details.rev` matches what landed\n // on disk; `value.document` is the pre-write copy and lags by one.\n return buildToolResult(action, params, document, value.op);\n}\n\nexport function registerTaskTool(pi: ExtensionAPI, dependencies: TaskToolDependencies): void {\n const { store, delegation } = dependencies;\n\n pi.registerTool({\n name: TOOL_NAME,\n label: TOOL_LABEL,\n description:\n 'Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.',\n promptSnippet: DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: DEFAULT_PROMPT_GUIDELINES,\n parameters: TaskParamsSchema,\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx) {\n const action = params.action as TaskAction;\n const mutationParams = params as TaskMutationParams;\n\n try {\n rejectStrayFields(action, mutationParams);\n if (REDUCER_ACTIONS.has(action)) {\n const result = await executeReducerAction(store, action as ReducerAction, mutationParams);\n dependencies.onChange?.();\n return result;\n }\n\n let outcome: DelegationOutcome;\n if (action === 'assign') {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Delegating task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.assign(mutationParams.id ?? Number.NaN, {\n agent: mutationParams.agent,\n inlineAgent: mutationParams.inlineAgent,\n instructions: mutationParams.instructions,\n relevantFiles: mutationParams.relevantFiles,\n priorFindings: mutationParams.priorFindings,\n model: mutationParams.model,\n context: mutationParams.context,\n signal,\n });\n } else {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Requesting cancellation for task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.cancel(mutationParams.id ?? Number.NaN);\n }\n\n dependencies.onChange?.();\n if (!outcome.ok) throw actionableTaskError(action, outcome.message);\n return buildTextResult(action, mutationParams, store.snapshot, outcome.message, undefined);\n } catch (error) {\n // Rethrown so pi still renders the failure to the model; recorded\n // because this is where a store fault becomes visible to the user.\n dependencies.report?.error(TASK_EVENT.toolFailed, error, {\n [ACTION_ATTRIBUTE]: action,\n ...(mutationParams.id === undefined ? {} : { [TASK_ID_ATTRIBUTE]: mutationParams.id }),\n });\n if (error instanceof TaskToolExecutionError) throw error;\n throw actionableTaskError(action, error instanceof Error ? error.message : String(error));\n }\n },\n\n // Render hooks receive no session identity, so they read the store snapshot\n // that the executing session last loaded. That is the foreground session's\n // own view, which is exactly what its transcript should show.\n renderCall(args, theme, _context) {\n return renderTaskCall(args as never, theme, store.snapshot.tasks);\n },\n\n renderResult(result, options, theme, _context) {\n return renderTaskResult(result, options, theme);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"taskTool.cjs","names":["taskActionAcceptsField","MSG_UPSERT_FIELD_MISPLACED","applyTaskMutation","isCommittingOp","formatUpsertFailureText","buildToolResult","TOOL_NAME","TOOL_LABEL","DEFAULT_PROMPT_GUIDELINES","TaskParamsSchema","buildTextResult","TASK_EVENT","renderTaskCall","renderTaskResult"],"sources":["../../../src/commands/task/taskTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { TASK_EVENT, type TaskFailureReporter } from '../../adapters/telemetry/logSinkTelemetry.ts';\nimport {\n MSG_UPSERT_FIELD_MISPLACED,\n TaskParamsSchema,\n TOOL_LABEL,\n TOOL_NAME,\n taskActionAcceptsField,\n} from '../../schemas/task.ts';\nimport type { DelegationManager, DelegationOutcome } from '../../services/delegation/manager.ts';\nimport { applyTaskMutation, isCommittingOp, type ReducerAction } from '../../services/store/reducer.ts';\nimport type { TaskStore } from '../../services/store/taskStore.ts';\nimport type { TaskAction, TaskMutationParams } from '../../services/store/types.ts';\nimport { renderTaskCall, renderTaskResult } from '../../tui/format.ts';\nimport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\nimport { buildTextResult, buildToolResult, formatUpsertFailureText, type ToolResult } from './responseEnvelope.ts';\n\nexport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\n\nexport const DEFAULT_PROMPT_SNIPPET =\n 'Manage a shared task list to track multi-step progress, and delegate tasks to subagents';\n\nexport interface TaskToolDependencies {\n store: TaskStore;\n delegation: DelegationManager;\n onChange?: () => void;\n report?: TaskFailureReporter;\n}\n\nconst ACTION_ATTRIBUTE = 'tool.action';\nconst TASK_ID_ATTRIBUTE = 'task.id';\n\nconst REDUCER_ACTIONS = new Set<TaskAction>(['upsert', 'list', 'get', 'delete', 'clear']);\n\nclass TaskToolExecutionError extends Error {}\n\nfunction actionableTaskError(action: TaskAction, message: string): TaskToolExecutionError {\n return new TaskToolExecutionError(\n [\n `Task ${action} failed: ${message}`,\n '',\n 'Options:',\n '- List the task board and inspect the current ids, states, and blockers.',\n '- Correct the arguments or state conflict, then retry once.',\n '- Ask the user before cancelling work or changing dependencies to recover.',\n ].join('\\n'),\n );\n}\n\n/**\n * Reject a field that belongs to a different action.\n *\n * The params schema is flat and shared across every action, so this is the only\n * place `{\"action\":\"upsert\",\"id\":3}` can be caught — and it must be, because\n * the entry it carries has no id and would silently create a duplicate task.\n */\nfunction rejectStrayFields(action: TaskAction, params: TaskMutationParams): void {\n const stray = Object.keys(params).filter((key) => params[key] !== undefined && !taskActionAcceptsField(action, key));\n if (stray.length === 0) return;\n const hint = action === 'upsert' ? ` ${MSG_UPSERT_FIELD_MISPLACED}` : '';\n throw actionableTaskError(action, `${action} does not accept ${stray.join(', ')}.${hint}`);\n}\n\n/** Run an action through the reducer, persisting only when it produced a change. */\nasync function executeReducerAction(\n store: TaskStore,\n action: ReducerAction,\n params: TaskMutationParams,\n): Promise<ToolResult> {\n const { document, value } = await store.mutate((current) => {\n const result = applyTaskMutation(current, action, params);\n return {\n ...(isCommittingOp(result.op) ? { document: result.document } : {}),\n value: result,\n };\n });\n if (value.op.kind === 'error') throw actionableTaskError(action, value.op.message);\n // An upsert that applied nothing is a failed call: it wrote nothing and left\n // `rev` alone, so it must not read to the model as a success. A batch where\n // some entries landed returns normally — that is what partial apply means.\n if (value.op.kind === 'upsert' && value.op.applied === 0) {\n throw actionableTaskError(action, formatUpsertFailureText(value.op));\n }\n // `document` is the committed document, so `details.rev` matches what landed\n // on disk; `value.document` is the pre-write copy and lags by one.\n return buildToolResult(action, params, document, value.op);\n}\n\nexport function registerTaskTool(pi: ExtensionAPI, dependencies: TaskToolDependencies): void {\n const { store, delegation } = dependencies;\n\n pi.registerTool({\n name: TOOL_NAME,\n label: TOOL_LABEL,\n description:\n 'Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.',\n promptSnippet: DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: DEFAULT_PROMPT_GUIDELINES,\n parameters: TaskParamsSchema,\n // Task rows carry their own status colors. Owning the shell prevents Pi's\n // pending/success/error background fill from obscuring those row states.\n renderShell: 'self',\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx) {\n const action = params.action as TaskAction;\n const mutationParams = params as TaskMutationParams;\n\n try {\n rejectStrayFields(action, mutationParams);\n if (REDUCER_ACTIONS.has(action)) {\n const result = await executeReducerAction(store, action as ReducerAction, mutationParams);\n dependencies.onChange?.();\n return result;\n }\n\n let outcome: DelegationOutcome;\n if (action === 'assign') {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Delegating task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.assign(mutationParams.id ?? Number.NaN, {\n agent: mutationParams.agent,\n inlineAgent: mutationParams.inlineAgent,\n instructions: mutationParams.instructions,\n relevantFiles: mutationParams.relevantFiles,\n priorFindings: mutationParams.priorFindings,\n model: mutationParams.model,\n context: mutationParams.context,\n signal,\n });\n } else {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Requesting cancellation for task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.cancel(mutationParams.id ?? Number.NaN);\n }\n\n dependencies.onChange?.();\n if (!outcome.ok) throw actionableTaskError(action, outcome.message);\n return buildTextResult(action, mutationParams, store.snapshot, outcome.message, undefined);\n } catch (error) {\n // Rethrown so pi still renders the failure to the model; recorded\n // because this is where a store fault becomes visible to the user.\n dependencies.report?.error(TASK_EVENT.toolFailed, error, {\n [ACTION_ATTRIBUTE]: action,\n ...(mutationParams.id === undefined ? {} : { [TASK_ID_ATTRIBUTE]: mutationParams.id }),\n });\n if (error instanceof TaskToolExecutionError) throw error;\n throw actionableTaskError(action, error instanceof Error ? error.message : String(error));\n }\n },\n\n // Render hooks receive no session identity, so they read the store snapshot\n // that the executing session last loaded. That is the foreground session's\n // own view, which is exactly what its transcript should show.\n renderCall(args, theme, _context) {\n return renderTaskCall(args as never, theme, store.snapshot.tasks);\n },\n\n renderResult(result, options, theme, _context) {\n return renderTaskResult(result, options, theme);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{TASK_EVENT as e}from"../../adapters/telemetry/logSinkTelemetry.mjs";import{MSG_UPSERT_FIELD_MISPLACED as t,TOOL_LABEL as n,TOOL_NAME as r,TaskParamsSchema as i,taskActionAcceptsField as a}from"../../schemas/task.mjs";import{applyTaskMutation as o,isCommittingOp as s}from"../../services/store/reducer.mjs";import{renderTaskCall as c,renderTaskResult as l}from"../../tui/format.mjs";import{DEFAULT_PROMPT_GUIDELINES as u}from"./promptGuidelines.mjs";import{buildTextResult as d,buildToolResult as f,formatUpsertFailureText as p}from"./responseEnvelope.mjs";const m=`Manage a shared task list to track multi-step progress, and delegate tasks to subagents`,h=new Set([`upsert`,`list`,`get`,`delete`,`clear`]);var g=class extends Error{};function _(e,t){return new g([`Task ${e} failed: ${t}`,``,`Options:`,`- List the task board and inspect the current ids, states, and blockers.`,`- Correct the arguments or state conflict, then retry once.`,`- Ask the user before cancelling work or changing dependencies to recover.`].join(`
|
|
2
|
-
`))}function v(e,n){let r=Object.keys(n).filter(t=>n[t]!==void 0&&!a(e,t));if(r.length===0)return;let i=e===`upsert`?` ${t}`:``;throw _(e,`${e} does not accept ${r.join(`, `)}.${i}`)}async function y(e,t,n){let{document:r,value:i}=await e.mutate(e=>{let r=o(e,t,n);return{...s(r.op)?{document:r.document}:{},value:r}});if(i.op.kind===`error`)throw _(t,i.op.message);if(i.op.kind===`upsert`&&i.op.applied===0)throw _(t,p(i.op));return f(t,n,r,i.op)}function b(t,a){let{store:o,delegation:s}=a;t.registerTool({name:r,label:n,description:`Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.`,promptSnippet:m,promptGuidelines:u,parameters:i,async execute(t,n,r,i,c){let l=n.action,u=n;try{if(v(l,u),h.has(l)){let e=await y(o,l,u);return a.onChange?.(),e}let e;if(l===`assign`?(i?.(d(l,u,o.snapshot,`Delegating task #${u.id??`unknown`}...`)),e=await s.assign(u.id??NaN,{agent:u.agent,inlineAgent:u.inlineAgent,instructions:u.instructions,relevantFiles:u.relevantFiles,priorFindings:u.priorFindings,model:u.model,context:u.context,signal:r})):(i?.(d(l,u,o.snapshot,`Requesting cancellation for task #${u.id??`unknown`}...`)),e=await s.cancel(u.id??NaN)),a.onChange?.(),!e.ok)throw _(l,e.message);return d(l,u,o.snapshot,e.message,void 0)}catch(t){throw a.report?.error(e.toolFailed,t,{"tool.action":l,...u.id===void 0?{}:{"task.id":u.id}}),t instanceof g?t:_(l,t instanceof Error?t.message:String(t))}},renderCall(e,t,n){return c(e,t,o.snapshot.tasks)},renderResult(e,t,n,r){return l(e,t,n)}})}export{u as DEFAULT_PROMPT_GUIDELINES,m as DEFAULT_PROMPT_SNIPPET,b as registerTaskTool};
|
|
2
|
+
`))}function v(e,n){let r=Object.keys(n).filter(t=>n[t]!==void 0&&!a(e,t));if(r.length===0)return;let i=e===`upsert`?` ${t}`:``;throw _(e,`${e} does not accept ${r.join(`, `)}.${i}`)}async function y(e,t,n){let{document:r,value:i}=await e.mutate(e=>{let r=o(e,t,n);return{...s(r.op)?{document:r.document}:{},value:r}});if(i.op.kind===`error`)throw _(t,i.op.message);if(i.op.kind===`upsert`&&i.op.applied===0)throw _(t,p(i.op));return f(t,n,r,i.op)}function b(t,a){let{store:o,delegation:s}=a;t.registerTool({name:r,label:n,description:`Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.`,promptSnippet:m,promptGuidelines:u,parameters:i,renderShell:`self`,async execute(t,n,r,i,c){let l=n.action,u=n;try{if(v(l,u),h.has(l)){let e=await y(o,l,u);return a.onChange?.(),e}let e;if(l===`assign`?(i?.(d(l,u,o.snapshot,`Delegating task #${u.id??`unknown`}...`)),e=await s.assign(u.id??NaN,{agent:u.agent,inlineAgent:u.inlineAgent,instructions:u.instructions,relevantFiles:u.relevantFiles,priorFindings:u.priorFindings,model:u.model,context:u.context,signal:r})):(i?.(d(l,u,o.snapshot,`Requesting cancellation for task #${u.id??`unknown`}...`)),e=await s.cancel(u.id??NaN)),a.onChange?.(),!e.ok)throw _(l,e.message);return d(l,u,o.snapshot,e.message,void 0)}catch(t){throw a.report?.error(e.toolFailed,t,{"tool.action":l,...u.id===void 0?{}:{"task.id":u.id}}),t instanceof g?t:_(l,t instanceof Error?t.message:String(t))}},renderCall(e,t,n){return c(e,t,o.snapshot.tasks)},renderResult(e,t,n,r){return l(e,t,n)}})}export{u as DEFAULT_PROMPT_GUIDELINES,m as DEFAULT_PROMPT_SNIPPET,b as registerTaskTool};
|
|
3
3
|
//# sourceMappingURL=taskTool.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskTool.mjs","names":[],"sources":["../../../src/commands/task/taskTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { TASK_EVENT, type TaskFailureReporter } from '../../adapters/telemetry/logSinkTelemetry.ts';\nimport {\n MSG_UPSERT_FIELD_MISPLACED,\n TaskParamsSchema,\n TOOL_LABEL,\n TOOL_NAME,\n taskActionAcceptsField,\n} from '../../schemas/task.ts';\nimport type { DelegationManager, DelegationOutcome } from '../../services/delegation/manager.ts';\nimport { applyTaskMutation, isCommittingOp, type ReducerAction } from '../../services/store/reducer.ts';\nimport type { TaskStore } from '../../services/store/taskStore.ts';\nimport type { TaskAction, TaskMutationParams } from '../../services/store/types.ts';\nimport { renderTaskCall, renderTaskResult } from '../../tui/format.ts';\nimport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\nimport { buildTextResult, buildToolResult, formatUpsertFailureText, type ToolResult } from './responseEnvelope.ts';\n\nexport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\n\nexport const DEFAULT_PROMPT_SNIPPET =\n 'Manage a shared task list to track multi-step progress, and delegate tasks to subagents';\n\nexport interface TaskToolDependencies {\n store: TaskStore;\n delegation: DelegationManager;\n onChange?: () => void;\n report?: TaskFailureReporter;\n}\n\nconst ACTION_ATTRIBUTE = 'tool.action';\nconst TASK_ID_ATTRIBUTE = 'task.id';\n\nconst REDUCER_ACTIONS = new Set<TaskAction>(['upsert', 'list', 'get', 'delete', 'clear']);\n\nclass TaskToolExecutionError extends Error {}\n\nfunction actionableTaskError(action: TaskAction, message: string): TaskToolExecutionError {\n return new TaskToolExecutionError(\n [\n `Task ${action} failed: ${message}`,\n '',\n 'Options:',\n '- List the task board and inspect the current ids, states, and blockers.',\n '- Correct the arguments or state conflict, then retry once.',\n '- Ask the user before cancelling work or changing dependencies to recover.',\n ].join('\\n'),\n );\n}\n\n/**\n * Reject a field that belongs to a different action.\n *\n * The params schema is flat and shared across every action, so this is the only\n * place `{\"action\":\"upsert\",\"id\":3}` can be caught — and it must be, because\n * the entry it carries has no id and would silently create a duplicate task.\n */\nfunction rejectStrayFields(action: TaskAction, params: TaskMutationParams): void {\n const stray = Object.keys(params).filter((key) => params[key] !== undefined && !taskActionAcceptsField(action, key));\n if (stray.length === 0) return;\n const hint = action === 'upsert' ? ` ${MSG_UPSERT_FIELD_MISPLACED}` : '';\n throw actionableTaskError(action, `${action} does not accept ${stray.join(', ')}.${hint}`);\n}\n\n/** Run an action through the reducer, persisting only when it produced a change. */\nasync function executeReducerAction(\n store: TaskStore,\n action: ReducerAction,\n params: TaskMutationParams,\n): Promise<ToolResult> {\n const { document, value } = await store.mutate((current) => {\n const result = applyTaskMutation(current, action, params);\n return {\n ...(isCommittingOp(result.op) ? { document: result.document } : {}),\n value: result,\n };\n });\n if (value.op.kind === 'error') throw actionableTaskError(action, value.op.message);\n // An upsert that applied nothing is a failed call: it wrote nothing and left\n // `rev` alone, so it must not read to the model as a success. A batch where\n // some entries landed returns normally — that is what partial apply means.\n if (value.op.kind === 'upsert' && value.op.applied === 0) {\n throw actionableTaskError(action, formatUpsertFailureText(value.op));\n }\n // `document` is the committed document, so `details.rev` matches what landed\n // on disk; `value.document` is the pre-write copy and lags by one.\n return buildToolResult(action, params, document, value.op);\n}\n\nexport function registerTaskTool(pi: ExtensionAPI, dependencies: TaskToolDependencies): void {\n const { store, delegation } = dependencies;\n\n pi.registerTool({\n name: TOOL_NAME,\n label: TOOL_LABEL,\n description:\n 'Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.',\n promptSnippet: DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: DEFAULT_PROMPT_GUIDELINES,\n parameters: TaskParamsSchema,\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx) {\n const action = params.action as TaskAction;\n const mutationParams = params as TaskMutationParams;\n\n try {\n rejectStrayFields(action, mutationParams);\n if (REDUCER_ACTIONS.has(action)) {\n const result = await executeReducerAction(store, action as ReducerAction, mutationParams);\n dependencies.onChange?.();\n return result;\n }\n\n let outcome: DelegationOutcome;\n if (action === 'assign') {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Delegating task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.assign(mutationParams.id ?? Number.NaN, {\n agent: mutationParams.agent,\n inlineAgent: mutationParams.inlineAgent,\n instructions: mutationParams.instructions,\n relevantFiles: mutationParams.relevantFiles,\n priorFindings: mutationParams.priorFindings,\n model: mutationParams.model,\n context: mutationParams.context,\n signal,\n });\n } else {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Requesting cancellation for task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.cancel(mutationParams.id ?? Number.NaN);\n }\n\n dependencies.onChange?.();\n if (!outcome.ok) throw actionableTaskError(action, outcome.message);\n return buildTextResult(action, mutationParams, store.snapshot, outcome.message, undefined);\n } catch (error) {\n // Rethrown so pi still renders the failure to the model; recorded\n // because this is where a store fault becomes visible to the user.\n dependencies.report?.error(TASK_EVENT.toolFailed, error, {\n [ACTION_ATTRIBUTE]: action,\n ...(mutationParams.id === undefined ? {} : { [TASK_ID_ATTRIBUTE]: mutationParams.id }),\n });\n if (error instanceof TaskToolExecutionError) throw error;\n throw actionableTaskError(action, error instanceof Error ? error.message : String(error));\n }\n },\n\n // Render hooks receive no session identity, so they read the store snapshot\n // that the executing session last loaded. That is the foreground session's\n // own view, which is exactly what its transcript should show.\n renderCall(args, theme, _context) {\n return renderTaskCall(args as never, theme, store.snapshot.tasks);\n },\n\n renderResult(result, options, theme, _context) {\n return renderTaskResult(result, options, theme);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"taskTool.mjs","names":[],"sources":["../../../src/commands/task/taskTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { TASK_EVENT, type TaskFailureReporter } from '../../adapters/telemetry/logSinkTelemetry.ts';\nimport {\n MSG_UPSERT_FIELD_MISPLACED,\n TaskParamsSchema,\n TOOL_LABEL,\n TOOL_NAME,\n taskActionAcceptsField,\n} from '../../schemas/task.ts';\nimport type { DelegationManager, DelegationOutcome } from '../../services/delegation/manager.ts';\nimport { applyTaskMutation, isCommittingOp, type ReducerAction } from '../../services/store/reducer.ts';\nimport type { TaskStore } from '../../services/store/taskStore.ts';\nimport type { TaskAction, TaskMutationParams } from '../../services/store/types.ts';\nimport { renderTaskCall, renderTaskResult } from '../../tui/format.ts';\nimport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\nimport { buildTextResult, buildToolResult, formatUpsertFailureText, type ToolResult } from './responseEnvelope.ts';\n\nexport { DEFAULT_PROMPT_GUIDELINES } from './promptGuidelines.ts';\n\nexport const DEFAULT_PROMPT_SNIPPET =\n 'Manage a shared task list to track multi-step progress, and delegate tasks to subagents';\n\nexport interface TaskToolDependencies {\n store: TaskStore;\n delegation: DelegationManager;\n onChange?: () => void;\n report?: TaskFailureReporter;\n}\n\nconst ACTION_ATTRIBUTE = 'tool.action';\nconst TASK_ID_ATTRIBUTE = 'task.id';\n\nconst REDUCER_ACTIONS = new Set<TaskAction>(['upsert', 'list', 'get', 'delete', 'clear']);\n\nclass TaskToolExecutionError extends Error {}\n\nfunction actionableTaskError(action: TaskAction, message: string): TaskToolExecutionError {\n return new TaskToolExecutionError(\n [\n `Task ${action} failed: ${message}`,\n '',\n 'Options:',\n '- List the task board and inspect the current ids, states, and blockers.',\n '- Correct the arguments or state conflict, then retry once.',\n '- Ask the user before cancelling work or changing dependencies to recover.',\n ].join('\\n'),\n );\n}\n\n/**\n * Reject a field that belongs to a different action.\n *\n * The params schema is flat and shared across every action, so this is the only\n * place `{\"action\":\"upsert\",\"id\":3}` can be caught — and it must be, because\n * the entry it carries has no id and would silently create a duplicate task.\n */\nfunction rejectStrayFields(action: TaskAction, params: TaskMutationParams): void {\n const stray = Object.keys(params).filter((key) => params[key] !== undefined && !taskActionAcceptsField(action, key));\n if (stray.length === 0) return;\n const hint = action === 'upsert' ? ` ${MSG_UPSERT_FIELD_MISPLACED}` : '';\n throw actionableTaskError(action, `${action} does not accept ${stray.join(', ')}.${hint}`);\n}\n\n/** Run an action through the reducer, persisting only when it produced a change. */\nasync function executeReducerAction(\n store: TaskStore,\n action: ReducerAction,\n params: TaskMutationParams,\n): Promise<ToolResult> {\n const { document, value } = await store.mutate((current) => {\n const result = applyTaskMutation(current, action, params);\n return {\n ...(isCommittingOp(result.op) ? { document: result.document } : {}),\n value: result,\n };\n });\n if (value.op.kind === 'error') throw actionableTaskError(action, value.op.message);\n // An upsert that applied nothing is a failed call: it wrote nothing and left\n // `rev` alone, so it must not read to the model as a success. A batch where\n // some entries landed returns normally — that is what partial apply means.\n if (value.op.kind === 'upsert' && value.op.applied === 0) {\n throw actionableTaskError(action, formatUpsertFailureText(value.op));\n }\n // `document` is the committed document, so `details.rev` matches what landed\n // on disk; `value.document` is the pre-write copy and lags by one.\n return buildToolResult(action, params, document, value.op);\n}\n\nexport function registerTaskTool(pi: ExtensionAPI, dependencies: TaskToolDependencies): void {\n const { store, delegation } = dependencies;\n\n pi.registerTool({\n name: TOOL_NAME,\n label: TOOL_LABEL,\n description:\n 'Manage a shared task list for tracking multi-step progress, and delegate tasks to subagents. Actions: upsert (write tasks — a tasks[] array where an entry with an id changes that task and an entry without an id creates one, so one call can capture a whole plan), list, get, delete (tombstone), clear (close/reset the list), assign (hand the task to a named subagent that works it in the background), cancel (stop a delegated run). upsert applies each entry independently: successes are committed and failures are reported by array index, and an entry can depend on a task created by an earlier entry in the same call via a ref. Batch when writing a plan down; report progress one task at a time. Status: pending → in_progress → completed, plus failed and a deleted tombstone. The list is shared within the current session tree, including delegated subagents, while unrelated sessions start empty. Session stores persist until retention cleanup.',\n promptSnippet: DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: DEFAULT_PROMPT_GUIDELINES,\n parameters: TaskParamsSchema,\n // Task rows carry their own status colors. Owning the shell prevents Pi's\n // pending/success/error background fill from obscuring those row states.\n renderShell: 'self',\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx) {\n const action = params.action as TaskAction;\n const mutationParams = params as TaskMutationParams;\n\n try {\n rejectStrayFields(action, mutationParams);\n if (REDUCER_ACTIONS.has(action)) {\n const result = await executeReducerAction(store, action as ReducerAction, mutationParams);\n dependencies.onChange?.();\n return result;\n }\n\n let outcome: DelegationOutcome;\n if (action === 'assign') {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Delegating task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.assign(mutationParams.id ?? Number.NaN, {\n agent: mutationParams.agent,\n inlineAgent: mutationParams.inlineAgent,\n instructions: mutationParams.instructions,\n relevantFiles: mutationParams.relevantFiles,\n priorFindings: mutationParams.priorFindings,\n model: mutationParams.model,\n context: mutationParams.context,\n signal,\n });\n } else {\n onUpdate?.(\n buildTextResult(\n action,\n mutationParams,\n store.snapshot,\n `Requesting cancellation for task #${mutationParams.id ?? 'unknown'}...`,\n ),\n );\n outcome = await delegation.cancel(mutationParams.id ?? Number.NaN);\n }\n\n dependencies.onChange?.();\n if (!outcome.ok) throw actionableTaskError(action, outcome.message);\n return buildTextResult(action, mutationParams, store.snapshot, outcome.message, undefined);\n } catch (error) {\n // Rethrown so pi still renders the failure to the model; recorded\n // because this is where a store fault becomes visible to the user.\n dependencies.report?.error(TASK_EVENT.toolFailed, error, {\n [ACTION_ATTRIBUTE]: action,\n ...(mutationParams.id === undefined ? {} : { [TASK_ID_ATTRIBUTE]: mutationParams.id }),\n });\n if (error instanceof TaskToolExecutionError) throw error;\n throw actionableTaskError(action, error instanceof Error ? error.message : String(error));\n }\n },\n\n // Render hooks receive no session identity, so they read the store snapshot\n // that the executing session last loaded. That is the foreground session's\n // own view, which is exactly what its transcript should show.\n renderCall(args, theme, _context) {\n return renderTaskCall(args as never, theme, store.snapshot.tasks);\n },\n\n renderResult(result, options, theme, _context) {\n return renderTaskResult(result, options, theme);\n },\n });\n}\n"],"mappings":"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"}
|
package/dist/tui/format.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../services/delegation/manager.cjs");let t=require("@earendil-works/pi-tui"),n=require("@agimon-ai/doompi-ui/theme");const
|
|
2
|
-
`).filter(e=>e.trim().length>0)}function
|
|
3
|
-
`),0,0)}if(!
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../services/delegation/manager.cjs");let t=require("@earendil-works/pi-tui"),n=require("@agimon-ai/doompi-ui/theme"),r=require("@agimon-ai/doompi-ui/toolChrome");const i={pending:`○`,in_progress:`◐`,completed:`●`,failed:`✗`,deleted:`⊘`},a={pending:`dim`,in_progress:`warning`,completed:`success`,failed:`error`,deleted:`muted`},o={pending:`pending`,in_progress:`in progress`,completed:`completed`,failed:`failed`,deleted:`deleted`},s={...i,completed:`✓`,deleted:i.failed},c={...a,deleted:`error`},l=1e3,u=/^working:\s*/i;function d(e){return e?.replace(u,``).trim()||void 0}const f={upsert:`✎`,delete:`×`,get:`›`,list:`☰`,clear:`∅`,assign:`⇒`,cancel:`⊗`};function p(e){return e.length===0?f.upsert:e.every(e=>e.id===void 0)?`+`:e.every(e=>e.id!==void 0)?`→`:f.upsert}function m(e,t){return t.fg(c[e],s[e])}function h(e){let t=Math.max(0,Math.floor(e/1e3)),n=t%60,r=Math.floor(t/60);if(r<60)return r===0?`${n}s`:`${r}m${String(n).padStart(2,`0`)}s`;let i=r%60;return`${Math.floor(r/60)}h${String(i).padStart(2,`0`)}m${String(n).padStart(2,`0`)}s`}function g(e){let t=Math.max(0,Math.round(e));return t<l?String(t):t<1e4?`${(t/l).toFixed(1)}k`:`${Math.round(t/l)}k`}function _(t,r,i,a,o=Date.now(),s){let c=t.status===`in_progress`?`accent`:t.status===`completed`||t.status===`deleted`?`muted`:`text`,l=r.fg(c,t.subject);(t.status===`completed`||t.status===`deleted`)&&(l=r.strikethrough(l));let u=m(t.status,r);i&&(u+=` ${r.fg(`dim`,`#${t.id}`)}`),u+=` ${l}`;let f=t.delegation?.agent;if(f&&t.delegation?.state===`running`){let i=[];a&&(i.push(r.fg(`muted`,`[${h(e.DelegationManager.elapsedMs(a,o))}]`)),a.tokens!==void 0&&i.push(r.fg(`muted`,`[${g(a.tokens)}]`)));let c=t.delegation.runId??t.delegation.requestId;i.push(r.fg(s??(0,n.agentIdentityColor)(c),`[${f}]`)),u+=` ${i.join(``)}`;let l=d(a?.currentTool);l&&(u+=` ${r.fg(`muted`,`· ${l}`)}`)}else f&&(u+=` ${r.fg(`muted`,`[${f}]`)}`);return t.status===`in_progress`&&t.activeForm&&(u+=` ${r.fg(`muted`,`(${t.activeForm})`)}`),t.blockedBy?.length&&(u+=` ${r.fg(`muted`,`⛓ ${t.blockedBy.map(e=>`#${e}`).join(`,`)}`)}`),u}function v(e,t){let n=e.status===`in_progress`&&e.activeForm?` (${e.activeForm})`:``,r=e.delegation?.agent?` [${e.delegation.agent}]`:``,i=e.blockedBy?.length?` ⛓ ${e.blockedBy.map(e=>`#${e}`).join(`,`)}`:``;return` ${t} #${e.id} ${e.subject}${n}${r}${i}`}function y(e,t,n){let i=e.action===`upsert`?e.tasks??[]:[],a=e.action===`upsert`?p(i):f[e.action]??e.action,s=(0,r.renderToolHeading)(`task`,`${a} ${e.action}`,t);if(e.action===`upsert`){if(i.length===1){let e=i[0];if(e.id===void 0)e.subject&&(s+=` ${t.fg(`dim`,e.subject)}`);else{let r=n.find(t=>t.id===e.id)?.subject??e.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`}}else i.length>1&&(s+=` ${t.fg(`dim`,`${i.length} tasks`)}`)}else if(e.action===`assign`&&e.id!==void 0){let r=n.find(t=>t.id===e.id)?.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`,e.agent&&(s+=` ${t.fg(`muted`,`→ ${e.agent}`)}`)}else if((e.action===`get`||e.action===`delete`||e.action===`cancel`)&&e.id!==void 0){let r=n.find(t=>t.id===e.id)?.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`}else e.action===`list`&&e.status&&(s+=` ${t.fg(`muted`,o[e.status])}`);return new r.DoomToolCall(s)}var b=class{rows;constructor(e){this.rows=e}invalidate(){}render(e){return this.rows.map(({left:n,right:r})=>{if(r===void 0||r.length===0)return(0,t.truncateToWidth)(n,e);let i=(0,t.visibleWidth)(r),a=e-i-2;if(a<=0)return(0,t.truncateToWidth)(n,e);let o=(0,t.truncateToWidth)(n,a),s=Math.max(2,e-(0,t.visibleWidth)(o)-i);return o+` `.repeat(s)+r})}},x=class{content;theme;constructor(e,t){this.content=e,this.theme=t}render(e){if(e<=0)return[];if(e<3)return this.content.render(e);let t=e-2,n=this.theme.fg(`borderMuted`,`─`.repeat(t)),r=this.content.render(t).map(e=>` ${e}`);return[` ${n}`,...r,``]}invalidate(){this.content.invalidate()}};function S(e){return(e.content??[]).map(e=>e.text??``).join(``)}function C(e){return e.split(`
|
|
2
|
+
`).filter(e=>e.trim().length>0)}function w(e){let t=e.params,n=e.tasks;return t.includeDeleted!==!0&&(n=n.filter(e=>e.status!==`deleted`)),t.status&&(n=n.filter(e=>e.status===t.status)),n}function T(e){let t=e.params;if(t.id!==void 0)return e.tasks.find(e=>e.id===t.id)}function E(e){return(e.upsert?.applied??[]).flatMap(t=>e.tasks.find(e=>e.id===t)??[])}function D(e,n,r){let i=e.details;if(n.isPartial){let n=C(S(e)).at(-1)??`working`;return new t.Text(r.fg(`warning`,`◐`)+r.fg(`dim`,` ${n}`),0,0)}if(e.isError===!0||i?.error!==void 0){let a=C(i?.error??S(e));if(a.length<=1)return new t.Text(r.fg(`error`,`✗`)+r.fg(`dim`,a[0]?` ${a[0]}`:``),0,0);let o=n.expanded?a:a.slice(0,8);return new t.Text([...o.map(e=>r.fg(`toolOutput`,e)),r.fg(`error`,`✗`)].join(`
|
|
3
|
+
`),0,0)}if(!i)return new t.Text(r.fg(`success`,`✓`),0,0);let s=e=>({left:_(e,r,!0),right:r.fg(a[e.status],o[e.status])});if(i.action===`list`){let e=w(i),t=n.expanded?e:e.slice(0,8),a=t.map(s),o=e.length===0?`no tasks`:`${e.length} task${e.length===1?``:`s`}`;return e.length>t.length&&(o+=` · ctrl+o`),a.push({left:r.fg(`success`,`✓`)+r.fg(`dim`,` ${o}`)}),new b(a)}if(i.action===`clear`)return new t.Text(r.fg(`success`,`✓`)+r.fg(`dim`,` cleared`),0,0);if(i.action===`upsert`){let e=E(i),t=i.upsert?.failed??0;if(e.length===1&&t===0)return new b([s(e[0])]);let a=n.expanded?e:e.slice(0,8),o=a.map(s),c=`${e.length} applied`;t>0&&(c+=` · ${t} failed`),e.length>a.length&&(c+=` · ctrl+o`);let l=t>0?r.fg(`warning`,`!`):r.fg(`success`,`✓`);return o.push({left:l+r.fg(`dim`,` ${c}`)}),new b(o)}let c=T(i);return c?new b([s(c)]):new t.Text(r.fg(`success`,`✓`),0,0)}function O(e,t,n){return new x(D(e,t,n),n)}exports.ACTION_GLYPH=f,exports.JustifiedRows=b,exports.STATUS_COLOR=a,exports.STATUS_GLYPH=i,exports.STATUS_LABEL=o,exports.formatCommandTaskLine=v,exports.formatElapsedMs=h,exports.formatOverlayTaskLine=_,exports.formatTokenCount=g,exports.overlayStatusGlyph=m,exports.renderTaskCall=y,exports.renderTaskResult=O;
|
|
4
4
|
//# sourceMappingURL=format.cjs.map
|
package/dist/tui/format.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":["DelegationManager","agentIdentityColor","Text","truncateToWidth","visibleWidth"],"sources":["../../src/tui/format.ts"],"sourcesContent":["import { agentIdentityColor } from '@agimon-ai/doompi-ui/theme';\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, Text, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { DelegationManager, type DelegationProgress } from '../services/delegation/manager.ts';\nimport type {\n Task,\n TaskAction,\n TaskDetails,\n TaskItemMutation,\n TaskMutationParams,\n TaskStatus,\n} from '../services/store/types.ts';\n\nexport const STATUS_GLYPH: Record<TaskStatus, string> = {\n pending: '○',\n in_progress: '◐',\n completed: '●',\n failed: '✗',\n deleted: '⊘',\n};\n\ntype TaskStatusColor = 'dim' | 'warning' | 'success' | 'error' | 'muted';\n\nexport const STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n pending: 'dim',\n in_progress: 'warning',\n completed: 'success',\n failed: 'error',\n deleted: 'muted',\n};\n\nexport const STATUS_LABEL: Record<TaskStatus, string> = {\n pending: 'pending',\n in_progress: 'in progress',\n completed: 'completed',\n failed: 'failed',\n deleted: 'deleted',\n};\n\nconst OVERLAY_STATUS_GLYPH: Record<TaskStatus, string> = {\n ...STATUS_GLYPH,\n completed: '✓',\n deleted: STATUS_GLYPH.failed,\n};\n\nconst OVERLAY_STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n ...STATUS_COLOR,\n deleted: 'error',\n};\nconst MILLISECONDS_PER_SECOND = 1000;\nconst SECONDS_PER_MINUTE = 60;\nconst MINUTES_PER_HOUR = 60;\nconst TOKENS_PER_KILO = 1000;\nconst TOKEN_DECIMAL_LIMIT = 10_000;\nconst REDUNDANT_WORKING_PREFIX = /^working:\\s*/i;\n\n/** Thinking text already communicates activity; keep only its useful content. */\nfunction progressText(currentTool: string | undefined): string | undefined {\n const text = currentTool?.replace(REDUNDANT_WORKING_PREFIX, '').trim();\n return text || undefined;\n}\n\nexport const ACTION_GLYPH: Record<TaskAction, string> = {\n upsert: '✎',\n delete: '×',\n get: '›',\n list: '☰',\n clear: '∅',\n assign: '⇒',\n cancel: '⊗',\n};\n\n/**\n * `upsert` is two operations behind one name, so a homogeneous batch narrows to\n * the glyph the operator already reads: `+` means new work appeared, `→` means\n * work moved. A mixed batch keeps the neutral write glyph.\n */\nconst UPSERT_CREATE_GLYPH = '+';\nconst UPSERT_UPDATE_GLYPH = '→';\n\nfunction upsertGlyph(items: readonly TaskItemMutation[]): string {\n if (items.length === 0) return ACTION_GLYPH.upsert;\n if (items.every((item) => item.id === undefined)) return UPSERT_CREATE_GLYPH;\n if (items.every((item) => item.id !== undefined)) return UPSERT_UPDATE_GLYPH;\n return ACTION_GLYPH.upsert;\n}\n\nexport function overlayStatusGlyph(status: TaskStatus, theme: Theme): string {\n return theme.fg(OVERLAY_STATUS_COLOR[status], OVERLAY_STATUS_GLYPH[status]);\n}\n\nexport function formatElapsedMs(durationMs: number): string {\n const totalSeconds = Math.max(0, Math.floor(durationMs / MILLISECONDS_PER_SECOND));\n const seconds = totalSeconds % SECONDS_PER_MINUTE;\n const totalMinutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);\n if (totalMinutes < MINUTES_PER_HOUR) {\n return totalMinutes === 0 ? `${seconds}s` : `${totalMinutes}m${String(seconds).padStart(2, '0')}s`;\n }\n const minutes = totalMinutes % MINUTES_PER_HOUR;\n const hours = Math.floor(totalMinutes / MINUTES_PER_HOUR);\n return `${hours}h${String(minutes).padStart(2, '0')}m${String(seconds).padStart(2, '0')}s`;\n}\n\nexport function formatTokenCount(tokens: number): string {\n const count = Math.max(0, Math.round(tokens));\n if (count < TOKENS_PER_KILO) return String(count);\n if (count < TOKEN_DECIMAL_LIMIT) return `${(count / TOKENS_PER_KILO).toFixed(1)}k`;\n return `${Math.round(count / TOKENS_PER_KILO)}k`;\n}\n\n/**\n * One task row for the persistent overlay.\n *\n * A delegated row carries the agent name and its current tool so the operator\n * can see that background work is progressing without opening a transcript.\n */\nexport function formatOverlayTaskLine(\n task: Task,\n theme: Theme,\n showId: boolean,\n progress?: DelegationProgress,\n nowMs = Date.now(),\n agentColor?: Parameters<Theme['fg']>[0],\n): string {\n const subjectColor =\n task.status === 'in_progress'\n ? 'accent'\n : task.status === 'completed' || task.status === 'deleted'\n ? 'muted'\n : 'text';\n let subject = theme.fg(subjectColor, task.subject);\n if (task.status === 'completed' || task.status === 'deleted') {\n subject = theme.strikethrough(subject);\n }\n\n let line = overlayStatusGlyph(task.status, theme);\n if (showId) line += ` ${theme.fg('dim', `#${task.id}`)}`;\n line += ` ${subject}`;\n\n const agent = task.delegation?.agent;\n if (agent && task.delegation?.state === 'running') {\n // No progress means no timing is known, so the chips are omitted rather than\n // reported as `[0s]`. The transcript renderer has no progress feed at all.\n const chips: string[] = [];\n if (progress) {\n chips.push(theme.fg('muted', `[${formatElapsedMs(DelegationManager.elapsedMs(progress, nowMs))}]`));\n if (progress.tokens !== undefined) chips.push(theme.fg('muted', `[${formatTokenCount(progress.tokens)}]`));\n }\n const identity = task.delegation.runId ?? task.delegation.requestId;\n chips.push(theme.fg(agentColor ?? agentIdentityColor(identity), `[${agent}]`));\n line += ` ${chips.join('')}`;\n const activity = progressText(progress?.currentTool);\n if (activity) line += ` ${theme.fg('muted', `· ${activity}`)}`;\n } else if (agent) {\n line += ` ${theme.fg('muted', `[${agent}]`)}`;\n }\n\n if (task.status === 'in_progress' && task.activeForm) {\n line += ` ${theme.fg('muted', `(${task.activeForm})`)}`;\n }\n if (task.blockedBy?.length) {\n line += ` ${theme.fg('muted', `⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}`)}`;\n }\n return line;\n}\n\n/** One task line for the `/tasks` command output. */\nexport function formatCommandTaskLine(task: Task, glyph: string): string {\n const form = task.status === 'in_progress' && task.activeForm ? ` (${task.activeForm})` : '';\n const agent = task.delegation?.agent ? ` [${task.delegation.agent}]` : '';\n const block = task.blockedBy?.length ? ` ⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}` : '';\n return ` ${glyph} #${task.id} ${task.subject}${form}${agent}${block}`;\n}\n\nexport function renderTaskCall(\n args: TaskMutationParams & { action: TaskAction },\n theme: Theme,\n tasks: readonly Task[],\n): Text {\n const items = args.action === 'upsert' ? (args.tasks ?? []) : [];\n const glyph = args.action === 'upsert' ? upsertGlyph(items) : (ACTION_GLYPH[args.action] ?? args.action);\n let text = theme.fg('toolTitle', theme.bold('task ')) + theme.fg('muted', glyph);\n\n if (args.action === 'upsert') {\n // One entry reads exactly as create or update did; a batch reads as a count\n // rather than a subject it would have to pick arbitrarily.\n if (items.length === 1) {\n const only = items[0];\n if (only.id === undefined) {\n if (only.subject) text += ` ${theme.fg('dim', only.subject)}`;\n } else {\n const subject = tasks.find((task) => task.id === only.id)?.subject ?? only.subject ?? `#${only.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n }\n } else if (items.length > 1) {\n text += ` ${theme.fg('dim', `${items.length} tasks`)}`;\n }\n } else if (args.action === 'assign' && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n if (args.agent) text += ` ${theme.fg('muted', `→ ${args.agent}`)}`;\n } else if ((args.action === 'get' || args.action === 'delete' || args.action === 'cancel') && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n } else if (args.action === 'list' && args.status) {\n text += ` ${theme.fg('muted', STATUS_LABEL[args.status])}`;\n }\n return new Text(text, 0, 0);\n}\n\n/** Task rows kept in a collapsed result, before `ctrl+o` reveals the rest. */\nconst TRANSCRIPT_TASK_ROWS = 8;\n/** Minimum gap between a row's subject and its right-aligned status. */\nconst STATUS_GUTTER = 2;\n\nexport interface JustifiedRow {\n left: string;\n /** Pinned to the right edge. The subject is truncated before this is dropped. */\n right?: string;\n}\n\n/**\n * Rows whose trailing text is flushed right.\n *\n * `Text` cannot do this: the status has to be placed against the viewport edge,\n * which is only known at render time.\n */\nexport class JustifiedRows implements Component {\n constructor(private readonly rows: readonly JustifiedRow[]) {}\n\n /**\n * Nothing to discard: the rows are themed by the caller and held verbatim, and\n * Pi rebuilds this component from `renderResult` whenever the theme changes.\n */\n invalidate(): void {}\n\n render(width: number): string[] {\n return this.rows.map(({ left, right }) => {\n if (right === undefined || right.length === 0) return truncateToWidth(left, width);\n const rightWidth = visibleWidth(right);\n const room = width - rightWidth - STATUS_GUTTER;\n if (room <= 0) return truncateToWidth(left, width);\n const subject = truncateToWidth(left, room);\n const gap = Math.max(STATUS_GUTTER, width - visibleWidth(subject) - rightWidth);\n return subject + ' '.repeat(gap) + right;\n });\n }\n}\n\nexport interface TaskResultLike {\n content?: Array<{ type: string; text?: string }>;\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface TaskResultOptions {\n expanded: boolean;\n isPartial: boolean;\n}\n\n/** Concatenated result text, which is all a thrown failure leaves behind. */\nfunction contentText(result: TaskResultLike): string {\n return (result.content ?? []).map((block) => block.text ?? '').join('');\n}\n\nfunction nonEmptyLines(text: string): string[] {\n return text.split('\\n').filter((line) => line.trim().length > 0);\n}\n\n/**\n * The rows `list` reported to the model.\n *\n * `details.tasks` is the whole post-mutation document, so the filters applied by\n * `formatContent` have to be applied again here or the transcript would disagree\n * with the text the model was given.\n */\nfunction listedTasks(details: TaskDetails): Task[] {\n const params = details.params as TaskMutationParams;\n let view = details.tasks;\n if (params.includeDeleted !== true) view = view.filter((task) => task.status !== 'deleted');\n if (params.status) view = view.filter((task) => task.status === params.status);\n return view;\n}\n\n/** The single task an id-bearing action acted on, so the transcript names it rather than a bare tick. */\nfunction subjectTask(details: TaskDetails): Task | undefined {\n const params = details.params as TaskMutationParams;\n if (params.id === undefined) return undefined;\n return details.tasks.find((task) => task.id === params.id);\n}\n\n/**\n * Tasks an upsert applied, resolved from the ids the reducer reported.\n *\n * The ids are read rather than guessed from the tail of the list: a batch\n * touches many rows, and a failed entry contributes none.\n */\nfunction upsertTasks(details: TaskDetails): Task[] {\n return (details.upsert?.applied ?? []).flatMap((id) => details.tasks.find((task) => task.id === id) ?? []);\n}\n\nexport function renderTaskResult(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n const details = result.details as TaskDetails | undefined;\n\n // Running. `assign` and `cancel` stream a placeholder before the run settles.\n if (options.isPartial) {\n const text = nonEmptyLines(contentText(result)).at(-1) ?? 'working';\n return new Text(theme.fg('warning', '◐') + theme.fg('dim', ` ${text}`), 0, 0);\n }\n\n // Failed. Every failure path throws, so Pi hands back error text and no\n // details; without this the block would render a green tick on a failure.\n if (result.isError === true || details?.error !== undefined) {\n const message = details?.error ?? contentText(result);\n const all = nonEmptyLines(message);\n if (all.length <= 1) {\n return new Text(theme.fg('error', '✗') + theme.fg('dim', all[0] ? ` ${all[0]}` : ''), 0, 0);\n }\n const body = options.expanded ? all : all.slice(0, TRANSCRIPT_TASK_ROWS);\n return new Text([...body.map((line) => theme.fg('toolOutput', line)), theme.fg('error', '✗')].join('\\n'), 0, 0);\n }\n\n if (!details) return new Text(theme.fg('success', '✓'), 0, 0);\n\n /** Status rides on the task's own row, flushed right, rather than costing a line. */\n const row = (task: Task): JustifiedRow => ({\n left: formatOverlayTaskLine(task, theme, true),\n right: theme.fg(STATUS_COLOR[task.status], STATUS_LABEL[task.status]),\n });\n\n if (details.action === 'list') {\n const tasks = listedTasks(details);\n const shown = options.expanded ? tasks : tasks.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = tasks.length === 0 ? 'no tasks' : `${tasks.length} task${tasks.length === 1 ? '' : 's'}`;\n if (tasks.length > shown.length) summary += ' · ctrl+o';\n rows.push({ left: theme.fg('success', '✓') + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n if (details.action === 'clear') {\n return new Text(theme.fg('success', '✓') + theme.fg('dim', ' cleared'), 0, 0);\n }\n\n if (details.action === 'upsert') {\n const touched = upsertTasks(details);\n const failed = details.upsert?.failed ?? 0;\n // One applied task and nothing failed is the common case: keep the single\n // row it has always had rather than dressing it as a bulk report.\n if (touched.length === 1 && failed === 0) return new JustifiedRows([row(touched[0])]);\n\n const shown = options.expanded ? touched : touched.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = `${touched.length} applied`;\n if (failed > 0) summary += ` · ${failed} failed`;\n if (touched.length > shown.length) summary += ' · ctrl+o';\n // Failed entries get no row: there is no task to render, and their messages\n // are already in the text the model was given.\n const mark = failed > 0 ? theme.fg('warning', '!') : theme.fg('success', '✓');\n rows.push({ left: mark + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n const task = subjectTask(details);\n if (!task) return new Text(theme.fg('success', '✓'), 0, 0);\n return new JustifiedRows([row(task)]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.cjs","names":["DelegationManager","agentIdentityColor","renderToolHeading","DoomToolCall","truncateToWidth","visibleWidth","Text"],"sources":["../../src/tui/format.ts"],"sourcesContent":["import { agentIdentityColor } from '@agimon-ai/doompi-ui/theme';\nimport { DoomToolCall, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, Text, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { DelegationManager, type DelegationProgress } from '../services/delegation/manager.ts';\nimport type {\n Task,\n TaskAction,\n TaskDetails,\n TaskItemMutation,\n TaskMutationParams,\n TaskStatus,\n} from '../services/store/types.ts';\n\nexport const STATUS_GLYPH: Record<TaskStatus, string> = {\n pending: '○',\n in_progress: '◐',\n completed: '●',\n failed: '✗',\n deleted: '⊘',\n};\n\ntype TaskStatusColor = 'dim' | 'warning' | 'success' | 'error' | 'muted';\n\nexport const STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n pending: 'dim',\n in_progress: 'warning',\n completed: 'success',\n failed: 'error',\n deleted: 'muted',\n};\n\nexport const STATUS_LABEL: Record<TaskStatus, string> = {\n pending: 'pending',\n in_progress: 'in progress',\n completed: 'completed',\n failed: 'failed',\n deleted: 'deleted',\n};\n\nconst OVERLAY_STATUS_GLYPH: Record<TaskStatus, string> = {\n ...STATUS_GLYPH,\n completed: '✓',\n deleted: STATUS_GLYPH.failed,\n};\n\nconst OVERLAY_STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n ...STATUS_COLOR,\n deleted: 'error',\n};\nconst MILLISECONDS_PER_SECOND = 1000;\nconst SECONDS_PER_MINUTE = 60;\nconst MINUTES_PER_HOUR = 60;\nconst TOKENS_PER_KILO = 1000;\nconst TOKEN_DECIMAL_LIMIT = 10_000;\nconst REDUNDANT_WORKING_PREFIX = /^working:\\s*/i;\n\n/** Thinking text already communicates activity; keep only its useful content. */\nfunction progressText(currentTool: string | undefined): string | undefined {\n const text = currentTool?.replace(REDUNDANT_WORKING_PREFIX, '').trim();\n return text || undefined;\n}\n\nexport const ACTION_GLYPH: Record<TaskAction, string> = {\n upsert: '✎',\n delete: '×',\n get: '›',\n list: '☰',\n clear: '∅',\n assign: '⇒',\n cancel: '⊗',\n};\n\n/**\n * `upsert` is two operations behind one name, so a homogeneous batch narrows to\n * the glyph the operator already reads: `+` means new work appeared, `→` means\n * work moved. A mixed batch keeps the neutral write glyph.\n */\nconst UPSERT_CREATE_GLYPH = '+';\nconst UPSERT_UPDATE_GLYPH = '→';\n\nfunction upsertGlyph(items: readonly TaskItemMutation[]): string {\n if (items.length === 0) return ACTION_GLYPH.upsert;\n if (items.every((item) => item.id === undefined)) return UPSERT_CREATE_GLYPH;\n if (items.every((item) => item.id !== undefined)) return UPSERT_UPDATE_GLYPH;\n return ACTION_GLYPH.upsert;\n}\n\nexport function overlayStatusGlyph(status: TaskStatus, theme: Theme): string {\n return theme.fg(OVERLAY_STATUS_COLOR[status], OVERLAY_STATUS_GLYPH[status]);\n}\n\nexport function formatElapsedMs(durationMs: number): string {\n const totalSeconds = Math.max(0, Math.floor(durationMs / MILLISECONDS_PER_SECOND));\n const seconds = totalSeconds % SECONDS_PER_MINUTE;\n const totalMinutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);\n if (totalMinutes < MINUTES_PER_HOUR) {\n return totalMinutes === 0 ? `${seconds}s` : `${totalMinutes}m${String(seconds).padStart(2, '0')}s`;\n }\n const minutes = totalMinutes % MINUTES_PER_HOUR;\n const hours = Math.floor(totalMinutes / MINUTES_PER_HOUR);\n return `${hours}h${String(minutes).padStart(2, '0')}m${String(seconds).padStart(2, '0')}s`;\n}\n\nexport function formatTokenCount(tokens: number): string {\n const count = Math.max(0, Math.round(tokens));\n if (count < TOKENS_PER_KILO) return String(count);\n if (count < TOKEN_DECIMAL_LIMIT) return `${(count / TOKENS_PER_KILO).toFixed(1)}k`;\n return `${Math.round(count / TOKENS_PER_KILO)}k`;\n}\n\n/**\n * One task row for the persistent overlay.\n *\n * A delegated row carries the agent name and its current tool so the operator\n * can see that background work is progressing without opening a transcript.\n */\nexport function formatOverlayTaskLine(\n task: Task,\n theme: Theme,\n showId: boolean,\n progress?: DelegationProgress,\n nowMs = Date.now(),\n agentColor?: Parameters<Theme['fg']>[0],\n): string {\n const subjectColor =\n task.status === 'in_progress'\n ? 'accent'\n : task.status === 'completed' || task.status === 'deleted'\n ? 'muted'\n : 'text';\n let subject = theme.fg(subjectColor, task.subject);\n if (task.status === 'completed' || task.status === 'deleted') {\n subject = theme.strikethrough(subject);\n }\n\n let line = overlayStatusGlyph(task.status, theme);\n if (showId) line += ` ${theme.fg('dim', `#${task.id}`)}`;\n line += ` ${subject}`;\n\n const agent = task.delegation?.agent;\n if (agent && task.delegation?.state === 'running') {\n // No progress means no timing is known, so the chips are omitted rather than\n // reported as `[0s]`. The transcript renderer has no progress feed at all.\n const chips: string[] = [];\n if (progress) {\n chips.push(theme.fg('muted', `[${formatElapsedMs(DelegationManager.elapsedMs(progress, nowMs))}]`));\n if (progress.tokens !== undefined) chips.push(theme.fg('muted', `[${formatTokenCount(progress.tokens)}]`));\n }\n const identity = task.delegation.runId ?? task.delegation.requestId;\n chips.push(theme.fg(agentColor ?? agentIdentityColor(identity), `[${agent}]`));\n line += ` ${chips.join('')}`;\n const activity = progressText(progress?.currentTool);\n if (activity) line += ` ${theme.fg('muted', `· ${activity}`)}`;\n } else if (agent) {\n line += ` ${theme.fg('muted', `[${agent}]`)}`;\n }\n\n if (task.status === 'in_progress' && task.activeForm) {\n line += ` ${theme.fg('muted', `(${task.activeForm})`)}`;\n }\n if (task.blockedBy?.length) {\n line += ` ${theme.fg('muted', `⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}`)}`;\n }\n return line;\n}\n\n/** One task line for the `/tasks` command output. */\nexport function formatCommandTaskLine(task: Task, glyph: string): string {\n const form = task.status === 'in_progress' && task.activeForm ? ` (${task.activeForm})` : '';\n const agent = task.delegation?.agent ? ` [${task.delegation.agent}]` : '';\n const block = task.blockedBy?.length ? ` ⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}` : '';\n return ` ${glyph} #${task.id} ${task.subject}${form}${agent}${block}`;\n}\n\nexport function renderTaskCall(\n args: TaskMutationParams & { action: TaskAction },\n theme: Theme,\n tasks: readonly Task[],\n): Component {\n const items = args.action === 'upsert' ? (args.tasks ?? []) : [];\n const glyph = args.action === 'upsert' ? upsertGlyph(items) : (ACTION_GLYPH[args.action] ?? args.action);\n let text = renderToolHeading('task', `${glyph} ${args.action}`, theme);\n\n if (args.action === 'upsert') {\n // One entry reads exactly as create or update did; a batch reads as a count\n // rather than a subject it would have to pick arbitrarily.\n if (items.length === 1) {\n const only = items[0];\n if (only.id === undefined) {\n if (only.subject) text += ` ${theme.fg('dim', only.subject)}`;\n } else {\n const subject = tasks.find((task) => task.id === only.id)?.subject ?? only.subject ?? `#${only.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n }\n } else if (items.length > 1) {\n text += ` ${theme.fg('dim', `${items.length} tasks`)}`;\n }\n } else if (args.action === 'assign' && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n if (args.agent) text += ` ${theme.fg('muted', `→ ${args.agent}`)}`;\n } else if ((args.action === 'get' || args.action === 'delete' || args.action === 'cancel') && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n } else if (args.action === 'list' && args.status) {\n text += ` ${theme.fg('muted', STATUS_LABEL[args.status])}`;\n }\n return new DoomToolCall(text);\n}\n\n/** Task rows kept in a collapsed result, before `ctrl+o` reveals the rest. */\nconst TRANSCRIPT_TASK_ROWS = 8;\n/** Minimum gap between a row's subject and its right-aligned status. */\nconst STATUS_GUTTER = 2;\n\nexport interface JustifiedRow {\n left: string;\n /** Pinned to the right edge. The subject is truncated before this is dropped. */\n right?: string;\n}\n\n/**\n * Rows whose trailing text is flushed right.\n *\n * `Text` cannot do this: the status has to be placed against the viewport edge,\n * which is only known at render time.\n */\nexport class JustifiedRows implements Component {\n constructor(private readonly rows: readonly JustifiedRow[]) {}\n\n /**\n * Nothing to discard: the rows are themed by the caller and held verbatim, and\n * Pi rebuilds this component from `renderResult` whenever the theme changes.\n */\n invalidate(): void {}\n\n render(width: number): string[] {\n return this.rows.map(({ left, right }) => {\n if (right === undefined || right.length === 0) return truncateToWidth(left, width);\n const rightWidth = visibleWidth(right);\n const room = width - rightWidth - STATUS_GUTTER;\n if (room <= 0) return truncateToWidth(left, width);\n const subject = truncateToWidth(left, room);\n const gap = Math.max(STATUS_GUTTER, width - visibleWidth(subject) - rightWidth);\n return subject + ' '.repeat(gap) + right;\n });\n }\n}\n\n/**\n * Self-owned task shell with the same structural divider as the bash tool.\n *\n * Task rows already carry precise per-task state, so the divider stays neutral\n * instead of competing with or appearing to animate between those states.\n */\nclass TaskResultFrame implements Component {\n constructor(\n private readonly content: Component,\n private readonly theme: Theme,\n ) {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < 3) return this.content.render(width);\n\n const contentWidth = width - 2;\n const divider = this.theme.fg('borderMuted', '─'.repeat(contentWidth));\n const lines = this.content.render(contentWidth).map((line) => ` ${line}`);\n return [` ${divider}`, ...lines, ''];\n }\n\n invalidate(): void {\n this.content.invalidate();\n }\n}\n\nexport interface TaskResultLike {\n content?: Array<{ type: string; text?: string }>;\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface TaskResultOptions {\n expanded: boolean;\n isPartial: boolean;\n}\n\n/** Concatenated result text, which is all a thrown failure leaves behind. */\nfunction contentText(result: TaskResultLike): string {\n return (result.content ?? []).map((block) => block.text ?? '').join('');\n}\n\nfunction nonEmptyLines(text: string): string[] {\n return text.split('\\n').filter((line) => line.trim().length > 0);\n}\n\n/**\n * The rows `list` reported to the model.\n *\n * `details.tasks` is the whole post-mutation document, so the filters applied by\n * `formatContent` have to be applied again here or the transcript would disagree\n * with the text the model was given.\n */\nfunction listedTasks(details: TaskDetails): Task[] {\n const params = details.params as TaskMutationParams;\n let view = details.tasks;\n if (params.includeDeleted !== true) view = view.filter((task) => task.status !== 'deleted');\n if (params.status) view = view.filter((task) => task.status === params.status);\n return view;\n}\n\n/** The single task an id-bearing action acted on, so the transcript names it rather than a bare tick. */\nfunction subjectTask(details: TaskDetails): Task | undefined {\n const params = details.params as TaskMutationParams;\n if (params.id === undefined) return undefined;\n return details.tasks.find((task) => task.id === params.id);\n}\n\n/**\n * Tasks an upsert applied, resolved from the ids the reducer reported.\n *\n * The ids are read rather than guessed from the tail of the list: a batch\n * touches many rows, and a failed entry contributes none.\n */\nfunction upsertTasks(details: TaskDetails): Task[] {\n return (details.upsert?.applied ?? []).flatMap((id) => details.tasks.find((task) => task.id === id) ?? []);\n}\n\nfunction renderTaskResultBody(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n const details = result.details as TaskDetails | undefined;\n\n // Running. `assign` and `cancel` stream a placeholder before the run settles.\n if (options.isPartial) {\n const text = nonEmptyLines(contentText(result)).at(-1) ?? 'working';\n return new Text(theme.fg('warning', '◐') + theme.fg('dim', ` ${text}`), 0, 0);\n }\n\n // Failed. Every failure path throws, so Pi hands back error text and no\n // details; without this the block would render a green tick on a failure.\n if (result.isError === true || details?.error !== undefined) {\n const message = details?.error ?? contentText(result);\n const all = nonEmptyLines(message);\n if (all.length <= 1) {\n return new Text(theme.fg('error', '✗') + theme.fg('dim', all[0] ? ` ${all[0]}` : ''), 0, 0);\n }\n const body = options.expanded ? all : all.slice(0, TRANSCRIPT_TASK_ROWS);\n return new Text([...body.map((line) => theme.fg('toolOutput', line)), theme.fg('error', '✗')].join('\\n'), 0, 0);\n }\n\n if (!details) return new Text(theme.fg('success', '✓'), 0, 0);\n\n /** Status rides on the task's own row, flushed right, rather than costing a line. */\n const row = (task: Task): JustifiedRow => ({\n left: formatOverlayTaskLine(task, theme, true),\n right: theme.fg(STATUS_COLOR[task.status], STATUS_LABEL[task.status]),\n });\n\n if (details.action === 'list') {\n const tasks = listedTasks(details);\n const shown = options.expanded ? tasks : tasks.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = tasks.length === 0 ? 'no tasks' : `${tasks.length} task${tasks.length === 1 ? '' : 's'}`;\n if (tasks.length > shown.length) summary += ' · ctrl+o';\n rows.push({ left: theme.fg('success', '✓') + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n if (details.action === 'clear') {\n return new Text(theme.fg('success', '✓') + theme.fg('dim', ' cleared'), 0, 0);\n }\n\n if (details.action === 'upsert') {\n const touched = upsertTasks(details);\n const failed = details.upsert?.failed ?? 0;\n // One applied task and nothing failed is the common case: keep the single\n // row it has always had rather than dressing it as a bulk report.\n if (touched.length === 1 && failed === 0) return new JustifiedRows([row(touched[0])]);\n\n const shown = options.expanded ? touched : touched.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = `${touched.length} applied`;\n if (failed > 0) summary += ` · ${failed} failed`;\n if (touched.length > shown.length) summary += ' · ctrl+o';\n // Failed entries get no row: there is no task to render, and their messages\n // are already in the text the model was given.\n const mark = failed > 0 ? theme.fg('warning', '!') : theme.fg('success', '✓');\n rows.push({ left: mark + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n const task = subjectTask(details);\n if (!task) return new Text(theme.fg('success', '✓'), 0, 0);\n return new JustifiedRows([row(task)]);\n}\n\nexport function renderTaskResult(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n return new TaskResultFrame(renderTaskResultBody(result, options, theme), theme);\n}\n"],"mappings":"sPAcA,MAAa,EAA2C,CACtD,QAAS,IACT,YAAa,IACb,UAAW,IACX,OAAQ,IACR,QAAS,GACX,EAIa,EAAoD,CAC/D,QAAS,MACT,YAAa,UACb,UAAW,UACX,OAAQ,QACR,QAAS,OACX,EAEa,EAA2C,CACtD,QAAS,UACT,YAAa,cACb,UAAW,YACX,OAAQ,SACR,QAAS,SACX,EAEM,EAAmD,CACvD,GAAG,EACH,UAAW,IACX,QAAS,EAAa,MACxB,EAEM,EAA4D,CAChE,GAAG,EACH,QAAS,OACX,EAIM,EAAkB,IAElB,EAA2B,gBAGjC,SAAS,EAAa,EAAqD,CAEzE,OADa,GAAa,QAAQ,EAA0B,EAAE,CAAC,CAAC,KAAK,GACtD,IAAA,EACjB,CAEA,MAAa,EAA2C,CACtD,OAAQ,IACR,OAAQ,IACR,IAAK,IACL,KAAM,IACN,MAAO,IACP,OAAQ,IACR,OAAQ,GACV,EAUA,SAAS,EAAY,EAA4C,CAI/D,OAHI,EAAM,SAAW,EAAU,EAAa,OACxC,EAAM,MAAO,GAAS,EAAK,KAAO,IAAA,EAAS,EAAU,IACrD,EAAM,MAAO,GAAS,EAAK,KAAO,IAAA,EAAS,EAAU,IAClD,EAAa,MACtB,CAEA,SAAgB,EAAmB,EAAoB,EAAsB,CAC3E,OAAO,EAAM,GAAG,EAAqB,GAAS,EAAqB,EAAO,CAC5E,CAEA,SAAgB,EAAgB,EAA4B,CAC1D,IAAM,EAAe,KAAK,IAAI,EAAG,KAAK,MAAM,EAAa,GAAuB,CAAC,EAC3E,EAAU,EAAe,GACzB,EAAe,KAAK,MAAM,EAAe,EAAkB,EACjE,GAAI,EAAe,GACjB,OAAO,IAAiB,EAAI,GAAG,EAAQ,GAAK,GAAG,EAAa,GAAG,OAAO,CAAO,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAElG,IAAM,EAAU,EAAe,GAE/B,MAAO,GADO,KAAK,MAAM,EAAe,EAC1B,EAAE,GAAG,OAAO,CAAO,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,CAAO,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,EAC1F,CAEA,SAAgB,EAAiB,EAAwB,CACvD,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,CAAM,CAAC,EAG5C,OAFI,EAAQ,EAAwB,OAAO,CAAK,EAC5C,EAAQ,IAA4B,IAAI,EAAQ,EAAA,CAAiB,QAAQ,CAAC,EAAE,GACzE,GAAG,KAAK,MAAM,EAAQ,CAAe,EAAE,EAChD,CAQA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAAQ,KAAK,IAAI,EACjB,EACQ,CACR,IAAM,EACJ,EAAK,SAAW,cACZ,SACA,EAAK,SAAW,aAAe,EAAK,SAAW,UAC7C,QACA,OACJ,EAAU,EAAM,GAAG,EAAc,EAAK,OAAO,GAC7C,EAAK,SAAW,aAAe,EAAK,SAAW,aACjD,EAAU,EAAM,cAAc,CAAO,GAGvC,IAAI,EAAO,EAAmB,EAAK,OAAQ,CAAK,EAC5C,IAAQ,GAAQ,IAAI,EAAM,GAAG,MAAO,IAAI,EAAK,IAAI,KACrD,GAAQ,IAAI,IAEZ,IAAM,EAAQ,EAAK,YAAY,MAC/B,GAAI,GAAS,EAAK,YAAY,QAAU,UAAW,CAGjD,IAAM,EAAkB,CAAC,EACrB,IACF,EAAM,KAAK,EAAM,GAAG,QAAS,IAAI,EAAgBA,EAAAA,kBAAkB,UAAU,EAAU,CAAK,CAAC,EAAE,EAAE,CAAC,EAC9F,EAAS,SAAW,IAAA,IAAW,EAAM,KAAK,EAAM,GAAG,QAAS,IAAI,EAAiB,EAAS,MAAM,EAAE,EAAE,CAAC,GAE3G,IAAM,EAAW,EAAK,WAAW,OAAS,EAAK,WAAW,UAC1D,EAAM,KAAK,EAAM,GAAG,IAAA,EAAcC,EAAAA,mBAAAA,CAAmB,CAAQ,EAAG,IAAI,EAAM,EAAE,CAAC,EAC7E,GAAQ,IAAI,EAAM,KAAK,EAAE,IACzB,IAAM,EAAW,EAAa,GAAU,WAAW,EAC/C,IAAU,GAAQ,IAAI,EAAM,GAAG,QAAS,KAAK,GAAU,IAC7D,MAAW,IACT,GAAQ,IAAI,EAAM,GAAG,QAAS,IAAI,EAAM,EAAE,KAS5C,OANI,EAAK,SAAW,eAAiB,EAAK,aACxC,GAAQ,IAAI,EAAM,GAAG,QAAS,IAAI,EAAK,WAAW,EAAE,KAElD,EAAK,WAAW,SAClB,GAAQ,IAAI,EAAM,GAAG,QAAS,KAAK,EAAK,UAAU,IAAK,GAAO,IAAI,GAAI,CAAC,CAAC,KAAK,GAAG,GAAG,KAE9E,CACT,CAGA,SAAgB,EAAsB,EAAY,EAAuB,CACvE,IAAM,EAAO,EAAK,SAAW,eAAiB,EAAK,WAAa,KAAK,EAAK,WAAW,GAAK,GACpF,EAAQ,EAAK,YAAY,MAAQ,KAAK,EAAK,WAAW,MAAM,GAAK,GACjE,EAAQ,EAAK,WAAW,OAAS,SAAS,EAAK,UAAU,IAAK,GAAO,IAAI,GAAI,CAAC,CAAC,KAAK,GAAG,IAAM,GACnG,MAAO,KAAK,EAAM,IAAI,EAAK,GAAG,GAAG,EAAK,UAAU,IAAO,IAAQ,GACjE,CAEA,SAAgB,EACd,EACA,EACA,EACW,CACX,IAAM,EAAQ,EAAK,SAAW,SAAY,EAAK,OAAS,CAAC,EAAK,CAAC,EACzD,EAAQ,EAAK,SAAW,SAAW,EAAY,CAAK,EAAK,EAAa,EAAK,SAAW,EAAK,OAC7F,GAAA,EAAOC,EAAAA,kBAAAA,CAAkB,OAAQ,GAAG,EAAM,GAAG,EAAK,SAAU,CAAK,EAErE,GAAI,EAAK,SAAW,SAAU,CAG5B,GAAI,EAAM,SAAW,EAAG,CACtB,IAAM,EAAO,EAAM,GACnB,GAAI,EAAK,KAAO,IAAA,GACV,EAAK,UAAS,GAAQ,IAAI,EAAM,GAAG,MAAO,EAAK,OAAO,SACrD,CACL,IAAM,EAAU,EAAM,KAAM,GAAS,EAAK,KAAO,EAAK,EAAE,CAAC,EAAE,SAAW,EAAK,SAAW,IAAI,EAAK,KAC/F,GAAQ,IAAI,EAAM,GAAG,SAAU,CAAO,GACxC,CACF,MAAW,EAAM,OAAS,IACxB,GAAQ,IAAI,EAAM,GAAG,MAAO,GAAG,EAAM,OAAO,OAAO,IAEvD,MAAO,GAAI,EAAK,SAAW,UAAY,EAAK,KAAO,IAAA,GAAW,CAC5D,IAAM,EAAU,EAAM,KAAM,GAAS,EAAK,KAAO,EAAK,EAAE,CAAC,EAAE,SAAW,IAAI,EAAK,KAC/E,GAAQ,IAAI,EAAM,GAAG,SAAU,CAAO,IAClC,EAAK,QAAO,GAAQ,IAAI,EAAM,GAAG,QAAS,KAAK,EAAK,OAAO,IACjE,MAAO,IAAK,EAAK,SAAW,OAAS,EAAK,SAAW,UAAY,EAAK,SAAW,WAAa,EAAK,KAAO,IAAA,GAAW,CACnH,IAAM,EAAU,EAAM,KAAM,GAAS,EAAK,KAAO,EAAK,EAAE,CAAC,EAAE,SAAW,IAAI,EAAK,KAC/E,GAAQ,IAAI,EAAM,GAAG,SAAU,CAAO,GACxC,MAAW,EAAK,SAAW,QAAU,EAAK,SACxC,GAAQ,IAAI,EAAM,GAAG,QAAS,EAAa,EAAK,OAAO,KAEzD,OAAO,IAAIC,EAAAA,aAAa,CAAI,CAC9B,CAmBA,IAAa,EAAb,KAAgD,CACjB,KAA7B,YAAY,EAAgD,CAA/B,KAAA,KAAA,CAAgC,CAM7D,YAAmB,CAAC,CAEpB,OAAO,EAAyB,CAC9B,OAAO,KAAK,KAAK,KAAK,CAAE,OAAM,WAAY,CACxC,GAAI,IAAU,IAAA,IAAa,EAAM,SAAW,EAAG,OAAA,EAAOC,EAAAA,gBAAAA,CAAgB,EAAM,CAAK,EACjF,IAAM,GAAA,EAAaC,EAAAA,aAAAA,CAAa,CAAK,EAC/B,EAAO,EAAQ,EAAa,EAClC,GAAI,GAAQ,EAAG,OAAA,EAAOD,EAAAA,gBAAAA,CAAgB,EAAM,CAAK,EACjD,IAAM,GAAA,EAAUA,EAAAA,gBAAAA,CAAgB,EAAM,CAAI,EACpC,EAAM,KAAK,IAAI,EAAe,GAAA,EAAQC,EAAAA,aAAAA,CAAa,CAAO,EAAI,CAAU,EAC9E,OAAO,EAAU,IAAI,OAAO,CAAG,EAAI,CACrC,CAAC,CACH,CACF,EAQM,EAAN,KAA2C,CAEtB,QACA,MAFnB,YACE,EACA,EACA,CAFiB,KAAA,QAAA,EACA,KAAA,MAAA,CAChB,CAEH,OAAO,EAAyB,CAC9B,GAAI,GAAS,EAAG,MAAO,CAAC,EACxB,GAAI,EAAQ,EAAG,OAAO,KAAK,QAAQ,OAAO,CAAK,EAE/C,IAAM,EAAe,EAAQ,EACvB,EAAU,KAAK,MAAM,GAAG,cAAe,IAAI,OAAO,CAAY,CAAC,EAC/D,EAAQ,KAAK,QAAQ,OAAO,CAAY,CAAC,CAAC,IAAK,GAAS,IAAI,GAAM,EACxE,MAAO,CAAC,IAAI,IAAW,GAAG,EAAO,EAAE,CACrC,CAEA,YAAmB,CACjB,KAAK,QAAQ,WAAW,CAC1B,CACF,EAcA,SAAS,EAAY,EAAgC,CACnD,OAAQ,EAAO,SAAW,CAAC,EAAA,CAAG,IAAK,GAAU,EAAM,MAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,CACxE,CAEA,SAAS,EAAc,EAAwB,CAC7C,OAAO,EAAK,MAAM;CAAI,CAAC,CAAC,OAAQ,GAAS,EAAK,KAAK,CAAC,CAAC,OAAS,CAAC,CACjE,CASA,SAAS,EAAY,EAA8B,CACjD,IAAM,EAAS,EAAQ,OACnB,EAAO,EAAQ,MAGnB,OAFI,EAAO,iBAAmB,KAAM,EAAO,EAAK,OAAQ,GAAS,EAAK,SAAW,SAAS,GACtF,EAAO,SAAQ,EAAO,EAAK,OAAQ,GAAS,EAAK,SAAW,EAAO,MAAM,GACtE,CACT,CAGA,SAAS,EAAY,EAAwC,CAC3D,IAAM,EAAS,EAAQ,OACnB,KAAO,KAAO,IAAA,GAClB,OAAO,EAAQ,MAAM,KAAM,GAAS,EAAK,KAAO,EAAO,EAAE,CAC3D,CAQA,SAAS,EAAY,EAA8B,CACjD,OAAQ,EAAQ,QAAQ,SAAW,CAAC,EAAA,CAAG,QAAS,GAAO,EAAQ,MAAM,KAAM,GAAS,EAAK,KAAO,CAAE,GAAK,CAAC,CAAC,CAC3G,CAEA,SAAS,EAAqB,EAAwB,EAA4B,EAAyB,CACzG,IAAM,EAAU,EAAO,QAGvB,GAAI,EAAQ,UAAW,CACrB,IAAM,EAAO,EAAc,EAAY,CAAM,CAAC,CAAC,CAAC,GAAG,EAAE,GAAK,UAC1D,OAAO,IAAIC,EAAAA,KAAK,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,IAAI,GAAM,EAAG,EAAG,CAAC,CAC9E,CAIA,GAAI,EAAO,UAAY,IAAQ,GAAS,QAAU,IAAA,GAAW,CAE3D,IAAM,EAAM,EADI,GAAS,OAAS,EAAY,CAAM,CACnB,EACjC,GAAI,EAAI,QAAU,EAChB,OAAO,IAAIA,EAAAA,KAAK,EAAM,GAAG,QAAS,GAAG,EAAI,EAAM,GAAG,MAAO,EAAI,GAAK,IAAI,EAAI,KAAO,EAAE,EAAG,EAAG,CAAC,EAE5F,IAAM,EAAO,EAAQ,SAAW,EAAM,EAAI,MAAM,EAAG,CAAoB,EACvE,OAAO,IAAIA,EAAAA,KAAK,CAAC,GAAG,EAAK,IAAK,GAAS,EAAM,GAAG,aAAc,CAAI,CAAC,EAAG,EAAM,GAAG,QAAS,GAAG,CAAC,CAAC,CAAC,KAAK;CAAI,EAAG,EAAG,CAAC,CAChH,CAEA,GAAI,CAAC,EAAS,OAAO,IAAIA,EAAAA,KAAK,EAAM,GAAG,UAAW,GAAG,EAAG,EAAG,CAAC,EAG5D,IAAM,EAAO,IAA8B,CACzC,KAAM,EAAsB,EAAM,EAAO,EAAI,EAC7C,MAAO,EAAM,GAAG,EAAa,EAAK,QAAS,EAAa,EAAK,OAAO,CACtE,GAEA,GAAI,EAAQ,SAAW,OAAQ,CAC7B,IAAM,EAAQ,EAAY,CAAO,EAC3B,EAAQ,EAAQ,SAAW,EAAQ,EAAM,MAAM,EAAG,CAAoB,EACtE,EAAuB,EAAM,IAAI,CAAG,EACtC,EAAU,EAAM,SAAW,EAAI,WAAa,GAAG,EAAM,OAAO,OAAO,EAAM,SAAW,EAAI,GAAK,MAGjG,OAFI,EAAM,OAAS,EAAM,SAAQ,GAAW,aAC5C,EAAK,KAAK,CAAE,KAAM,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,IAAI,GAAS,CAAE,CAAC,EACtE,IAAI,EAAc,CAAI,CAC/B,CAEA,GAAI,EAAQ,SAAW,QACrB,OAAO,IAAIA,EAAAA,KAAK,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,UAAU,EAAG,EAAG,CAAC,EAG9E,GAAI,EAAQ,SAAW,SAAU,CAC/B,IAAM,EAAU,EAAY,CAAO,EAC7B,EAAS,EAAQ,QAAQ,QAAU,EAGzC,GAAI,EAAQ,SAAW,GAAK,IAAW,EAAG,OAAO,IAAI,EAAc,CAAC,EAAI,EAAQ,EAAE,CAAC,CAAC,EAEpF,IAAM,EAAQ,EAAQ,SAAW,EAAU,EAAQ,MAAM,EAAG,CAAoB,EAC1E,EAAuB,EAAM,IAAI,CAAG,EACtC,EAAU,GAAG,EAAQ,OAAO,UAC5B,EAAS,IAAG,GAAW,MAAM,EAAO,UACpC,EAAQ,OAAS,EAAM,SAAQ,GAAW,aAG9C,IAAM,EAAO,EAAS,EAAI,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,UAAW,GAAG,EAE5E,OADA,EAAK,KAAK,CAAE,KAAM,EAAO,EAAM,GAAG,MAAO,IAAI,GAAS,CAAE,CAAC,EAClD,IAAI,EAAc,CAAI,CAC/B,CAEA,IAAM,EAAO,EAAY,CAAO,EAEhC,OADK,EACE,IAAI,EAAc,CAAC,EAAI,CAAI,CAAC,CAAC,EADlB,IAAIA,EAAAA,KAAK,EAAM,GAAG,UAAW,GAAG,EAAG,EAAG,CAAC,CAE3D,CAEA,SAAgB,EAAiB,EAAwB,EAA4B,EAAyB,CAC5G,OAAO,IAAI,EAAgB,EAAqB,EAAQ,EAAS,CAAK,EAAG,CAAK,CAChF"}
|
package/dist/tui/format.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Task, TaskAction, TaskMutationParams, TaskStatus } from "../services/store/types.cjs";
|
|
2
2
|
import { DelegationProgress } from "../services/delegation/manager.cjs";
|
|
3
3
|
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Component
|
|
4
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
5
5
|
//#region src/tui/format.d.ts
|
|
6
6
|
declare const STATUS_GLYPH: Record<TaskStatus, string>;
|
|
7
7
|
type TaskStatusColor = 'dim' | 'warning' | 'success' | 'error' | 'muted';
|
|
@@ -22,7 +22,7 @@ declare function formatOverlayTaskLine(task: Task, theme: Theme, showId: boolean
|
|
|
22
22
|
declare function formatCommandTaskLine(task: Task, glyph: string): string;
|
|
23
23
|
declare function renderTaskCall(args: TaskMutationParams & {
|
|
24
24
|
action: TaskAction;
|
|
25
|
-
}, theme: Theme, tasks: readonly Task[]):
|
|
25
|
+
}, theme: Theme, tasks: readonly Task[]): Component;
|
|
26
26
|
interface JustifiedRow {
|
|
27
27
|
left: string;
|
|
28
28
|
/** Pinned to the right edge. The subject is truncated before this is dropped. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.cts","names":[],"sources":["../../src/tui/format.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"format.d.cts","names":[],"sources":["../../src/tui/format.ts"],"mappings":";;;;;cAca,cAAc,OAAO;KAQ7B;cAEQ,cAAc,OAAO,YAAY;cAQjC,cAAc,OAAO;cA+BrB,cAAc,OAAO;iBAyBlB,mBAAmB,QAAQ,YAAY,OAAO;iBAI9C,gBAAgB;iBAYhB,iBAAiB;;;;;;;iBAajB,sBACd,MAAM,MACN,OAAO,OACP,iBACA,WAAW,oBACX,gBACA,aAAa,WAAW;;iBA6CV,sBAAsB,MAAM,MAAM;iBAOlC,eACd,MAAM;EAAuB,QAAQ;GACrC,OAAO,OACP,gBAAgB,SACf;UAqCc;EACf;;EAEA;;;;;;;;cASW,yBAAyB;mBACP;EAAA,YAAA,eAAe;;;;;EAM5C;EAEA,OAAO;;UAwCQ;EACf,UAAU;IAAQ;IAAc;;EAChC;EACA;;UAGe;EACf;EACA;;iBA+Gc,iBAAiB,QAAQ,gBAAgB,SAAS,mBAAmB,OAAO,QAAQ"}
|
package/dist/tui/format.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Task, TaskAction, TaskMutationParams, TaskStatus } from "../services/store/types.mjs";
|
|
2
2
|
import { DelegationProgress } from "../services/delegation/manager.mjs";
|
|
3
|
-
import { Component
|
|
3
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
4
4
|
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
//#region src/tui/format.d.ts
|
|
6
6
|
declare const STATUS_GLYPH: Record<TaskStatus, string>;
|
|
@@ -22,7 +22,7 @@ declare function formatOverlayTaskLine(task: Task, theme: Theme, showId: boolean
|
|
|
22
22
|
declare function formatCommandTaskLine(task: Task, glyph: string): string;
|
|
23
23
|
declare function renderTaskCall(args: TaskMutationParams & {
|
|
24
24
|
action: TaskAction;
|
|
25
|
-
}, theme: Theme, tasks: readonly Task[]):
|
|
25
|
+
}, theme: Theme, tasks: readonly Task[]): Component;
|
|
26
26
|
interface JustifiedRow {
|
|
27
27
|
left: string;
|
|
28
28
|
/** Pinned to the right edge. The subject is truncated before this is dropped. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.mts","names":[],"sources":["../../src/tui/format.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"format.d.mts","names":[],"sources":["../../src/tui/format.ts"],"mappings":";;;;;cAca,cAAc,OAAO;KAQ7B;cAEQ,cAAc,OAAO,YAAY;cAQjC,cAAc,OAAO;cA+BrB,cAAc,OAAO;iBAyBlB,mBAAmB,QAAQ,YAAY,OAAO;iBAI9C,gBAAgB;iBAYhB,iBAAiB;;;;;;;iBAajB,sBACd,MAAM,MACN,OAAO,OACP,iBACA,WAAW,oBACX,gBACA,aAAa,WAAW;;iBA6CV,sBAAsB,MAAM,MAAM;iBAOlC,eACd,MAAM;EAAuB,QAAQ;GACrC,OAAO,OACP,gBAAgB,SACf;UAqCc;EACf;;EAEA;;;;;;;;cASW,yBAAyB;mBACP;EAAA,YAAA,eAAe;;;;;EAM5C;EAEA,OAAO;;UAwCQ;EACf,UAAU;IAAQ;IAAc;;EAChC;EACA;;UAGe;EACf;EACA;;iBA+Gc,iBAAiB,QAAQ,gBAAgB,SAAS,mBAAmB,OAAO,QAAQ"}
|
package/dist/tui/format.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{DelegationManager as e}from"../services/delegation/manager.mjs";import{Text as t,truncateToWidth as n,visibleWidth as r}from"@earendil-works/pi-tui";import{agentIdentityColor as i}from"@agimon-ai/doompi-ui/theme";const
|
|
2
|
-
`).filter(e=>e.trim().length>0)}function
|
|
3
|
-
`),0,0)}if(!i)return new t(r.fg(`success`,`✓`),0,0);let a=e=>({left:
|
|
1
|
+
import{DelegationManager as e}from"../services/delegation/manager.mjs";import{Text as t,truncateToWidth as n,visibleWidth as r}from"@earendil-works/pi-tui";import{agentIdentityColor as i}from"@agimon-ai/doompi-ui/theme";import{DoomToolCall as a,renderToolHeading as o}from"@agimon-ai/doompi-ui/toolChrome";const s={pending:`○`,in_progress:`◐`,completed:`●`,failed:`✗`,deleted:`⊘`},c={pending:`dim`,in_progress:`warning`,completed:`success`,failed:`error`,deleted:`muted`},l={pending:`pending`,in_progress:`in progress`,completed:`completed`,failed:`failed`,deleted:`deleted`},u={...s,completed:`✓`,deleted:s.failed},d={...c,deleted:`error`},f=1e3,p=/^working:\s*/i;function m(e){return e?.replace(p,``).trim()||void 0}const h={upsert:`✎`,delete:`×`,get:`›`,list:`☰`,clear:`∅`,assign:`⇒`,cancel:`⊗`};function g(e){return e.length===0?h.upsert:e.every(e=>e.id===void 0)?`+`:e.every(e=>e.id!==void 0)?`→`:h.upsert}function _(e,t){return t.fg(d[e],u[e])}function v(e){let t=Math.max(0,Math.floor(e/1e3)),n=t%60,r=Math.floor(t/60);if(r<60)return r===0?`${n}s`:`${r}m${String(n).padStart(2,`0`)}s`;let i=r%60;return`${Math.floor(r/60)}h${String(i).padStart(2,`0`)}m${String(n).padStart(2,`0`)}s`}function y(e){let t=Math.max(0,Math.round(e));return t<f?String(t):t<1e4?`${(t/f).toFixed(1)}k`:`${Math.round(t/f)}k`}function b(t,n,r,a,o=Date.now(),s){let c=t.status===`in_progress`?`accent`:t.status===`completed`||t.status===`deleted`?`muted`:`text`,l=n.fg(c,t.subject);(t.status===`completed`||t.status===`deleted`)&&(l=n.strikethrough(l));let u=_(t.status,n);r&&(u+=` ${n.fg(`dim`,`#${t.id}`)}`),u+=` ${l}`;let d=t.delegation?.agent;if(d&&t.delegation?.state===`running`){let r=[];a&&(r.push(n.fg(`muted`,`[${v(e.elapsedMs(a,o))}]`)),a.tokens!==void 0&&r.push(n.fg(`muted`,`[${y(a.tokens)}]`)));let c=t.delegation.runId??t.delegation.requestId;r.push(n.fg(s??i(c),`[${d}]`)),u+=` ${r.join(``)}`;let l=m(a?.currentTool);l&&(u+=` ${n.fg(`muted`,`· ${l}`)}`)}else d&&(u+=` ${n.fg(`muted`,`[${d}]`)}`);return t.status===`in_progress`&&t.activeForm&&(u+=` ${n.fg(`muted`,`(${t.activeForm})`)}`),t.blockedBy?.length&&(u+=` ${n.fg(`muted`,`⛓ ${t.blockedBy.map(e=>`#${e}`).join(`,`)}`)}`),u}function x(e,t){let n=e.status===`in_progress`&&e.activeForm?` (${e.activeForm})`:``,r=e.delegation?.agent?` [${e.delegation.agent}]`:``,i=e.blockedBy?.length?` ⛓ ${e.blockedBy.map(e=>`#${e}`).join(`,`)}`:``;return` ${t} #${e.id} ${e.subject}${n}${r}${i}`}function S(e,t,n){let r=e.action===`upsert`?e.tasks??[]:[],i=e.action===`upsert`?g(r):h[e.action]??e.action,s=o(`task`,`${i} ${e.action}`,t);if(e.action===`upsert`){if(r.length===1){let e=r[0];if(e.id===void 0)e.subject&&(s+=` ${t.fg(`dim`,e.subject)}`);else{let r=n.find(t=>t.id===e.id)?.subject??e.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`}}else r.length>1&&(s+=` ${t.fg(`dim`,`${r.length} tasks`)}`)}else if(e.action===`assign`&&e.id!==void 0){let r=n.find(t=>t.id===e.id)?.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`,e.agent&&(s+=` ${t.fg(`muted`,`→ ${e.agent}`)}`)}else if((e.action===`get`||e.action===`delete`||e.action===`cancel`)&&e.id!==void 0){let r=n.find(t=>t.id===e.id)?.subject??`#${e.id}`;s+=` ${t.fg(`accent`,r)}`}else e.action===`list`&&e.status&&(s+=` ${t.fg(`muted`,l[e.status])}`);return new a(s)}var C=class{rows;constructor(e){this.rows=e}invalidate(){}render(e){return this.rows.map(({left:t,right:i})=>{if(i===void 0||i.length===0)return n(t,e);let a=r(i),o=e-a-2;if(o<=0)return n(t,e);let s=n(t,o),c=Math.max(2,e-r(s)-a);return s+` `.repeat(c)+i})}},w=class{content;theme;constructor(e,t){this.content=e,this.theme=t}render(e){if(e<=0)return[];if(e<3)return this.content.render(e);let t=e-2,n=this.theme.fg(`borderMuted`,`─`.repeat(t)),r=this.content.render(t).map(e=>` ${e}`);return[` ${n}`,...r,``]}invalidate(){this.content.invalidate()}};function T(e){return(e.content??[]).map(e=>e.text??``).join(``)}function E(e){return e.split(`
|
|
2
|
+
`).filter(e=>e.trim().length>0)}function D(e){let t=e.params,n=e.tasks;return t.includeDeleted!==!0&&(n=n.filter(e=>e.status!==`deleted`)),t.status&&(n=n.filter(e=>e.status===t.status)),n}function O(e){let t=e.params;if(t.id!==void 0)return e.tasks.find(e=>e.id===t.id)}function k(e){return(e.upsert?.applied??[]).flatMap(t=>e.tasks.find(e=>e.id===t)??[])}function A(e,n,r){let i=e.details;if(n.isPartial){let n=E(T(e)).at(-1)??`working`;return new t(r.fg(`warning`,`◐`)+r.fg(`dim`,` ${n}`),0,0)}if(e.isError===!0||i?.error!==void 0){let a=E(i?.error??T(e));if(a.length<=1)return new t(r.fg(`error`,`✗`)+r.fg(`dim`,a[0]?` ${a[0]}`:``),0,0);let o=n.expanded?a:a.slice(0,8);return new t([...o.map(e=>r.fg(`toolOutput`,e)),r.fg(`error`,`✗`)].join(`
|
|
3
|
+
`),0,0)}if(!i)return new t(r.fg(`success`,`✓`),0,0);let a=e=>({left:b(e,r,!0),right:r.fg(c[e.status],l[e.status])});if(i.action===`list`){let e=D(i),t=n.expanded?e:e.slice(0,8),o=t.map(a),s=e.length===0?`no tasks`:`${e.length} task${e.length===1?``:`s`}`;return e.length>t.length&&(s+=` · ctrl+o`),o.push({left:r.fg(`success`,`✓`)+r.fg(`dim`,` ${s}`)}),new C(o)}if(i.action===`clear`)return new t(r.fg(`success`,`✓`)+r.fg(`dim`,` cleared`),0,0);if(i.action===`upsert`){let e=k(i),t=i.upsert?.failed??0;if(e.length===1&&t===0)return new C([a(e[0])]);let o=n.expanded?e:e.slice(0,8),s=o.map(a),c=`${e.length} applied`;t>0&&(c+=` · ${t} failed`),e.length>o.length&&(c+=` · ctrl+o`);let l=t>0?r.fg(`warning`,`!`):r.fg(`success`,`✓`);return s.push({left:l+r.fg(`dim`,` ${c}`)}),new C(s)}let o=O(i);return o?new C([a(o)]):new t(r.fg(`success`,`✓`),0,0)}function j(e,t,n){return new w(A(e,t,n),n)}export{h as ACTION_GLYPH,C as JustifiedRows,c as STATUS_COLOR,s as STATUS_GLYPH,l as STATUS_LABEL,x as formatCommandTaskLine,v as formatElapsedMs,b as formatOverlayTaskLine,y as formatTokenCount,_ as overlayStatusGlyph,S as renderTaskCall,j as renderTaskResult};
|
|
4
4
|
//# sourceMappingURL=format.mjs.map
|
package/dist/tui/format.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.mjs","names":[],"sources":["../../src/tui/format.ts"],"sourcesContent":["import { agentIdentityColor } from '@agimon-ai/doompi-ui/theme';\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, Text, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { DelegationManager, type DelegationProgress } from '../services/delegation/manager.ts';\nimport type {\n Task,\n TaskAction,\n TaskDetails,\n TaskItemMutation,\n TaskMutationParams,\n TaskStatus,\n} from '../services/store/types.ts';\n\nexport const STATUS_GLYPH: Record<TaskStatus, string> = {\n pending: '○',\n in_progress: '◐',\n completed: '●',\n failed: '✗',\n deleted: '⊘',\n};\n\ntype TaskStatusColor = 'dim' | 'warning' | 'success' | 'error' | 'muted';\n\nexport const STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n pending: 'dim',\n in_progress: 'warning',\n completed: 'success',\n failed: 'error',\n deleted: 'muted',\n};\n\nexport const STATUS_LABEL: Record<TaskStatus, string> = {\n pending: 'pending',\n in_progress: 'in progress',\n completed: 'completed',\n failed: 'failed',\n deleted: 'deleted',\n};\n\nconst OVERLAY_STATUS_GLYPH: Record<TaskStatus, string> = {\n ...STATUS_GLYPH,\n completed: '✓',\n deleted: STATUS_GLYPH.failed,\n};\n\nconst OVERLAY_STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n ...STATUS_COLOR,\n deleted: 'error',\n};\nconst MILLISECONDS_PER_SECOND = 1000;\nconst SECONDS_PER_MINUTE = 60;\nconst MINUTES_PER_HOUR = 60;\nconst TOKENS_PER_KILO = 1000;\nconst TOKEN_DECIMAL_LIMIT = 10_000;\nconst REDUNDANT_WORKING_PREFIX = /^working:\\s*/i;\n\n/** Thinking text already communicates activity; keep only its useful content. */\nfunction progressText(currentTool: string | undefined): string | undefined {\n const text = currentTool?.replace(REDUNDANT_WORKING_PREFIX, '').trim();\n return text || undefined;\n}\n\nexport const ACTION_GLYPH: Record<TaskAction, string> = {\n upsert: '✎',\n delete: '×',\n get: '›',\n list: '☰',\n clear: '∅',\n assign: '⇒',\n cancel: '⊗',\n};\n\n/**\n * `upsert` is two operations behind one name, so a homogeneous batch narrows to\n * the glyph the operator already reads: `+` means new work appeared, `→` means\n * work moved. A mixed batch keeps the neutral write glyph.\n */\nconst UPSERT_CREATE_GLYPH = '+';\nconst UPSERT_UPDATE_GLYPH = '→';\n\nfunction upsertGlyph(items: readonly TaskItemMutation[]): string {\n if (items.length === 0) return ACTION_GLYPH.upsert;\n if (items.every((item) => item.id === undefined)) return UPSERT_CREATE_GLYPH;\n if (items.every((item) => item.id !== undefined)) return UPSERT_UPDATE_GLYPH;\n return ACTION_GLYPH.upsert;\n}\n\nexport function overlayStatusGlyph(status: TaskStatus, theme: Theme): string {\n return theme.fg(OVERLAY_STATUS_COLOR[status], OVERLAY_STATUS_GLYPH[status]);\n}\n\nexport function formatElapsedMs(durationMs: number): string {\n const totalSeconds = Math.max(0, Math.floor(durationMs / MILLISECONDS_PER_SECOND));\n const seconds = totalSeconds % SECONDS_PER_MINUTE;\n const totalMinutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);\n if (totalMinutes < MINUTES_PER_HOUR) {\n return totalMinutes === 0 ? `${seconds}s` : `${totalMinutes}m${String(seconds).padStart(2, '0')}s`;\n }\n const minutes = totalMinutes % MINUTES_PER_HOUR;\n const hours = Math.floor(totalMinutes / MINUTES_PER_HOUR);\n return `${hours}h${String(minutes).padStart(2, '0')}m${String(seconds).padStart(2, '0')}s`;\n}\n\nexport function formatTokenCount(tokens: number): string {\n const count = Math.max(0, Math.round(tokens));\n if (count < TOKENS_PER_KILO) return String(count);\n if (count < TOKEN_DECIMAL_LIMIT) return `${(count / TOKENS_PER_KILO).toFixed(1)}k`;\n return `${Math.round(count / TOKENS_PER_KILO)}k`;\n}\n\n/**\n * One task row for the persistent overlay.\n *\n * A delegated row carries the agent name and its current tool so the operator\n * can see that background work is progressing without opening a transcript.\n */\nexport function formatOverlayTaskLine(\n task: Task,\n theme: Theme,\n showId: boolean,\n progress?: DelegationProgress,\n nowMs = Date.now(),\n agentColor?: Parameters<Theme['fg']>[0],\n): string {\n const subjectColor =\n task.status === 'in_progress'\n ? 'accent'\n : task.status === 'completed' || task.status === 'deleted'\n ? 'muted'\n : 'text';\n let subject = theme.fg(subjectColor, task.subject);\n if (task.status === 'completed' || task.status === 'deleted') {\n subject = theme.strikethrough(subject);\n }\n\n let line = overlayStatusGlyph(task.status, theme);\n if (showId) line += ` ${theme.fg('dim', `#${task.id}`)}`;\n line += ` ${subject}`;\n\n const agent = task.delegation?.agent;\n if (agent && task.delegation?.state === 'running') {\n // No progress means no timing is known, so the chips are omitted rather than\n // reported as `[0s]`. The transcript renderer has no progress feed at all.\n const chips: string[] = [];\n if (progress) {\n chips.push(theme.fg('muted', `[${formatElapsedMs(DelegationManager.elapsedMs(progress, nowMs))}]`));\n if (progress.tokens !== undefined) chips.push(theme.fg('muted', `[${formatTokenCount(progress.tokens)}]`));\n }\n const identity = task.delegation.runId ?? task.delegation.requestId;\n chips.push(theme.fg(agentColor ?? agentIdentityColor(identity), `[${agent}]`));\n line += ` ${chips.join('')}`;\n const activity = progressText(progress?.currentTool);\n if (activity) line += ` ${theme.fg('muted', `· ${activity}`)}`;\n } else if (agent) {\n line += ` ${theme.fg('muted', `[${agent}]`)}`;\n }\n\n if (task.status === 'in_progress' && task.activeForm) {\n line += ` ${theme.fg('muted', `(${task.activeForm})`)}`;\n }\n if (task.blockedBy?.length) {\n line += ` ${theme.fg('muted', `⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}`)}`;\n }\n return line;\n}\n\n/** One task line for the `/tasks` command output. */\nexport function formatCommandTaskLine(task: Task, glyph: string): string {\n const form = task.status === 'in_progress' && task.activeForm ? ` (${task.activeForm})` : '';\n const agent = task.delegation?.agent ? ` [${task.delegation.agent}]` : '';\n const block = task.blockedBy?.length ? ` ⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}` : '';\n return ` ${glyph} #${task.id} ${task.subject}${form}${agent}${block}`;\n}\n\nexport function renderTaskCall(\n args: TaskMutationParams & { action: TaskAction },\n theme: Theme,\n tasks: readonly Task[],\n): Text {\n const items = args.action === 'upsert' ? (args.tasks ?? []) : [];\n const glyph = args.action === 'upsert' ? upsertGlyph(items) : (ACTION_GLYPH[args.action] ?? args.action);\n let text = theme.fg('toolTitle', theme.bold('task ')) + theme.fg('muted', glyph);\n\n if (args.action === 'upsert') {\n // One entry reads exactly as create or update did; a batch reads as a count\n // rather than a subject it would have to pick arbitrarily.\n if (items.length === 1) {\n const only = items[0];\n if (only.id === undefined) {\n if (only.subject) text += ` ${theme.fg('dim', only.subject)}`;\n } else {\n const subject = tasks.find((task) => task.id === only.id)?.subject ?? only.subject ?? `#${only.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n }\n } else if (items.length > 1) {\n text += ` ${theme.fg('dim', `${items.length} tasks`)}`;\n }\n } else if (args.action === 'assign' && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n if (args.agent) text += ` ${theme.fg('muted', `→ ${args.agent}`)}`;\n } else if ((args.action === 'get' || args.action === 'delete' || args.action === 'cancel') && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n } else if (args.action === 'list' && args.status) {\n text += ` ${theme.fg('muted', STATUS_LABEL[args.status])}`;\n }\n return new Text(text, 0, 0);\n}\n\n/** Task rows kept in a collapsed result, before `ctrl+o` reveals the rest. */\nconst TRANSCRIPT_TASK_ROWS = 8;\n/** Minimum gap between a row's subject and its right-aligned status. */\nconst STATUS_GUTTER = 2;\n\nexport interface JustifiedRow {\n left: string;\n /** Pinned to the right edge. The subject is truncated before this is dropped. */\n right?: string;\n}\n\n/**\n * Rows whose trailing text is flushed right.\n *\n * `Text` cannot do this: the status has to be placed against the viewport edge,\n * which is only known at render time.\n */\nexport class JustifiedRows implements Component {\n constructor(private readonly rows: readonly JustifiedRow[]) {}\n\n /**\n * Nothing to discard: the rows are themed by the caller and held verbatim, and\n * Pi rebuilds this component from `renderResult` whenever the theme changes.\n */\n invalidate(): void {}\n\n render(width: number): string[] {\n return this.rows.map(({ left, right }) => {\n if (right === undefined || right.length === 0) return truncateToWidth(left, width);\n const rightWidth = visibleWidth(right);\n const room = width - rightWidth - STATUS_GUTTER;\n if (room <= 0) return truncateToWidth(left, width);\n const subject = truncateToWidth(left, room);\n const gap = Math.max(STATUS_GUTTER, width - visibleWidth(subject) - rightWidth);\n return subject + ' '.repeat(gap) + right;\n });\n }\n}\n\nexport interface TaskResultLike {\n content?: Array<{ type: string; text?: string }>;\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface TaskResultOptions {\n expanded: boolean;\n isPartial: boolean;\n}\n\n/** Concatenated result text, which is all a thrown failure leaves behind. */\nfunction contentText(result: TaskResultLike): string {\n return (result.content ?? []).map((block) => block.text ?? '').join('');\n}\n\nfunction nonEmptyLines(text: string): string[] {\n return text.split('\\n').filter((line) => line.trim().length > 0);\n}\n\n/**\n * The rows `list` reported to the model.\n *\n * `details.tasks` is the whole post-mutation document, so the filters applied by\n * `formatContent` have to be applied again here or the transcript would disagree\n * with the text the model was given.\n */\nfunction listedTasks(details: TaskDetails): Task[] {\n const params = details.params as TaskMutationParams;\n let view = details.tasks;\n if (params.includeDeleted !== true) view = view.filter((task) => task.status !== 'deleted');\n if (params.status) view = view.filter((task) => task.status === params.status);\n return view;\n}\n\n/** The single task an id-bearing action acted on, so the transcript names it rather than a bare tick. */\nfunction subjectTask(details: TaskDetails): Task | undefined {\n const params = details.params as TaskMutationParams;\n if (params.id === undefined) return undefined;\n return details.tasks.find((task) => task.id === params.id);\n}\n\n/**\n * Tasks an upsert applied, resolved from the ids the reducer reported.\n *\n * The ids are read rather than guessed from the tail of the list: a batch\n * touches many rows, and a failed entry contributes none.\n */\nfunction upsertTasks(details: TaskDetails): Task[] {\n return (details.upsert?.applied ?? []).flatMap((id) => details.tasks.find((task) => task.id === id) ?? []);\n}\n\nexport function renderTaskResult(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n const details = result.details as TaskDetails | undefined;\n\n // Running. `assign` and `cancel` stream a placeholder before the run settles.\n if (options.isPartial) {\n const text = nonEmptyLines(contentText(result)).at(-1) ?? 'working';\n return new Text(theme.fg('warning', '◐') + theme.fg('dim', ` ${text}`), 0, 0);\n }\n\n // Failed. Every failure path throws, so Pi hands back error text and no\n // details; without this the block would render a green tick on a failure.\n if (result.isError === true || details?.error !== undefined) {\n const message = details?.error ?? contentText(result);\n const all = nonEmptyLines(message);\n if (all.length <= 1) {\n return new Text(theme.fg('error', '✗') + theme.fg('dim', all[0] ? ` ${all[0]}` : ''), 0, 0);\n }\n const body = options.expanded ? all : all.slice(0, TRANSCRIPT_TASK_ROWS);\n return new Text([...body.map((line) => theme.fg('toolOutput', line)), theme.fg('error', '✗')].join('\\n'), 0, 0);\n }\n\n if (!details) return new Text(theme.fg('success', '✓'), 0, 0);\n\n /** Status rides on the task's own row, flushed right, rather than costing a line. */\n const row = (task: Task): JustifiedRow => ({\n left: formatOverlayTaskLine(task, theme, true),\n right: theme.fg(STATUS_COLOR[task.status], STATUS_LABEL[task.status]),\n });\n\n if (details.action === 'list') {\n const tasks = listedTasks(details);\n const shown = options.expanded ? tasks : tasks.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = tasks.length === 0 ? 'no tasks' : `${tasks.length} task${tasks.length === 1 ? '' : 's'}`;\n if (tasks.length > shown.length) summary += ' · ctrl+o';\n rows.push({ left: theme.fg('success', '✓') + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n if (details.action === 'clear') {\n return new Text(theme.fg('success', '✓') + theme.fg('dim', ' cleared'), 0, 0);\n }\n\n if (details.action === 'upsert') {\n const touched = upsertTasks(details);\n const failed = details.upsert?.failed ?? 0;\n // One applied task and nothing failed is the common case: keep the single\n // row it has always had rather than dressing it as a bulk report.\n if (touched.length === 1 && failed === 0) return new JustifiedRows([row(touched[0])]);\n\n const shown = options.expanded ? touched : touched.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = `${touched.length} applied`;\n if (failed > 0) summary += ` · ${failed} failed`;\n if (touched.length > shown.length) summary += ' · ctrl+o';\n // Failed entries get no row: there is no task to render, and their messages\n // are already in the text the model was given.\n const mark = failed > 0 ? theme.fg('warning', '!') : theme.fg('success', '✓');\n rows.push({ left: mark + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n const task = subjectTask(details);\n if (!task) return new Text(theme.fg('success', '✓'), 0, 0);\n return new JustifiedRows([row(task)]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.mjs","names":[],"sources":["../../src/tui/format.ts"],"sourcesContent":["import { agentIdentityColor } from '@agimon-ai/doompi-ui/theme';\nimport { DoomToolCall, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, Text, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { DelegationManager, type DelegationProgress } from '../services/delegation/manager.ts';\nimport type {\n Task,\n TaskAction,\n TaskDetails,\n TaskItemMutation,\n TaskMutationParams,\n TaskStatus,\n} from '../services/store/types.ts';\n\nexport const STATUS_GLYPH: Record<TaskStatus, string> = {\n pending: '○',\n in_progress: '◐',\n completed: '●',\n failed: '✗',\n deleted: '⊘',\n};\n\ntype TaskStatusColor = 'dim' | 'warning' | 'success' | 'error' | 'muted';\n\nexport const STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n pending: 'dim',\n in_progress: 'warning',\n completed: 'success',\n failed: 'error',\n deleted: 'muted',\n};\n\nexport const STATUS_LABEL: Record<TaskStatus, string> = {\n pending: 'pending',\n in_progress: 'in progress',\n completed: 'completed',\n failed: 'failed',\n deleted: 'deleted',\n};\n\nconst OVERLAY_STATUS_GLYPH: Record<TaskStatus, string> = {\n ...STATUS_GLYPH,\n completed: '✓',\n deleted: STATUS_GLYPH.failed,\n};\n\nconst OVERLAY_STATUS_COLOR: Record<TaskStatus, TaskStatusColor> = {\n ...STATUS_COLOR,\n deleted: 'error',\n};\nconst MILLISECONDS_PER_SECOND = 1000;\nconst SECONDS_PER_MINUTE = 60;\nconst MINUTES_PER_HOUR = 60;\nconst TOKENS_PER_KILO = 1000;\nconst TOKEN_DECIMAL_LIMIT = 10_000;\nconst REDUNDANT_WORKING_PREFIX = /^working:\\s*/i;\n\n/** Thinking text already communicates activity; keep only its useful content. */\nfunction progressText(currentTool: string | undefined): string | undefined {\n const text = currentTool?.replace(REDUNDANT_WORKING_PREFIX, '').trim();\n return text || undefined;\n}\n\nexport const ACTION_GLYPH: Record<TaskAction, string> = {\n upsert: '✎',\n delete: '×',\n get: '›',\n list: '☰',\n clear: '∅',\n assign: '⇒',\n cancel: '⊗',\n};\n\n/**\n * `upsert` is two operations behind one name, so a homogeneous batch narrows to\n * the glyph the operator already reads: `+` means new work appeared, `→` means\n * work moved. A mixed batch keeps the neutral write glyph.\n */\nconst UPSERT_CREATE_GLYPH = '+';\nconst UPSERT_UPDATE_GLYPH = '→';\n\nfunction upsertGlyph(items: readonly TaskItemMutation[]): string {\n if (items.length === 0) return ACTION_GLYPH.upsert;\n if (items.every((item) => item.id === undefined)) return UPSERT_CREATE_GLYPH;\n if (items.every((item) => item.id !== undefined)) return UPSERT_UPDATE_GLYPH;\n return ACTION_GLYPH.upsert;\n}\n\nexport function overlayStatusGlyph(status: TaskStatus, theme: Theme): string {\n return theme.fg(OVERLAY_STATUS_COLOR[status], OVERLAY_STATUS_GLYPH[status]);\n}\n\nexport function formatElapsedMs(durationMs: number): string {\n const totalSeconds = Math.max(0, Math.floor(durationMs / MILLISECONDS_PER_SECOND));\n const seconds = totalSeconds % SECONDS_PER_MINUTE;\n const totalMinutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);\n if (totalMinutes < MINUTES_PER_HOUR) {\n return totalMinutes === 0 ? `${seconds}s` : `${totalMinutes}m${String(seconds).padStart(2, '0')}s`;\n }\n const minutes = totalMinutes % MINUTES_PER_HOUR;\n const hours = Math.floor(totalMinutes / MINUTES_PER_HOUR);\n return `${hours}h${String(minutes).padStart(2, '0')}m${String(seconds).padStart(2, '0')}s`;\n}\n\nexport function formatTokenCount(tokens: number): string {\n const count = Math.max(0, Math.round(tokens));\n if (count < TOKENS_PER_KILO) return String(count);\n if (count < TOKEN_DECIMAL_LIMIT) return `${(count / TOKENS_PER_KILO).toFixed(1)}k`;\n return `${Math.round(count / TOKENS_PER_KILO)}k`;\n}\n\n/**\n * One task row for the persistent overlay.\n *\n * A delegated row carries the agent name and its current tool so the operator\n * can see that background work is progressing without opening a transcript.\n */\nexport function formatOverlayTaskLine(\n task: Task,\n theme: Theme,\n showId: boolean,\n progress?: DelegationProgress,\n nowMs = Date.now(),\n agentColor?: Parameters<Theme['fg']>[0],\n): string {\n const subjectColor =\n task.status === 'in_progress'\n ? 'accent'\n : task.status === 'completed' || task.status === 'deleted'\n ? 'muted'\n : 'text';\n let subject = theme.fg(subjectColor, task.subject);\n if (task.status === 'completed' || task.status === 'deleted') {\n subject = theme.strikethrough(subject);\n }\n\n let line = overlayStatusGlyph(task.status, theme);\n if (showId) line += ` ${theme.fg('dim', `#${task.id}`)}`;\n line += ` ${subject}`;\n\n const agent = task.delegation?.agent;\n if (agent && task.delegation?.state === 'running') {\n // No progress means no timing is known, so the chips are omitted rather than\n // reported as `[0s]`. The transcript renderer has no progress feed at all.\n const chips: string[] = [];\n if (progress) {\n chips.push(theme.fg('muted', `[${formatElapsedMs(DelegationManager.elapsedMs(progress, nowMs))}]`));\n if (progress.tokens !== undefined) chips.push(theme.fg('muted', `[${formatTokenCount(progress.tokens)}]`));\n }\n const identity = task.delegation.runId ?? task.delegation.requestId;\n chips.push(theme.fg(agentColor ?? agentIdentityColor(identity), `[${agent}]`));\n line += ` ${chips.join('')}`;\n const activity = progressText(progress?.currentTool);\n if (activity) line += ` ${theme.fg('muted', `· ${activity}`)}`;\n } else if (agent) {\n line += ` ${theme.fg('muted', `[${agent}]`)}`;\n }\n\n if (task.status === 'in_progress' && task.activeForm) {\n line += ` ${theme.fg('muted', `(${task.activeForm})`)}`;\n }\n if (task.blockedBy?.length) {\n line += ` ${theme.fg('muted', `⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}`)}`;\n }\n return line;\n}\n\n/** One task line for the `/tasks` command output. */\nexport function formatCommandTaskLine(task: Task, glyph: string): string {\n const form = task.status === 'in_progress' && task.activeForm ? ` (${task.activeForm})` : '';\n const agent = task.delegation?.agent ? ` [${task.delegation.agent}]` : '';\n const block = task.blockedBy?.length ? ` ⛓ ${task.blockedBy.map((id) => `#${id}`).join(',')}` : '';\n return ` ${glyph} #${task.id} ${task.subject}${form}${agent}${block}`;\n}\n\nexport function renderTaskCall(\n args: TaskMutationParams & { action: TaskAction },\n theme: Theme,\n tasks: readonly Task[],\n): Component {\n const items = args.action === 'upsert' ? (args.tasks ?? []) : [];\n const glyph = args.action === 'upsert' ? upsertGlyph(items) : (ACTION_GLYPH[args.action] ?? args.action);\n let text = renderToolHeading('task', `${glyph} ${args.action}`, theme);\n\n if (args.action === 'upsert') {\n // One entry reads exactly as create or update did; a batch reads as a count\n // rather than a subject it would have to pick arbitrarily.\n if (items.length === 1) {\n const only = items[0];\n if (only.id === undefined) {\n if (only.subject) text += ` ${theme.fg('dim', only.subject)}`;\n } else {\n const subject = tasks.find((task) => task.id === only.id)?.subject ?? only.subject ?? `#${only.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n }\n } else if (items.length > 1) {\n text += ` ${theme.fg('dim', `${items.length} tasks`)}`;\n }\n } else if (args.action === 'assign' && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n if (args.agent) text += ` ${theme.fg('muted', `→ ${args.agent}`)}`;\n } else if ((args.action === 'get' || args.action === 'delete' || args.action === 'cancel') && args.id !== undefined) {\n const subject = tasks.find((task) => task.id === args.id)?.subject ?? `#${args.id}`;\n text += ` ${theme.fg('accent', subject)}`;\n } else if (args.action === 'list' && args.status) {\n text += ` ${theme.fg('muted', STATUS_LABEL[args.status])}`;\n }\n return new DoomToolCall(text);\n}\n\n/** Task rows kept in a collapsed result, before `ctrl+o` reveals the rest. */\nconst TRANSCRIPT_TASK_ROWS = 8;\n/** Minimum gap between a row's subject and its right-aligned status. */\nconst STATUS_GUTTER = 2;\n\nexport interface JustifiedRow {\n left: string;\n /** Pinned to the right edge. The subject is truncated before this is dropped. */\n right?: string;\n}\n\n/**\n * Rows whose trailing text is flushed right.\n *\n * `Text` cannot do this: the status has to be placed against the viewport edge,\n * which is only known at render time.\n */\nexport class JustifiedRows implements Component {\n constructor(private readonly rows: readonly JustifiedRow[]) {}\n\n /**\n * Nothing to discard: the rows are themed by the caller and held verbatim, and\n * Pi rebuilds this component from `renderResult` whenever the theme changes.\n */\n invalidate(): void {}\n\n render(width: number): string[] {\n return this.rows.map(({ left, right }) => {\n if (right === undefined || right.length === 0) return truncateToWidth(left, width);\n const rightWidth = visibleWidth(right);\n const room = width - rightWidth - STATUS_GUTTER;\n if (room <= 0) return truncateToWidth(left, width);\n const subject = truncateToWidth(left, room);\n const gap = Math.max(STATUS_GUTTER, width - visibleWidth(subject) - rightWidth);\n return subject + ' '.repeat(gap) + right;\n });\n }\n}\n\n/**\n * Self-owned task shell with the same structural divider as the bash tool.\n *\n * Task rows already carry precise per-task state, so the divider stays neutral\n * instead of competing with or appearing to animate between those states.\n */\nclass TaskResultFrame implements Component {\n constructor(\n private readonly content: Component,\n private readonly theme: Theme,\n ) {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < 3) return this.content.render(width);\n\n const contentWidth = width - 2;\n const divider = this.theme.fg('borderMuted', '─'.repeat(contentWidth));\n const lines = this.content.render(contentWidth).map((line) => ` ${line}`);\n return [` ${divider}`, ...lines, ''];\n }\n\n invalidate(): void {\n this.content.invalidate();\n }\n}\n\nexport interface TaskResultLike {\n content?: Array<{ type: string; text?: string }>;\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface TaskResultOptions {\n expanded: boolean;\n isPartial: boolean;\n}\n\n/** Concatenated result text, which is all a thrown failure leaves behind. */\nfunction contentText(result: TaskResultLike): string {\n return (result.content ?? []).map((block) => block.text ?? '').join('');\n}\n\nfunction nonEmptyLines(text: string): string[] {\n return text.split('\\n').filter((line) => line.trim().length > 0);\n}\n\n/**\n * The rows `list` reported to the model.\n *\n * `details.tasks` is the whole post-mutation document, so the filters applied by\n * `formatContent` have to be applied again here or the transcript would disagree\n * with the text the model was given.\n */\nfunction listedTasks(details: TaskDetails): Task[] {\n const params = details.params as TaskMutationParams;\n let view = details.tasks;\n if (params.includeDeleted !== true) view = view.filter((task) => task.status !== 'deleted');\n if (params.status) view = view.filter((task) => task.status === params.status);\n return view;\n}\n\n/** The single task an id-bearing action acted on, so the transcript names it rather than a bare tick. */\nfunction subjectTask(details: TaskDetails): Task | undefined {\n const params = details.params as TaskMutationParams;\n if (params.id === undefined) return undefined;\n return details.tasks.find((task) => task.id === params.id);\n}\n\n/**\n * Tasks an upsert applied, resolved from the ids the reducer reported.\n *\n * The ids are read rather than guessed from the tail of the list: a batch\n * touches many rows, and a failed entry contributes none.\n */\nfunction upsertTasks(details: TaskDetails): Task[] {\n return (details.upsert?.applied ?? []).flatMap((id) => details.tasks.find((task) => task.id === id) ?? []);\n}\n\nfunction renderTaskResultBody(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n const details = result.details as TaskDetails | undefined;\n\n // Running. `assign` and `cancel` stream a placeholder before the run settles.\n if (options.isPartial) {\n const text = nonEmptyLines(contentText(result)).at(-1) ?? 'working';\n return new Text(theme.fg('warning', '◐') + theme.fg('dim', ` ${text}`), 0, 0);\n }\n\n // Failed. Every failure path throws, so Pi hands back error text and no\n // details; without this the block would render a green tick on a failure.\n if (result.isError === true || details?.error !== undefined) {\n const message = details?.error ?? contentText(result);\n const all = nonEmptyLines(message);\n if (all.length <= 1) {\n return new Text(theme.fg('error', '✗') + theme.fg('dim', all[0] ? ` ${all[0]}` : ''), 0, 0);\n }\n const body = options.expanded ? all : all.slice(0, TRANSCRIPT_TASK_ROWS);\n return new Text([...body.map((line) => theme.fg('toolOutput', line)), theme.fg('error', '✗')].join('\\n'), 0, 0);\n }\n\n if (!details) return new Text(theme.fg('success', '✓'), 0, 0);\n\n /** Status rides on the task's own row, flushed right, rather than costing a line. */\n const row = (task: Task): JustifiedRow => ({\n left: formatOverlayTaskLine(task, theme, true),\n right: theme.fg(STATUS_COLOR[task.status], STATUS_LABEL[task.status]),\n });\n\n if (details.action === 'list') {\n const tasks = listedTasks(details);\n const shown = options.expanded ? tasks : tasks.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = tasks.length === 0 ? 'no tasks' : `${tasks.length} task${tasks.length === 1 ? '' : 's'}`;\n if (tasks.length > shown.length) summary += ' · ctrl+o';\n rows.push({ left: theme.fg('success', '✓') + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n if (details.action === 'clear') {\n return new Text(theme.fg('success', '✓') + theme.fg('dim', ' cleared'), 0, 0);\n }\n\n if (details.action === 'upsert') {\n const touched = upsertTasks(details);\n const failed = details.upsert?.failed ?? 0;\n // One applied task and nothing failed is the common case: keep the single\n // row it has always had rather than dressing it as a bulk report.\n if (touched.length === 1 && failed === 0) return new JustifiedRows([row(touched[0])]);\n\n const shown = options.expanded ? touched : touched.slice(0, TRANSCRIPT_TASK_ROWS);\n const rows: JustifiedRow[] = shown.map(row);\n let summary = `${touched.length} applied`;\n if (failed > 0) summary += ` · ${failed} failed`;\n if (touched.length > shown.length) summary += ' · ctrl+o';\n // Failed entries get no row: there is no task to render, and their messages\n // are already in the text the model was given.\n const mark = failed > 0 ? theme.fg('warning', '!') : theme.fg('success', '✓');\n rows.push({ left: mark + theme.fg('dim', ` ${summary}`) });\n return new JustifiedRows(rows);\n }\n\n const task = subjectTask(details);\n if (!task) return new Text(theme.fg('success', '✓'), 0, 0);\n return new JustifiedRows([row(task)]);\n}\n\nexport function renderTaskResult(result: TaskResultLike, options: TaskResultOptions, theme: Theme): Component {\n return new TaskResultFrame(renderTaskResultBody(result, options, theme), theme);\n}\n"],"mappings":"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"}
|
package/dist/tui/taskSpace.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../services/store/taskGraph.cjs"),t=require("../schemas/task.cjs"),n=require("../services/store/reducer.cjs"),r=require("./format.cjs"),i=require("./selectors.cjs");let a=require("@earendil-works/pi-tui"),o=require("node:os"),s=require("@agimon-ai/doompi-ui/components/doomOverlay");const
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../services/store/taskGraph.cjs"),t=require("../schemas/task.cjs"),n=require("../services/store/reducer.cjs"),r=require("./format.cjs"),i=require("./selectors.cjs");let a=require("@earendil-works/pi-tui"),o=require("node:os"),s=require("@agimon-ai/doompi-ui/components/doomOverlay"),c=require("@agimon-ai/doompi-ui/rendering");const l=s.DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;function u(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127)return!0}return!1}function d(e,t){let n=(0,o.homedir)(),r=n&&e.startsWith(n)?`~${e.slice(n.length)}`:e;if((0,a.visibleWidth)(r)<=t)return r;let i=r.split(`/`),s=i.pop()??r;for(;i.length>0;){let e=`${i[i.length-1]}/${s}`;if((0,a.visibleWidth)(`…/${e}`)>t)break;s=e,i.pop()}return(0,a.truncateToWidth)(`…/${s}`,t,``)}const f=`none`,p=[[`↑↓`,`select`],[`enter`,`edit subject`],[`s`,`status`],[`esc`,`close`]];function m(e,t){return(0,c.fitStyledLine)(e,t,`…`)}function h(e,t,n){let r=(0,a.visibleWidth)(t),i=Math.max(0,n-r-1);return m(e,i)+` `.repeat(Math.max(1,n-i-r))+m(t,r)}function g(e,t){if(t<=0)return[];let n=[],r=``;for(let i of e.split(/\s+/).filter(Boolean))r.length===0?r=i:r.length+1+i.length<=t?r+=` ${i}`:(n.push(r),r=i);return r.length>0&&n.push(r),n}var _=class extends s.DoomOverlay{options;done;unwatch;mode=`browse`;selected=0;statusIndex=0;draft=``;notice;bodyHeight=1;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r,this.unwatch=n.store.onExternalChange(()=>this.tui.requestRender())}tasks(){return i.visibleTasks(this.options.store.snapshot.tasks)}selectedTask(){let e=this.tasks();if(e.length!==0)return e[Math.min(this.selected,e.length-1)]}move(e){let t=this.tasks();t.length!==0&&(this.selected=Math.max(0,Math.min(t.length-1,this.selected+e)),this.tui.requestRender())}commit(e){this.options.store.mutate(t=>{let r=n.applyTaskMutation(t,`upsert`,{tasks:[e]});return n.isCommittingOp(r.op)?{document:r.document,value:r.op}:{value:r.op}}).then(e=>{let t=n.singleItemOutcome(e.value);this.notice=t?.kind===`failed`?t.message:void 0,this.tui.requestRender()}).catch(e=>{this.notice=e instanceof Error?e.message:String(e),this.tui.requestRender()})}handleStatusInput(e){let n=this.selectedTask();(0,a.matchesKey)(e,`up`)?this.statusIndex=Math.max(0,this.statusIndex-1):(0,a.matchesKey)(e,`down`)?this.statusIndex=Math.min(t.TASK_STATUSES.length-1,this.statusIndex+1):e===`s`?this.statusIndex=(this.statusIndex+1)%t.TASK_STATUSES.length:e===`\r`||e===`
|
|
2
2
|
`?(this.mode=`browse`,n&&this.commit({id:n.id,status:t.TASK_STATUSES[this.statusIndex]})):(0,a.matchesKey)(e,`escape`)&&(this.mode=`browse`),this.tui.requestRender()}handleSubjectInput(e){let t=this.selectedTask();e===`\r`||e===`
|
|
3
|
-
`?(this.mode=`browse`,t&&this.commit({id:t.id,subject:this.draft})):(0,a.matchesKey)(e,`escape`)?this.mode=`browse`:e===``?this.draft=this.draft.slice(0,-1):
|
|
4
|
-
`)&&(this.mode=`subject`,this.draft=n.subject,this.tui.requestRender())}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}listLines(n){let i=this.tasks(),o=this.theme.bold(`TASKS`),s=Math.max(0,n-(0,a.visibleWidth)(`TASKS`)-2),c=[`${o} ${this.theme.fg(`dim`,
|
|
3
|
+
`?(this.mode=`browse`,t&&this.commit({id:t.id,subject:this.draft})):(0,a.matchesKey)(e,`escape`)?this.mode=`browse`:e===``?this.draft=this.draft.slice(0,-1):u(e)||(this.draft+=e),this.tui.requestRender()}handleInput(e){if(this.mode===`status`){this.handleStatusInput(e);return}if(this.mode===`subject`){this.handleSubjectInput(e);return}if((0,a.matchesKey)(e,`escape`)||(0,a.matchesKey)(e,`ctrl+c`)){this.done(void 0);return}if((0,a.matchesKey)(e,`up`)){this.move(-1);return}if((0,a.matchesKey)(e,`down`)){this.move(1);return}let n=this.selectedTask();if(n){if(e===`s`){this.mode=`status`,this.statusIndex=Math.max(0,t.TASK_STATUSES.indexOf(n.status)),this.tui.requestRender();return}(e===`\r`||e===`
|
|
4
|
+
`)&&(this.mode=`subject`,this.draft=n.subject,this.tui.requestRender())}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}listLines(n){let i=this.tasks(),o=this.theme.bold(`TASKS`),s=Math.max(0,n-(0,a.visibleWidth)(`TASKS`)-2),c=[`${o} ${this.theme.fg(`dim`,d(this.options.store.storePath,s))}`,``];if(i.length===0)return c.push(this.theme.fg(`dim`,t.MSG_NO_TASKS)),c;let l=Math.max(1,Math.floor((this.bodyHeight-c.length)/2)),u=Math.max(0,Math.min(this.selected-l+1,Math.max(0,i.length-l)));for(let[t,a]of i.slice(u,u+l).entries()){let o=u+t===Math.min(this.selected,i.length-1),s=o?this.theme.fg(`accent`,`›`):` `,l=o?this.theme.bold(a.subject):a.subject,d=r.overlayStatusGlyph(a.status,this.theme),p=a.owner??a.delegation?.agent??f,h=a.status===`pending`&&e.isBlocked(this.options.store.snapshot.tasks,a)?this.theme.fg(`warning`,`blocked by ${(a.blockedBy??[]).map(e=>`#${e}`).join(`, `)}`):this.theme.fg(`dim`,r.STATUS_LABEL[a.status]),g=`${s} ${d} ${this.theme.fg(`dim`,`#${a.id}`)} ${l}`,_=`${` `.repeat(6)}${this.badge(p,`userMessageBg`,`muted`)} ${h}`,v=o?`selectedBg`:`userMessageBg`;for(let e of[g,_])c.push(this.theme.bg(v,m(e,n)))}return c}statusLines(e,n){if(this.mode!==`status`){let t=r.overlayStatusGlyph(e.status,this.theme);return[this.theme.fg(`dim`,`STATUS`),`${t} ${r.STATUS_LABEL[e.status]}`]}let i=[this.theme.fg(`dim`,`STATUS s cycles · enter commits`)];for(let[r,a]of t.TASK_STATUSES.entries()){let t=r===this.statusIndex,o=t?this.theme.fg(`accent`,`▸`):` `,s=t?this.theme.bold(this.theme.fg(`accent`,a)):this.theme.fg(`muted`,a),c=a===e.status?this.theme.fg(`dim`,`current`):``,l=h(` ${o} ${s}`,c,n);i.push(this.theme.bg(t?`selectedBg`:`userMessageBg`,l))}return i}subjectField(e,t){let n=this.mode===`subject`,r=n?`${this.draft}█`:e.subject;return[this.theme.fg(`dim`,n?`SUBJECT enter commits · esc cancels`:`SUBJECT`),this.theme.bg(n?`selectedBg`:`userMessageBg`,m(` ${r}`,t))]}detailLines(e){let t=this.selectedTask();if(!t)return[this.theme.fg(`dim`,`No task selected`)];let n=this.mode===`subject`||this.mode===`status`?this.badge(`EDIT`,`toolSuccessBg`,`warning`):``,r=[h(this.theme.bold(this.theme.fg(`accent`,`TASK #${t.id}`)),n,e),``];r.push(...this.subjectField(t,e)),r.push(``,...this.statusLines(t,e),``),t.activeForm&&r.push(this.theme.fg(`dim`,`ACTIVE FORM`),t.activeForm,``);let i=t.owner??t.delegation?.agent??f,a=t.delegation?.runId?this.theme.fg(`success`,`delegated run ${t.delegation.runId}`):``;return r.push(this.theme.fg(`dim`,`OWNER`),a?`${i} ${this.theme.fg(`dim`,`·`)} ${a}`:i,``),r.push(this.theme.fg(`dim`,`BLOCKED BY`),t.blockedBy?.length?t.blockedBy.map(e=>`#${e}`).join(` · `):f),t.description&&r.push(``,this.theme.fg(`dim`,`DESCRIPTION`),...g(t.description,e)),this.notice&&r.push(``,this.theme.fg(`error`,this.notice)),r}headerSummary(){let e=i.countTasks(this.options.store.snapshot.tasks),t=[`${e.completed}/${e.total} completed`];return e.inProgress>0&&t.push(`${e.inProgress} in progress`),e.blocked>0&&t.push(`${e.blocked} blocked`),e.failed>0&&t.push(`${e.failed} failed`),t.join(` · `)}getChrome(){return{title:`TASK SPACE`,accent:s.DOOM_OVERLAY_ACCENT,breadcrumb:`SPC › t › t / tasks`,headerRight:`${this.headerSummary()} · esc close`,footer:this.hintLine(p)}}renderBody(e,t){if(this.bodyHeight=t,e<48)return this.renderStacked(e,t);let n=Math.max(18,Math.floor((e-1)*.3333333333333333)),r=Math.max(1,e-n-1),i=Math.max(1,n-1),o=Math.max(1,r-1),s=this.listLines(i),c=this.detailLines(o),l=this.theme.fg(`borderMuted`,`│`);return Array.from({length:t},(t,n)=>{let r=`${m(s[n]??``,i)} ${l} ${m(c[n]??``,o)}`;return(0,a.truncateToWidth)(r,e,`…`)})}renderStacked(e,t){let n=Math.max(1,Math.floor((t-1)/2)),r=Math.max(0,t-n-1);this.bodyHeight=n;let i=this.listLines(e),a=this.detailLines(e);return[...i.slice(0,n),...Array.from({length:Math.max(0,n-i.length)},()=>``),this.theme.fg(`borderMuted`,`─`.repeat(e)),...a.slice(0,r)]}dispose(){this.unwatch()}};async function v(e,t){await e.ui.custom((e,n,r,i)=>new _(e,n,t,i),s.DOOM_FULLSCREEN_UI_OPTIONS)}exports.TASK_SPACE_OVERLAY_OPTIONS=l,exports.TaskSpaceComponent=_,exports.openTaskSpace=v;
|
|
5
5
|
//# sourceMappingURL=taskSpace.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","homedir","visibleWidth","truncateToWidth","DoomOverlay","visibleTasks","applyTaskMutation","isCommittingOp","singleItemOutcome","matchesKey","TASK_STATUSES","MSG_NO_TASKS","overlayStatusGlyph","isBlocked","STATUS_LABEL","countTasks","DOOM_OVERLAY_ACCENT"],"sources":["../../src/tui/taskSpace.ts"],"sourcesContent":["/**\n * Task Space: the `SPC t t` overlay.\n *\n * The task list and detail pane render from plain values so the whole surface\n * can be asserted as text without a live terminal.\n *\n * The overlay reads the store on every render rather than caching rows, so an\n * external write by a delegated child shows up without closing the overlay.\n *\n * Every commit it makes is a single-entry `upsert` that carries the selected\n * task's id. That id is what keeps creating a task a tool-only path: an entry\n * without one would create rather than be rejected.\n */\n\nimport { homedir } from 'node:os';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { MSG_NO_TASKS, TASK_STATUSES } from '../schemas/task.ts';\nimport { applyTaskMutation, isCommittingOp, type Op, singleItemOutcome } from '../services/store/reducer.ts';\nimport { isBlocked } from '../services/store/taskGraph.ts';\nimport type { TaskStore } from '../services/store/taskStore.ts';\nimport type { Task, TaskItemMutation } from '../services/store/types.ts';\nimport { overlayStatusGlyph, STATUS_LABEL } from './format.ts';\nimport { countTasks, visibleTasks } from './selectors.ts';\n\nexport const TASK_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nexport interface TaskSpaceOptions {\n store: TaskStore;\n}\n\ntype EditMode = 'browse' | 'status' | 'subject';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Store path for the list heading: home collapses to `~`, and an over-long path\n * loses leading segments rather than its tail, because the file name identifies\n * the store while `/var/folders/...` does not.\n */\nfunction storeLabel(storePath: string, width: number): string {\n const home = homedir();\n const abbreviated = home && storePath.startsWith(home) ? `~${storePath.slice(home.length)}` : storePath;\n if (visibleWidth(abbreviated) <= width) return abbreviated;\n\n const segments = abbreviated.split('/');\n let tail = segments.pop() ?? abbreviated;\n while (segments.length > 0) {\n const candidate = `${segments[segments.length - 1]}/${tail}`;\n if (visibleWidth(`…/${candidate}`) > width) break;\n tail = candidate;\n segments.pop();\n }\n return truncateToWidth(`…/${tail}`, width, '');\n}\n\n/** Neither palette name is exported, so take both from the theme's methods. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\ntype ThemeColor = Parameters<Theme['fg']>[0];\n\n/** Each task occupies a heading row and a metadata row beneath it. */\nconst ROWS_PER_TASK = 2;\n/** Hanging indent for the metadata row: clears marker, glyph and id. */\nconst META_INDENT = 6;\n\nconst MIN_WIDTH = 48;\n/** A third for the list, two thirds for the task being read or edited. */\nconst LIST_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst SELECTION_MARKER = '›';\nconst CURRENT_MARKER = '▸';\nconst CURSOR_BLOCK = '█';\nconst ELLIPSIS = '…';\nconst NO_VALUE = 'none';\n\nconst TITLE = 'TASK SPACE';\nconst BREADCRUMB = 'SPC › t › t / tasks';\n/** Key/label pairs rendered as capped keys, matching the mockup's footer. */\nconst LIST_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'edit subject'],\n ['s', 'status'],\n ['esc', 'close'],\n];\nconst LIST_HEADER_RIGHT = 'esc close';\nconst STATUS_HINT = 'STATUS s cycles · enter commits';\nconst SUBJECT_HINT = 'SUBJECT enter commits · esc cancels';\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/**\n * Task Space overlay component.\n *\n * Rendering is pure: `render(width)` reads the live store and returns lines with\n * no side effects, so the fidelity job can capture it and unit tests can assert on it.\n */\nexport class TaskSpaceComponent extends DoomOverlay {\n private readonly options: TaskSpaceOptions;\n private readonly done: (result: undefined) => void;\n private readonly unwatch: () => void;\n\n private mode: EditMode = 'browse';\n private selected = 0;\n private statusIndex = 0;\n private draft = '';\n private notice: string | undefined;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, options: TaskSpaceOptions, done: (result: undefined) => void) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.unwatch = options.store.onExternalChange(() => this.tui.requestRender());\n }\n\n private tasks(): Task[] {\n return visibleTasks(this.options.store.snapshot.tasks);\n }\n\n private selectedTask(): Task | undefined {\n const tasks = this.tasks();\n if (tasks.length === 0) return undefined;\n return tasks[Math.min(this.selected, tasks.length - 1)];\n }\n\n private move(delta: number): void {\n const tasks = this.tasks();\n if (tasks.length === 0) return;\n this.selected = Math.max(0, Math.min(tasks.length - 1, this.selected + delta));\n this.tui.requestRender();\n }\n\n /** Commits through the same reducer the task tool uses, so both paths share every invariant. */\n private commit(item: TaskItemMutation): void {\n this.options.store\n .mutate<Op>((document) => {\n const result = applyTaskMutation(document, 'upsert', { tasks: [item] });\n // A rejected mutation must not reach the writer: committing it would\n // bump `rev` for an edit that never happened.\n if (!isCommittingOp(result.op)) return { value: result.op };\n return { document: result.document, value: result.op };\n })\n .then((outcome) => {\n // The reducer's message is unprefixed, so the notice reads\n // \"subject must not be blank\" rather than \"item[0] failed: ...\".\n const single = singleItemOutcome(outcome.value);\n this.notice = single?.kind === 'failed' ? single.message : undefined;\n this.tui.requestRender();\n })\n .catch((error: unknown) => {\n // Shown in the overlay rather than swallowed: a rejected write means the\n // edit the operator just made never landed.\n this.notice = error instanceof Error ? error.message : String(error);\n this.tui.requestRender();\n });\n }\n\n private handleStatusInput(data: string): void {\n const task = this.selectedTask();\n if (matchesKey(data, 'up')) this.statusIndex = Math.max(0, this.statusIndex - 1);\n else if (matchesKey(data, 'down')) this.statusIndex = Math.min(TASK_STATUSES.length - 1, this.statusIndex + 1);\n else if (data === 's') this.statusIndex = (this.statusIndex + 1) % TASK_STATUSES.length;\n else if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, status: TASK_STATUSES[this.statusIndex] });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private handleSubjectInput(data: string): void {\n const task = this.selectedTask();\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, subject: this.draft });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n else if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'status') return this.handleStatusInput(data);\n if (this.mode === 'subject') return this.handleSubjectInput(data);\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up')) return this.move(-1);\n if (matchesKey(data, 'down')) return this.move(1);\n\n const task = this.selectedTask();\n if (!task) return;\n if (data === 's') {\n this.mode = 'status';\n this.statusIndex = Math.max(0, TASK_STATUSES.indexOf(task.status));\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'subject';\n this.draft = task.subject;\n this.tui.requestRender();\n }\n }\n\n /**\n * A padded, background-filled chip. Foreground is applied inside the fill so\n * the colour closes with it, rather than a reset stripping the background\n * partway through the label.\n */\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] edit subject`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n private listLines(width: number): string[] {\n const tasks = this.tasks();\n const label = this.theme.bold('TASKS');\n const pathWidth = Math.max(0, width - visibleWidth('TASKS') - 2);\n const lines = [`${label} ${this.theme.fg('dim', storeLabel(this.options.store.storePath, pathWidth))}`, ''];\n if (tasks.length === 0) {\n lines.push(this.theme.fg('dim', MSG_NO_TASKS));\n return lines;\n }\n\n // Two rows per task: at a third of the width a single row left the subject\n // with a handful of columns once owner and state took their share.\n const budget = Math.max(1, Math.floor((this.bodyHeight - lines.length) / ROWS_PER_TASK));\n const start = Math.max(0, Math.min(this.selected - budget + 1, Math.max(0, tasks.length - budget)));\n for (const [offset, task] of tasks.slice(start, start + budget).entries()) {\n const index = start + offset;\n const active = index === Math.min(this.selected, tasks.length - 1);\n const marker = active ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const subject = active ? this.theme.bold(task.subject) : task.subject;\n const glyph = overlayStatusGlyph(task.status, this.theme);\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const blocked = task.status === 'pending' && isBlocked(this.options.store.snapshot.tasks, task);\n const state = blocked\n ? this.theme.fg('warning', `blocked by ${(task.blockedBy ?? []).map((id) => `#${id}`).join(', ')}`)\n : this.theme.fg('dim', STATUS_LABEL[task.status]);\n // Subject owns the first row outright; the metadata hangs under it.\n const heading = `${marker} ${glyph} ${this.theme.fg('dim', `#${task.id}`)} ${subject}`;\n const meta = `${' '.repeat(META_INDENT)}${this.badge(owner, 'userMessageBg', 'muted')} ${state}`;\n // Every task sits on a band, the selected one a shade lighter, as in the\n // mockup; the marker stays so selection survives themes with no background.\n const background: ThemeBg = active ? 'selectedBg' : 'userMessageBg';\n for (const row of [heading, meta]) {\n lines.push(this.theme.bg(background, fit(row, width)));\n }\n }\n return lines;\n }\n\n private statusLines(task: Task, width: number): string[] {\n if (this.mode !== 'status') {\n const glyph = overlayStatusGlyph(task.status, this.theme);\n return [this.theme.fg('dim', 'STATUS'), `${glyph} ${STATUS_LABEL[task.status]}`];\n }\n // Editing draws the picker as a filled panel, so the whole option list reads\n // as one surface rather than five loose rows.\n const lines = [this.theme.fg('dim', STATUS_HINT)];\n for (const [index, status] of TASK_STATUSES.entries()) {\n const selected = index === this.statusIndex;\n const marker = selected ? this.theme.fg('accent', CURRENT_MARKER) : ' ';\n const label = selected ? this.theme.bold(this.theme.fg('accent', status)) : this.theme.fg('muted', status);\n const current = status === task.status ? this.theme.fg('dim', 'current') : '';\n const row = rightAligned(` ${marker} ${label}`, current, width);\n lines.push(this.theme.bg(selected ? 'selectedBg' : 'userMessageBg', row));\n }\n return lines;\n }\n\n /** The subject as an input: a filled field with a caret, as in the mockup. */\n private subjectField(task: Task, width: number): string[] {\n const editing = this.mode === 'subject';\n const value = editing ? `${this.draft}${CURSOR_BLOCK}` : task.subject;\n return [\n this.theme.fg('dim', editing ? SUBJECT_HINT : 'SUBJECT'),\n this.theme.bg(editing ? 'selectedBg' : 'userMessageBg', fit(` ${value}`, width)),\n ];\n }\n\n private detailLines(width: number): string[] {\n const task = this.selectedTask();\n if (!task) return [this.theme.fg('dim', 'No task selected')];\n\n const editing = this.mode === 'subject' || this.mode === 'status';\n const badge = editing ? this.badge('EDIT', 'toolSuccessBg', 'warning') : '';\n const lines = [rightAligned(this.theme.bold(this.theme.fg('accent', `TASK #${task.id}`)), badge, width), ''];\n lines.push(...this.subjectField(task, width));\n lines.push('', ...this.statusLines(task, width), '');\n\n if (task.activeForm) lines.push(this.theme.fg('dim', 'ACTIVE FORM'), task.activeForm, '');\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const delegated = task.delegation?.runId ? this.theme.fg('success', `delegated run ${task.delegation.runId}`) : '';\n lines.push(\n this.theme.fg('dim', 'OWNER'),\n delegated ? `${owner} ${this.theme.fg('dim', '·')} ${delegated}` : owner,\n '',\n );\n lines.push(\n this.theme.fg('dim', 'BLOCKED BY'),\n task.blockedBy?.length ? task.blockedBy.map((id) => `#${id}`).join(' · ') : NO_VALUE,\n );\n if (task.description) lines.push('', this.theme.fg('dim', 'DESCRIPTION'), ...wrap(task.description, width));\n if (this.notice) lines.push('', this.theme.fg('error', this.notice));\n return lines;\n }\n\n private headerSummary(): string {\n const counts = countTasks(this.options.store.snapshot.tasks);\n const summary = [`${counts.completed}/${counts.total} completed`];\n if (counts.inProgress > 0) summary.push(`${counts.inProgress} in progress`);\n if (counts.blocked > 0) summary.push(`${counts.blocked} blocked`);\n if (counts.failed > 0) summary.push(`${counts.failed} failed`);\n return summary.join(' · ');\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${this.headerSummary()} · ${LIST_HEADER_RIGHT}`,\n footer: this.hintLine(LIST_HINTS),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * LIST_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n // Each column gives up one column to the gutter, so content never abuts the\n // divider: a long store path used to run straight into the detail heading.\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.listLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.listLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n dispose(): void {\n this.unwatch();\n }\n}\n\nexport async function openTaskSpace(ctx: ExtensionContext, options: TaskSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new TaskSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"taskSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","homedir","visibleWidth","truncateToWidth","fitStyledLine","DoomOverlay","visibleTasks","applyTaskMutation","isCommittingOp","singleItemOutcome","matchesKey","TASK_STATUSES","MSG_NO_TASKS","overlayStatusGlyph","isBlocked","STATUS_LABEL","countTasks","DOOM_OVERLAY_ACCENT"],"sources":["../../src/tui/taskSpace.ts"],"sourcesContent":["/**\n * Task Space: the `SPC t t` overlay.\n *\n * The task list and detail pane render from plain values so the whole surface\n * can be asserted as text without a live terminal.\n *\n * The overlay reads the store on every render rather than caching rows, so an\n * external write by a delegated child shows up without closing the overlay.\n *\n * Every commit it makes is a single-entry `upsert` that carries the selected\n * task's id. That id is what keeps creating a task a tool-only path: an entry\n * without one would create rather than be rejected.\n */\n\nimport { homedir } from 'node:os';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport { fitStyledLine } from '@agimon-ai/doompi-ui/rendering';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { MSG_NO_TASKS, TASK_STATUSES } from '../schemas/task.ts';\nimport { applyTaskMutation, isCommittingOp, type Op, singleItemOutcome } from '../services/store/reducer.ts';\nimport { isBlocked } from '../services/store/taskGraph.ts';\nimport type { TaskStore } from '../services/store/taskStore.ts';\nimport type { Task, TaskItemMutation } from '../services/store/types.ts';\nimport { overlayStatusGlyph, STATUS_LABEL } from './format.ts';\nimport { countTasks, visibleTasks } from './selectors.ts';\n\nexport const TASK_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nexport interface TaskSpaceOptions {\n store: TaskStore;\n}\n\ntype EditMode = 'browse' | 'status' | 'subject';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Store path for the list heading: home collapses to `~`, and an over-long path\n * loses leading segments rather than its tail, because the file name identifies\n * the store while `/var/folders/...` does not.\n */\nfunction storeLabel(storePath: string, width: number): string {\n const home = homedir();\n const abbreviated = home && storePath.startsWith(home) ? `~${storePath.slice(home.length)}` : storePath;\n if (visibleWidth(abbreviated) <= width) return abbreviated;\n\n const segments = abbreviated.split('/');\n let tail = segments.pop() ?? abbreviated;\n while (segments.length > 0) {\n const candidate = `${segments[segments.length - 1]}/${tail}`;\n if (visibleWidth(`…/${candidate}`) > width) break;\n tail = candidate;\n segments.pop();\n }\n return truncateToWidth(`…/${tail}`, width, '');\n}\n\n/** Neither palette name is exported, so take both from the theme's methods. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\ntype ThemeColor = Parameters<Theme['fg']>[0];\n\n/** Each task occupies a heading row and a metadata row beneath it. */\nconst ROWS_PER_TASK = 2;\n/** Hanging indent for the metadata row: clears marker, glyph and id. */\nconst META_INDENT = 6;\n\nconst MIN_WIDTH = 48;\n/** A third for the list, two thirds for the task being read or edited. */\nconst LIST_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst SELECTION_MARKER = '›';\nconst CURRENT_MARKER = '▸';\nconst CURSOR_BLOCK = '█';\nconst ELLIPSIS = '…';\nconst NO_VALUE = 'none';\n\nconst TITLE = 'TASK SPACE';\nconst BREADCRUMB = 'SPC › t › t / tasks';\n/** Key/label pairs rendered as capped keys, matching the mockup's footer. */\nconst LIST_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'edit subject'],\n ['s', 'status'],\n ['esc', 'close'],\n];\nconst LIST_HEADER_RIGHT = 'esc close';\nconst STATUS_HINT = 'STATUS s cycles · enter commits';\nconst SUBJECT_HINT = 'SUBJECT enter commits · esc cancels';\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/**\n * Task Space overlay component.\n *\n * Rendering is pure: `render(width)` reads the live store and returns lines with\n * no side effects, so the fidelity job can capture it and unit tests can assert on it.\n */\nexport class TaskSpaceComponent extends DoomOverlay {\n private readonly options: TaskSpaceOptions;\n private readonly done: (result: undefined) => void;\n private readonly unwatch: () => void;\n\n private mode: EditMode = 'browse';\n private selected = 0;\n private statusIndex = 0;\n private draft = '';\n private notice: string | undefined;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, options: TaskSpaceOptions, done: (result: undefined) => void) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.unwatch = options.store.onExternalChange(() => this.tui.requestRender());\n }\n\n private tasks(): Task[] {\n return visibleTasks(this.options.store.snapshot.tasks);\n }\n\n private selectedTask(): Task | undefined {\n const tasks = this.tasks();\n if (tasks.length === 0) return undefined;\n return tasks[Math.min(this.selected, tasks.length - 1)];\n }\n\n private move(delta: number): void {\n const tasks = this.tasks();\n if (tasks.length === 0) return;\n this.selected = Math.max(0, Math.min(tasks.length - 1, this.selected + delta));\n this.tui.requestRender();\n }\n\n /** Commits through the same reducer the task tool uses, so both paths share every invariant. */\n private commit(item: TaskItemMutation): void {\n this.options.store\n .mutate<Op>((document) => {\n const result = applyTaskMutation(document, 'upsert', { tasks: [item] });\n // A rejected mutation must not reach the writer: committing it would\n // bump `rev` for an edit that never happened.\n if (!isCommittingOp(result.op)) return { value: result.op };\n return { document: result.document, value: result.op };\n })\n .then((outcome) => {\n // The reducer's message is unprefixed, so the notice reads\n // \"subject must not be blank\" rather than \"item[0] failed: ...\".\n const single = singleItemOutcome(outcome.value);\n this.notice = single?.kind === 'failed' ? single.message : undefined;\n this.tui.requestRender();\n })\n .catch((error: unknown) => {\n // Shown in the overlay rather than swallowed: a rejected write means the\n // edit the operator just made never landed.\n this.notice = error instanceof Error ? error.message : String(error);\n this.tui.requestRender();\n });\n }\n\n private handleStatusInput(data: string): void {\n const task = this.selectedTask();\n if (matchesKey(data, 'up')) this.statusIndex = Math.max(0, this.statusIndex - 1);\n else if (matchesKey(data, 'down')) this.statusIndex = Math.min(TASK_STATUSES.length - 1, this.statusIndex + 1);\n else if (data === 's') this.statusIndex = (this.statusIndex + 1) % TASK_STATUSES.length;\n else if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, status: TASK_STATUSES[this.statusIndex] });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private handleSubjectInput(data: string): void {\n const task = this.selectedTask();\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, subject: this.draft });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n else if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'status') {\n this.handleStatusInput(data);\n return;\n }\n if (this.mode === 'subject') {\n this.handleSubjectInput(data);\n return;\n }\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up')) {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down')) {\n this.move(1);\n return;\n }\n\n const task = this.selectedTask();\n if (!task) return;\n if (data === 's') {\n this.mode = 'status';\n this.statusIndex = Math.max(0, TASK_STATUSES.indexOf(task.status));\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'subject';\n this.draft = task.subject;\n this.tui.requestRender();\n }\n }\n\n /**\n * A padded, background-filled chip. Foreground is applied inside the fill so\n * the colour closes with it, rather than a reset stripping the background\n * partway through the label.\n */\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] edit subject`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n private listLines(width: number): string[] {\n const tasks = this.tasks();\n const label = this.theme.bold('TASKS');\n const pathWidth = Math.max(0, width - visibleWidth('TASKS') - 2);\n const lines = [`${label} ${this.theme.fg('dim', storeLabel(this.options.store.storePath, pathWidth))}`, ''];\n if (tasks.length === 0) {\n lines.push(this.theme.fg('dim', MSG_NO_TASKS));\n return lines;\n }\n\n // Two rows per task: at a third of the width a single row left the subject\n // with a handful of columns once owner and state took their share.\n const budget = Math.max(1, Math.floor((this.bodyHeight - lines.length) / ROWS_PER_TASK));\n const start = Math.max(0, Math.min(this.selected - budget + 1, Math.max(0, tasks.length - budget)));\n for (const [offset, task] of tasks.slice(start, start + budget).entries()) {\n const index = start + offset;\n const active = index === Math.min(this.selected, tasks.length - 1);\n const marker = active ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const subject = active ? this.theme.bold(task.subject) : task.subject;\n const glyph = overlayStatusGlyph(task.status, this.theme);\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const blocked = task.status === 'pending' && isBlocked(this.options.store.snapshot.tasks, task);\n const state = blocked\n ? this.theme.fg('warning', `blocked by ${(task.blockedBy ?? []).map((id) => `#${id}`).join(', ')}`)\n : this.theme.fg('dim', STATUS_LABEL[task.status]);\n // Subject owns the first row outright; the metadata hangs under it.\n const heading = `${marker} ${glyph} ${this.theme.fg('dim', `#${task.id}`)} ${subject}`;\n const meta = `${' '.repeat(META_INDENT)}${this.badge(owner, 'userMessageBg', 'muted')} ${state}`;\n // Every task sits on a band, the selected one a shade lighter, as in the\n // mockup; the marker stays so selection survives themes with no background.\n const background: ThemeBg = active ? 'selectedBg' : 'userMessageBg';\n for (const row of [heading, meta]) {\n lines.push(this.theme.bg(background, fitRow(row, width)));\n }\n }\n return lines;\n }\n\n private statusLines(task: Task, width: number): string[] {\n if (this.mode !== 'status') {\n const glyph = overlayStatusGlyph(task.status, this.theme);\n return [this.theme.fg('dim', 'STATUS'), `${glyph} ${STATUS_LABEL[task.status]}`];\n }\n // Editing draws the picker as a filled panel, so the whole option list reads\n // as one surface rather than five loose rows.\n const lines = [this.theme.fg('dim', STATUS_HINT)];\n for (const [index, status] of TASK_STATUSES.entries()) {\n const selected = index === this.statusIndex;\n const marker = selected ? this.theme.fg('accent', CURRENT_MARKER) : ' ';\n const label = selected ? this.theme.bold(this.theme.fg('accent', status)) : this.theme.fg('muted', status);\n const current = status === task.status ? this.theme.fg('dim', 'current') : '';\n const row = rightAligned(` ${marker} ${label}`, current, width);\n lines.push(this.theme.bg(selected ? 'selectedBg' : 'userMessageBg', row));\n }\n return lines;\n }\n\n /** The subject as an input: a filled field with a caret, as in the mockup. */\n private subjectField(task: Task, width: number): string[] {\n const editing = this.mode === 'subject';\n const value = editing ? `${this.draft}${CURSOR_BLOCK}` : task.subject;\n return [\n this.theme.fg('dim', editing ? SUBJECT_HINT : 'SUBJECT'),\n this.theme.bg(editing ? 'selectedBg' : 'userMessageBg', fitRow(` ${value}`, width)),\n ];\n }\n\n private detailLines(width: number): string[] {\n const task = this.selectedTask();\n if (!task) return [this.theme.fg('dim', 'No task selected')];\n\n const editing = this.mode === 'subject' || this.mode === 'status';\n const badge = editing ? this.badge('EDIT', 'toolSuccessBg', 'warning') : '';\n const lines = [rightAligned(this.theme.bold(this.theme.fg('accent', `TASK #${task.id}`)), badge, width), ''];\n lines.push(...this.subjectField(task, width));\n lines.push('', ...this.statusLines(task, width), '');\n\n if (task.activeForm) lines.push(this.theme.fg('dim', 'ACTIVE FORM'), task.activeForm, '');\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const delegated = task.delegation?.runId ? this.theme.fg('success', `delegated run ${task.delegation.runId}`) : '';\n lines.push(\n this.theme.fg('dim', 'OWNER'),\n delegated ? `${owner} ${this.theme.fg('dim', '·')} ${delegated}` : owner,\n '',\n );\n lines.push(\n this.theme.fg('dim', 'BLOCKED BY'),\n task.blockedBy?.length ? task.blockedBy.map((id) => `#${id}`).join(' · ') : NO_VALUE,\n );\n if (task.description) lines.push('', this.theme.fg('dim', 'DESCRIPTION'), ...wrap(task.description, width));\n if (this.notice) lines.push('', this.theme.fg('error', this.notice));\n return lines;\n }\n\n private headerSummary(): string {\n const counts = countTasks(this.options.store.snapshot.tasks);\n const summary = [`${counts.completed}/${counts.total} completed`];\n if (counts.inProgress > 0) summary.push(`${counts.inProgress} in progress`);\n if (counts.blocked > 0) summary.push(`${counts.blocked} blocked`);\n if (counts.failed > 0) summary.push(`${counts.failed} failed`);\n return summary.join(' · ');\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${this.headerSummary()} · ${LIST_HEADER_RIGHT}`,\n footer: this.hintLine(LIST_HINTS),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * LIST_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n // Each column gives up one column to the gutter, so content never abuts the\n // divider: a long store path used to run straight into the detail heading.\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.listLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.listLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n dispose(): void {\n this.unwatch();\n }\n}\n\nexport async function openTaskSpace(ctx: ExtensionContext, options: TaskSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new TaskSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskSpace.d.cts","names":[],"sources":["../../src/tui/taskSpace.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"taskSpace.d.cts","names":[],"sources":["../../src/tui/taskSpace.ts"],"mappings":";;;;;cAiCa;;;;;;UAEI;EACf,OAAO;;;;;;;;cA8GI,2BAA2B;mBACrB;mBACA;mBACA;UAET;UACA;UACA;UACA;UACA;UACA;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,SAAS,kBAAkB,OAAO;UAOzE;UAIA;UAMA;;UAQA;UAwBA;UAYA;EAWR,YAAY;;;;;;UA2CJ;;UAKA;UAMA;UAsCA;;UAoBA;UASA;UA2BA;YASE,aAAa;YAUb,WAAW,eAAe;UAoB5B;EAcR;;iBAKoB,cAAc,KAAK,kBAAkB,SAAS,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskSpace.d.mts","names":[],"sources":["../../src/tui/taskSpace.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"taskSpace.d.mts","names":[],"sources":["../../src/tui/taskSpace.ts"],"mappings":";;;;;cAiCa;;;;;;UAEI;EACf,OAAO;;;;;;;;cA8GI,2BAA2B;mBACrB;mBACA;mBACA;UAET;UACA;UACA;UACA;UACA;UACA;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,SAAS,kBAAkB,OAAO;UAOzE;UAIA;UAMA;;UAQA;UAwBA;UAYA;EAWR,YAAY;;;;;;UA2CJ;;UAKA;UAMA;UAsCA;;UAoBA;UASA;UA2BA;YASE,aAAa;YAUb,WAAW,eAAe;UAoB5B;EAcR;;iBAKoB,cAAc,KAAK,kBAAkB,SAAS,mBAAmB"}
|
package/dist/tui/taskSpace.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{isBlocked as e}from"../services/store/taskGraph.mjs";import{MSG_NO_TASKS as t,TASK_STATUSES as n}from"../schemas/task.mjs";import{applyTaskMutation as r,isCommittingOp as i,singleItemOutcome as a}from"../services/store/reducer.mjs";import{STATUS_LABEL as o,overlayStatusGlyph as s}from"./format.mjs";import{countTasks as c,visibleTasks as l}from"./selectors.mjs";import{matchesKey as u,truncateToWidth as d,visibleWidth as f}from"@earendil-works/pi-tui";import{homedir as p}from"node:os";import{DOOM_FULLSCREEN_UI_OPTIONS as m,DOOM_OVERLAY_ACCENT as h,DoomOverlay as g}from"@agimon-ai/doompi-ui/components/doomOverlay";const
|
|
1
|
+
import{isBlocked as e}from"../services/store/taskGraph.mjs";import{MSG_NO_TASKS as t,TASK_STATUSES as n}from"../schemas/task.mjs";import{applyTaskMutation as r,isCommittingOp as i,singleItemOutcome as a}from"../services/store/reducer.mjs";import{STATUS_LABEL as o,overlayStatusGlyph as s}from"./format.mjs";import{countTasks as c,visibleTasks as l}from"./selectors.mjs";import{matchesKey as u,truncateToWidth as d,visibleWidth as f}from"@earendil-works/pi-tui";import{homedir as p}from"node:os";import{DOOM_FULLSCREEN_UI_OPTIONS as m,DOOM_OVERLAY_ACCENT as h,DoomOverlay as g}from"@agimon-ai/doompi-ui/components/doomOverlay";import{fitStyledLine as _}from"@agimon-ai/doompi-ui/rendering";const v=m.overlayOptions;function y(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127)return!0}return!1}function b(e,t){let n=p(),r=n&&e.startsWith(n)?`~${e.slice(n.length)}`:e;if(f(r)<=t)return r;let i=r.split(`/`),a=i.pop()??r;for(;i.length>0;){let e=`${i[i.length-1]}/${a}`;if(f(`…/${e}`)>t)break;a=e,i.pop()}return d(`…/${a}`,t,``)}const x=`none`,S=[[`↑↓`,`select`],[`enter`,`edit subject`],[`s`,`status`],[`esc`,`close`]];function C(e,t){return _(e,t,`…`)}function w(e,t,n){let r=f(t),i=Math.max(0,n-r-1);return C(e,i)+` `.repeat(Math.max(1,n-i-r))+C(t,r)}function T(e,t){if(t<=0)return[];let n=[],r=``;for(let i of e.split(/\s+/).filter(Boolean))r.length===0?r=i:r.length+1+i.length<=t?r+=` ${i}`:(n.push(r),r=i);return r.length>0&&n.push(r),n}var E=class extends g{options;done;unwatch;mode=`browse`;selected=0;statusIndex=0;draft=``;notice;bodyHeight=1;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r,this.unwatch=n.store.onExternalChange(()=>this.tui.requestRender())}tasks(){return l(this.options.store.snapshot.tasks)}selectedTask(){let e=this.tasks();if(e.length!==0)return e[Math.min(this.selected,e.length-1)]}move(e){let t=this.tasks();t.length!==0&&(this.selected=Math.max(0,Math.min(t.length-1,this.selected+e)),this.tui.requestRender())}commit(e){this.options.store.mutate(t=>{let n=r(t,`upsert`,{tasks:[e]});return i(n.op)?{document:n.document,value:n.op}:{value:n.op}}).then(e=>{let t=a(e.value);this.notice=t?.kind===`failed`?t.message:void 0,this.tui.requestRender()}).catch(e=>{this.notice=e instanceof Error?e.message:String(e),this.tui.requestRender()})}handleStatusInput(e){let t=this.selectedTask();u(e,`up`)?this.statusIndex=Math.max(0,this.statusIndex-1):u(e,`down`)?this.statusIndex=Math.min(n.length-1,this.statusIndex+1):e===`s`?this.statusIndex=(this.statusIndex+1)%n.length:e===`\r`||e===`
|
|
2
2
|
`?(this.mode=`browse`,t&&this.commit({id:t.id,status:n[this.statusIndex]})):u(e,`escape`)&&(this.mode=`browse`),this.tui.requestRender()}handleSubjectInput(e){let t=this.selectedTask();e===`\r`||e===`
|
|
3
|
-
`?(this.mode=`browse`,t&&this.commit({id:t.id,subject:this.draft})):u(e,`escape`)?this.mode=`browse`:e===``?this.draft=this.draft.slice(0,-1):
|
|
4
|
-
`)&&(this.mode=`subject`,this.draft=t.subject,this.tui.requestRender())}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}listLines(n){let r=this.tasks(),i=this.theme.bold(`TASKS`),a=Math.max(0,n-f(`TASKS`)-2),c=[`${i} ${this.theme.fg(`dim`,
|
|
3
|
+
`?(this.mode=`browse`,t&&this.commit({id:t.id,subject:this.draft})):u(e,`escape`)?this.mode=`browse`:e===``?this.draft=this.draft.slice(0,-1):y(e)||(this.draft+=e),this.tui.requestRender()}handleInput(e){if(this.mode===`status`){this.handleStatusInput(e);return}if(this.mode===`subject`){this.handleSubjectInput(e);return}if(u(e,`escape`)||u(e,`ctrl+c`)){this.done(void 0);return}if(u(e,`up`)){this.move(-1);return}if(u(e,`down`)){this.move(1);return}let t=this.selectedTask();if(t){if(e===`s`){this.mode=`status`,this.statusIndex=Math.max(0,n.indexOf(t.status)),this.tui.requestRender();return}(e===`\r`||e===`
|
|
4
|
+
`)&&(this.mode=`subject`,this.draft=t.subject,this.tui.requestRender())}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}listLines(n){let r=this.tasks(),i=this.theme.bold(`TASKS`),a=Math.max(0,n-f(`TASKS`)-2),c=[`${i} ${this.theme.fg(`dim`,b(this.options.store.storePath,a))}`,``];if(r.length===0)return c.push(this.theme.fg(`dim`,t)),c;let l=Math.max(1,Math.floor((this.bodyHeight-c.length)/2)),u=Math.max(0,Math.min(this.selected-l+1,Math.max(0,r.length-l)));for(let[t,i]of r.slice(u,u+l).entries()){let a=u+t===Math.min(this.selected,r.length-1),l=a?this.theme.fg(`accent`,`›`):` `,d=a?this.theme.bold(i.subject):i.subject,f=s(i.status,this.theme),p=i.owner??i.delegation?.agent??x,m=i.status===`pending`&&e(this.options.store.snapshot.tasks,i)?this.theme.fg(`warning`,`blocked by ${(i.blockedBy??[]).map(e=>`#${e}`).join(`, `)}`):this.theme.fg(`dim`,o[i.status]),h=`${l} ${f} ${this.theme.fg(`dim`,`#${i.id}`)} ${d}`,g=`${` `.repeat(6)}${this.badge(p,`userMessageBg`,`muted`)} ${m}`,_=a?`selectedBg`:`userMessageBg`;for(let e of[h,g])c.push(this.theme.bg(_,C(e,n)))}return c}statusLines(e,t){if(this.mode!==`status`){let t=s(e.status,this.theme);return[this.theme.fg(`dim`,`STATUS`),`${t} ${o[e.status]}`]}let r=[this.theme.fg(`dim`,`STATUS s cycles · enter commits`)];for(let[i,a]of n.entries()){let n=i===this.statusIndex,o=n?this.theme.fg(`accent`,`▸`):` `,s=n?this.theme.bold(this.theme.fg(`accent`,a)):this.theme.fg(`muted`,a),c=a===e.status?this.theme.fg(`dim`,`current`):``,l=w(` ${o} ${s}`,c,t);r.push(this.theme.bg(n?`selectedBg`:`userMessageBg`,l))}return r}subjectField(e,t){let n=this.mode===`subject`,r=n?`${this.draft}█`:e.subject;return[this.theme.fg(`dim`,n?`SUBJECT enter commits · esc cancels`:`SUBJECT`),this.theme.bg(n?`selectedBg`:`userMessageBg`,C(` ${r}`,t))]}detailLines(e){let t=this.selectedTask();if(!t)return[this.theme.fg(`dim`,`No task selected`)];let n=this.mode===`subject`||this.mode===`status`?this.badge(`EDIT`,`toolSuccessBg`,`warning`):``,r=[w(this.theme.bold(this.theme.fg(`accent`,`TASK #${t.id}`)),n,e),``];r.push(...this.subjectField(t,e)),r.push(``,...this.statusLines(t,e),``),t.activeForm&&r.push(this.theme.fg(`dim`,`ACTIVE FORM`),t.activeForm,``);let i=t.owner??t.delegation?.agent??x,a=t.delegation?.runId?this.theme.fg(`success`,`delegated run ${t.delegation.runId}`):``;return r.push(this.theme.fg(`dim`,`OWNER`),a?`${i} ${this.theme.fg(`dim`,`·`)} ${a}`:i,``),r.push(this.theme.fg(`dim`,`BLOCKED BY`),t.blockedBy?.length?t.blockedBy.map(e=>`#${e}`).join(` · `):x),t.description&&r.push(``,this.theme.fg(`dim`,`DESCRIPTION`),...T(t.description,e)),this.notice&&r.push(``,this.theme.fg(`error`,this.notice)),r}headerSummary(){let e=c(this.options.store.snapshot.tasks),t=[`${e.completed}/${e.total} completed`];return e.inProgress>0&&t.push(`${e.inProgress} in progress`),e.blocked>0&&t.push(`${e.blocked} blocked`),e.failed>0&&t.push(`${e.failed} failed`),t.join(` · `)}getChrome(){return{title:`TASK SPACE`,accent:h,breadcrumb:`SPC › t › t / tasks`,headerRight:`${this.headerSummary()} · esc close`,footer:this.hintLine(S)}}renderBody(e,t){if(this.bodyHeight=t,e<48)return this.renderStacked(e,t);let n=Math.max(18,Math.floor((e-1)*.3333333333333333)),r=Math.max(1,e-n-1),i=Math.max(1,n-1),a=Math.max(1,r-1),o=this.listLines(i),s=this.detailLines(a),c=this.theme.fg(`borderMuted`,`│`);return Array.from({length:t},(t,n)=>{let r=`${C(o[n]??``,i)} ${c} ${C(s[n]??``,a)}`;return d(r,e,`…`)})}renderStacked(e,t){let n=Math.max(1,Math.floor((t-1)/2)),r=Math.max(0,t-n-1);this.bodyHeight=n;let i=this.listLines(e),a=this.detailLines(e);return[...i.slice(0,n),...Array.from({length:Math.max(0,n-i.length)},()=>``),this.theme.fg(`borderMuted`,`─`.repeat(e)),...a.slice(0,r)]}dispose(){this.unwatch()}};async function D(e,t){await e.ui.custom((e,n,r,i)=>new E(e,n,t,i),m)}export{v as TASK_SPACE_OVERLAY_OPTIONS,E as TaskSpaceComponent,D as openTaskSpace};
|
|
5
5
|
//# sourceMappingURL=taskSpace.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskSpace.mjs","names":[],"sources":["../../src/tui/taskSpace.ts"],"sourcesContent":["/**\n * Task Space: the `SPC t t` overlay.\n *\n * The task list and detail pane render from plain values so the whole surface\n * can be asserted as text without a live terminal.\n *\n * The overlay reads the store on every render rather than caching rows, so an\n * external write by a delegated child shows up without closing the overlay.\n *\n * Every commit it makes is a single-entry `upsert` that carries the selected\n * task's id. That id is what keeps creating a task a tool-only path: an entry\n * without one would create rather than be rejected.\n */\n\nimport { homedir } from 'node:os';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { MSG_NO_TASKS, TASK_STATUSES } from '../schemas/task.ts';\nimport { applyTaskMutation, isCommittingOp, type Op, singleItemOutcome } from '../services/store/reducer.ts';\nimport { isBlocked } from '../services/store/taskGraph.ts';\nimport type { TaskStore } from '../services/store/taskStore.ts';\nimport type { Task, TaskItemMutation } from '../services/store/types.ts';\nimport { overlayStatusGlyph, STATUS_LABEL } from './format.ts';\nimport { countTasks, visibleTasks } from './selectors.ts';\n\nexport const TASK_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nexport interface TaskSpaceOptions {\n store: TaskStore;\n}\n\ntype EditMode = 'browse' | 'status' | 'subject';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Store path for the list heading: home collapses to `~`, and an over-long path\n * loses leading segments rather than its tail, because the file name identifies\n * the store while `/var/folders/...` does not.\n */\nfunction storeLabel(storePath: string, width: number): string {\n const home = homedir();\n const abbreviated = home && storePath.startsWith(home) ? `~${storePath.slice(home.length)}` : storePath;\n if (visibleWidth(abbreviated) <= width) return abbreviated;\n\n const segments = abbreviated.split('/');\n let tail = segments.pop() ?? abbreviated;\n while (segments.length > 0) {\n const candidate = `${segments[segments.length - 1]}/${tail}`;\n if (visibleWidth(`…/${candidate}`) > width) break;\n tail = candidate;\n segments.pop();\n }\n return truncateToWidth(`…/${tail}`, width, '');\n}\n\n/** Neither palette name is exported, so take both from the theme's methods. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\ntype ThemeColor = Parameters<Theme['fg']>[0];\n\n/** Each task occupies a heading row and a metadata row beneath it. */\nconst ROWS_PER_TASK = 2;\n/** Hanging indent for the metadata row: clears marker, glyph and id. */\nconst META_INDENT = 6;\n\nconst MIN_WIDTH = 48;\n/** A third for the list, two thirds for the task being read or edited. */\nconst LIST_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst SELECTION_MARKER = '›';\nconst CURRENT_MARKER = '▸';\nconst CURSOR_BLOCK = '█';\nconst ELLIPSIS = '…';\nconst NO_VALUE = 'none';\n\nconst TITLE = 'TASK SPACE';\nconst BREADCRUMB = 'SPC › t › t / tasks';\n/** Key/label pairs rendered as capped keys, matching the mockup's footer. */\nconst LIST_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'edit subject'],\n ['s', 'status'],\n ['esc', 'close'],\n];\nconst LIST_HEADER_RIGHT = 'esc close';\nconst STATUS_HINT = 'STATUS s cycles · enter commits';\nconst SUBJECT_HINT = 'SUBJECT enter commits · esc cancels';\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/**\n * Task Space overlay component.\n *\n * Rendering is pure: `render(width)` reads the live store and returns lines with\n * no side effects, so the fidelity job can capture it and unit tests can assert on it.\n */\nexport class TaskSpaceComponent extends DoomOverlay {\n private readonly options: TaskSpaceOptions;\n private readonly done: (result: undefined) => void;\n private readonly unwatch: () => void;\n\n private mode: EditMode = 'browse';\n private selected = 0;\n private statusIndex = 0;\n private draft = '';\n private notice: string | undefined;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, options: TaskSpaceOptions, done: (result: undefined) => void) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.unwatch = options.store.onExternalChange(() => this.tui.requestRender());\n }\n\n private tasks(): Task[] {\n return visibleTasks(this.options.store.snapshot.tasks);\n }\n\n private selectedTask(): Task | undefined {\n const tasks = this.tasks();\n if (tasks.length === 0) return undefined;\n return tasks[Math.min(this.selected, tasks.length - 1)];\n }\n\n private move(delta: number): void {\n const tasks = this.tasks();\n if (tasks.length === 0) return;\n this.selected = Math.max(0, Math.min(tasks.length - 1, this.selected + delta));\n this.tui.requestRender();\n }\n\n /** Commits through the same reducer the task tool uses, so both paths share every invariant. */\n private commit(item: TaskItemMutation): void {\n this.options.store\n .mutate<Op>((document) => {\n const result = applyTaskMutation(document, 'upsert', { tasks: [item] });\n // A rejected mutation must not reach the writer: committing it would\n // bump `rev` for an edit that never happened.\n if (!isCommittingOp(result.op)) return { value: result.op };\n return { document: result.document, value: result.op };\n })\n .then((outcome) => {\n // The reducer's message is unprefixed, so the notice reads\n // \"subject must not be blank\" rather than \"item[0] failed: ...\".\n const single = singleItemOutcome(outcome.value);\n this.notice = single?.kind === 'failed' ? single.message : undefined;\n this.tui.requestRender();\n })\n .catch((error: unknown) => {\n // Shown in the overlay rather than swallowed: a rejected write means the\n // edit the operator just made never landed.\n this.notice = error instanceof Error ? error.message : String(error);\n this.tui.requestRender();\n });\n }\n\n private handleStatusInput(data: string): void {\n const task = this.selectedTask();\n if (matchesKey(data, 'up')) this.statusIndex = Math.max(0, this.statusIndex - 1);\n else if (matchesKey(data, 'down')) this.statusIndex = Math.min(TASK_STATUSES.length - 1, this.statusIndex + 1);\n else if (data === 's') this.statusIndex = (this.statusIndex + 1) % TASK_STATUSES.length;\n else if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, status: TASK_STATUSES[this.statusIndex] });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private handleSubjectInput(data: string): void {\n const task = this.selectedTask();\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, subject: this.draft });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n else if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'status') return this.handleStatusInput(data);\n if (this.mode === 'subject') return this.handleSubjectInput(data);\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up')) return this.move(-1);\n if (matchesKey(data, 'down')) return this.move(1);\n\n const task = this.selectedTask();\n if (!task) return;\n if (data === 's') {\n this.mode = 'status';\n this.statusIndex = Math.max(0, TASK_STATUSES.indexOf(task.status));\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'subject';\n this.draft = task.subject;\n this.tui.requestRender();\n }\n }\n\n /**\n * A padded, background-filled chip. Foreground is applied inside the fill so\n * the colour closes with it, rather than a reset stripping the background\n * partway through the label.\n */\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] edit subject`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n private listLines(width: number): string[] {\n const tasks = this.tasks();\n const label = this.theme.bold('TASKS');\n const pathWidth = Math.max(0, width - visibleWidth('TASKS') - 2);\n const lines = [`${label} ${this.theme.fg('dim', storeLabel(this.options.store.storePath, pathWidth))}`, ''];\n if (tasks.length === 0) {\n lines.push(this.theme.fg('dim', MSG_NO_TASKS));\n return lines;\n }\n\n // Two rows per task: at a third of the width a single row left the subject\n // with a handful of columns once owner and state took their share.\n const budget = Math.max(1, Math.floor((this.bodyHeight - lines.length) / ROWS_PER_TASK));\n const start = Math.max(0, Math.min(this.selected - budget + 1, Math.max(0, tasks.length - budget)));\n for (const [offset, task] of tasks.slice(start, start + budget).entries()) {\n const index = start + offset;\n const active = index === Math.min(this.selected, tasks.length - 1);\n const marker = active ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const subject = active ? this.theme.bold(task.subject) : task.subject;\n const glyph = overlayStatusGlyph(task.status, this.theme);\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const blocked = task.status === 'pending' && isBlocked(this.options.store.snapshot.tasks, task);\n const state = blocked\n ? this.theme.fg('warning', `blocked by ${(task.blockedBy ?? []).map((id) => `#${id}`).join(', ')}`)\n : this.theme.fg('dim', STATUS_LABEL[task.status]);\n // Subject owns the first row outright; the metadata hangs under it.\n const heading = `${marker} ${glyph} ${this.theme.fg('dim', `#${task.id}`)} ${subject}`;\n const meta = `${' '.repeat(META_INDENT)}${this.badge(owner, 'userMessageBg', 'muted')} ${state}`;\n // Every task sits on a band, the selected one a shade lighter, as in the\n // mockup; the marker stays so selection survives themes with no background.\n const background: ThemeBg = active ? 'selectedBg' : 'userMessageBg';\n for (const row of [heading, meta]) {\n lines.push(this.theme.bg(background, fit(row, width)));\n }\n }\n return lines;\n }\n\n private statusLines(task: Task, width: number): string[] {\n if (this.mode !== 'status') {\n const glyph = overlayStatusGlyph(task.status, this.theme);\n return [this.theme.fg('dim', 'STATUS'), `${glyph} ${STATUS_LABEL[task.status]}`];\n }\n // Editing draws the picker as a filled panel, so the whole option list reads\n // as one surface rather than five loose rows.\n const lines = [this.theme.fg('dim', STATUS_HINT)];\n for (const [index, status] of TASK_STATUSES.entries()) {\n const selected = index === this.statusIndex;\n const marker = selected ? this.theme.fg('accent', CURRENT_MARKER) : ' ';\n const label = selected ? this.theme.bold(this.theme.fg('accent', status)) : this.theme.fg('muted', status);\n const current = status === task.status ? this.theme.fg('dim', 'current') : '';\n const row = rightAligned(` ${marker} ${label}`, current, width);\n lines.push(this.theme.bg(selected ? 'selectedBg' : 'userMessageBg', row));\n }\n return lines;\n }\n\n /** The subject as an input: a filled field with a caret, as in the mockup. */\n private subjectField(task: Task, width: number): string[] {\n const editing = this.mode === 'subject';\n const value = editing ? `${this.draft}${CURSOR_BLOCK}` : task.subject;\n return [\n this.theme.fg('dim', editing ? SUBJECT_HINT : 'SUBJECT'),\n this.theme.bg(editing ? 'selectedBg' : 'userMessageBg', fit(` ${value}`, width)),\n ];\n }\n\n private detailLines(width: number): string[] {\n const task = this.selectedTask();\n if (!task) return [this.theme.fg('dim', 'No task selected')];\n\n const editing = this.mode === 'subject' || this.mode === 'status';\n const badge = editing ? this.badge('EDIT', 'toolSuccessBg', 'warning') : '';\n const lines = [rightAligned(this.theme.bold(this.theme.fg('accent', `TASK #${task.id}`)), badge, width), ''];\n lines.push(...this.subjectField(task, width));\n lines.push('', ...this.statusLines(task, width), '');\n\n if (task.activeForm) lines.push(this.theme.fg('dim', 'ACTIVE FORM'), task.activeForm, '');\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const delegated = task.delegation?.runId ? this.theme.fg('success', `delegated run ${task.delegation.runId}`) : '';\n lines.push(\n this.theme.fg('dim', 'OWNER'),\n delegated ? `${owner} ${this.theme.fg('dim', '·')} ${delegated}` : owner,\n '',\n );\n lines.push(\n this.theme.fg('dim', 'BLOCKED BY'),\n task.blockedBy?.length ? task.blockedBy.map((id) => `#${id}`).join(' · ') : NO_VALUE,\n );\n if (task.description) lines.push('', this.theme.fg('dim', 'DESCRIPTION'), ...wrap(task.description, width));\n if (this.notice) lines.push('', this.theme.fg('error', this.notice));\n return lines;\n }\n\n private headerSummary(): string {\n const counts = countTasks(this.options.store.snapshot.tasks);\n const summary = [`${counts.completed}/${counts.total} completed`];\n if (counts.inProgress > 0) summary.push(`${counts.inProgress} in progress`);\n if (counts.blocked > 0) summary.push(`${counts.blocked} blocked`);\n if (counts.failed > 0) summary.push(`${counts.failed} failed`);\n return summary.join(' · ');\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${this.headerSummary()} · ${LIST_HEADER_RIGHT}`,\n footer: this.hintLine(LIST_HINTS),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * LIST_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n // Each column gives up one column to the gutter, so content never abuts the\n // divider: a long store path used to run straight into the detail heading.\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.listLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.listLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n dispose(): void {\n this.unwatch();\n }\n}\n\nexport async function openTaskSpace(ctx: ExtensionContext, options: TaskSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new TaskSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"taskSpace.mjs","names":[],"sources":["../../src/tui/taskSpace.ts"],"sourcesContent":["/**\n * Task Space: the `SPC t t` overlay.\n *\n * The task list and detail pane render from plain values so the whole surface\n * can be asserted as text without a live terminal.\n *\n * The overlay reads the store on every render rather than caching rows, so an\n * external write by a delegated child shows up without closing the overlay.\n *\n * Every commit it makes is a single-entry `upsert` that carries the selected\n * task's id. That id is what keeps creating a task a tool-only path: an entry\n * without one would create rather than be rejected.\n */\n\nimport { homedir } from 'node:os';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport { fitStyledLine } from '@agimon-ai/doompi-ui/rendering';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { MSG_NO_TASKS, TASK_STATUSES } from '../schemas/task.ts';\nimport { applyTaskMutation, isCommittingOp, type Op, singleItemOutcome } from '../services/store/reducer.ts';\nimport { isBlocked } from '../services/store/taskGraph.ts';\nimport type { TaskStore } from '../services/store/taskStore.ts';\nimport type { Task, TaskItemMutation } from '../services/store/types.ts';\nimport { overlayStatusGlyph, STATUS_LABEL } from './format.ts';\nimport { countTasks, visibleTasks } from './selectors.ts';\n\nexport const TASK_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nexport interface TaskSpaceOptions {\n store: TaskStore;\n}\n\ntype EditMode = 'browse' | 'status' | 'subject';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Store path for the list heading: home collapses to `~`, and an over-long path\n * loses leading segments rather than its tail, because the file name identifies\n * the store while `/var/folders/...` does not.\n */\nfunction storeLabel(storePath: string, width: number): string {\n const home = homedir();\n const abbreviated = home && storePath.startsWith(home) ? `~${storePath.slice(home.length)}` : storePath;\n if (visibleWidth(abbreviated) <= width) return abbreviated;\n\n const segments = abbreviated.split('/');\n let tail = segments.pop() ?? abbreviated;\n while (segments.length > 0) {\n const candidate = `${segments[segments.length - 1]}/${tail}`;\n if (visibleWidth(`…/${candidate}`) > width) break;\n tail = candidate;\n segments.pop();\n }\n return truncateToWidth(`…/${tail}`, width, '');\n}\n\n/** Neither palette name is exported, so take both from the theme's methods. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\ntype ThemeColor = Parameters<Theme['fg']>[0];\n\n/** Each task occupies a heading row and a metadata row beneath it. */\nconst ROWS_PER_TASK = 2;\n/** Hanging indent for the metadata row: clears marker, glyph and id. */\nconst META_INDENT = 6;\n\nconst MIN_WIDTH = 48;\n/** A third for the list, two thirds for the task being read or edited. */\nconst LIST_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst SELECTION_MARKER = '›';\nconst CURRENT_MARKER = '▸';\nconst CURSOR_BLOCK = '█';\nconst ELLIPSIS = '…';\nconst NO_VALUE = 'none';\n\nconst TITLE = 'TASK SPACE';\nconst BREADCRUMB = 'SPC › t › t / tasks';\n/** Key/label pairs rendered as capped keys, matching the mockup's footer. */\nconst LIST_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'edit subject'],\n ['s', 'status'],\n ['esc', 'close'],\n];\nconst LIST_HEADER_RIGHT = 'esc close';\nconst STATUS_HINT = 'STATUS s cycles · enter commits';\nconst SUBJECT_HINT = 'SUBJECT enter commits · esc cancels';\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/**\n * Task Space overlay component.\n *\n * Rendering is pure: `render(width)` reads the live store and returns lines with\n * no side effects, so the fidelity job can capture it and unit tests can assert on it.\n */\nexport class TaskSpaceComponent extends DoomOverlay {\n private readonly options: TaskSpaceOptions;\n private readonly done: (result: undefined) => void;\n private readonly unwatch: () => void;\n\n private mode: EditMode = 'browse';\n private selected = 0;\n private statusIndex = 0;\n private draft = '';\n private notice: string | undefined;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, options: TaskSpaceOptions, done: (result: undefined) => void) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.unwatch = options.store.onExternalChange(() => this.tui.requestRender());\n }\n\n private tasks(): Task[] {\n return visibleTasks(this.options.store.snapshot.tasks);\n }\n\n private selectedTask(): Task | undefined {\n const tasks = this.tasks();\n if (tasks.length === 0) return undefined;\n return tasks[Math.min(this.selected, tasks.length - 1)];\n }\n\n private move(delta: number): void {\n const tasks = this.tasks();\n if (tasks.length === 0) return;\n this.selected = Math.max(0, Math.min(tasks.length - 1, this.selected + delta));\n this.tui.requestRender();\n }\n\n /** Commits through the same reducer the task tool uses, so both paths share every invariant. */\n private commit(item: TaskItemMutation): void {\n this.options.store\n .mutate<Op>((document) => {\n const result = applyTaskMutation(document, 'upsert', { tasks: [item] });\n // A rejected mutation must not reach the writer: committing it would\n // bump `rev` for an edit that never happened.\n if (!isCommittingOp(result.op)) return { value: result.op };\n return { document: result.document, value: result.op };\n })\n .then((outcome) => {\n // The reducer's message is unprefixed, so the notice reads\n // \"subject must not be blank\" rather than \"item[0] failed: ...\".\n const single = singleItemOutcome(outcome.value);\n this.notice = single?.kind === 'failed' ? single.message : undefined;\n this.tui.requestRender();\n })\n .catch((error: unknown) => {\n // Shown in the overlay rather than swallowed: a rejected write means the\n // edit the operator just made never landed.\n this.notice = error instanceof Error ? error.message : String(error);\n this.tui.requestRender();\n });\n }\n\n private handleStatusInput(data: string): void {\n const task = this.selectedTask();\n if (matchesKey(data, 'up')) this.statusIndex = Math.max(0, this.statusIndex - 1);\n else if (matchesKey(data, 'down')) this.statusIndex = Math.min(TASK_STATUSES.length - 1, this.statusIndex + 1);\n else if (data === 's') this.statusIndex = (this.statusIndex + 1) % TASK_STATUSES.length;\n else if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, status: TASK_STATUSES[this.statusIndex] });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private handleSubjectInput(data: string): void {\n const task = this.selectedTask();\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'browse';\n if (task) this.commit({ id: task.id, subject: this.draft });\n } else if (matchesKey(data, 'escape')) this.mode = 'browse';\n else if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'status') {\n this.handleStatusInput(data);\n return;\n }\n if (this.mode === 'subject') {\n this.handleSubjectInput(data);\n return;\n }\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up')) {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down')) {\n this.move(1);\n return;\n }\n\n const task = this.selectedTask();\n if (!task) return;\n if (data === 's') {\n this.mode = 'status';\n this.statusIndex = Math.max(0, TASK_STATUSES.indexOf(task.status));\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.mode = 'subject';\n this.draft = task.subject;\n this.tui.requestRender();\n }\n }\n\n /**\n * A padded, background-filled chip. Foreground is applied inside the fill so\n * the colour closes with it, rather than a reset stripping the background\n * partway through the label.\n */\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] edit subject`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n private listLines(width: number): string[] {\n const tasks = this.tasks();\n const label = this.theme.bold('TASKS');\n const pathWidth = Math.max(0, width - visibleWidth('TASKS') - 2);\n const lines = [`${label} ${this.theme.fg('dim', storeLabel(this.options.store.storePath, pathWidth))}`, ''];\n if (tasks.length === 0) {\n lines.push(this.theme.fg('dim', MSG_NO_TASKS));\n return lines;\n }\n\n // Two rows per task: at a third of the width a single row left the subject\n // with a handful of columns once owner and state took their share.\n const budget = Math.max(1, Math.floor((this.bodyHeight - lines.length) / ROWS_PER_TASK));\n const start = Math.max(0, Math.min(this.selected - budget + 1, Math.max(0, tasks.length - budget)));\n for (const [offset, task] of tasks.slice(start, start + budget).entries()) {\n const index = start + offset;\n const active = index === Math.min(this.selected, tasks.length - 1);\n const marker = active ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const subject = active ? this.theme.bold(task.subject) : task.subject;\n const glyph = overlayStatusGlyph(task.status, this.theme);\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const blocked = task.status === 'pending' && isBlocked(this.options.store.snapshot.tasks, task);\n const state = blocked\n ? this.theme.fg('warning', `blocked by ${(task.blockedBy ?? []).map((id) => `#${id}`).join(', ')}`)\n : this.theme.fg('dim', STATUS_LABEL[task.status]);\n // Subject owns the first row outright; the metadata hangs under it.\n const heading = `${marker} ${glyph} ${this.theme.fg('dim', `#${task.id}`)} ${subject}`;\n const meta = `${' '.repeat(META_INDENT)}${this.badge(owner, 'userMessageBg', 'muted')} ${state}`;\n // Every task sits on a band, the selected one a shade lighter, as in the\n // mockup; the marker stays so selection survives themes with no background.\n const background: ThemeBg = active ? 'selectedBg' : 'userMessageBg';\n for (const row of [heading, meta]) {\n lines.push(this.theme.bg(background, fitRow(row, width)));\n }\n }\n return lines;\n }\n\n private statusLines(task: Task, width: number): string[] {\n if (this.mode !== 'status') {\n const glyph = overlayStatusGlyph(task.status, this.theme);\n return [this.theme.fg('dim', 'STATUS'), `${glyph} ${STATUS_LABEL[task.status]}`];\n }\n // Editing draws the picker as a filled panel, so the whole option list reads\n // as one surface rather than five loose rows.\n const lines = [this.theme.fg('dim', STATUS_HINT)];\n for (const [index, status] of TASK_STATUSES.entries()) {\n const selected = index === this.statusIndex;\n const marker = selected ? this.theme.fg('accent', CURRENT_MARKER) : ' ';\n const label = selected ? this.theme.bold(this.theme.fg('accent', status)) : this.theme.fg('muted', status);\n const current = status === task.status ? this.theme.fg('dim', 'current') : '';\n const row = rightAligned(` ${marker} ${label}`, current, width);\n lines.push(this.theme.bg(selected ? 'selectedBg' : 'userMessageBg', row));\n }\n return lines;\n }\n\n /** The subject as an input: a filled field with a caret, as in the mockup. */\n private subjectField(task: Task, width: number): string[] {\n const editing = this.mode === 'subject';\n const value = editing ? `${this.draft}${CURSOR_BLOCK}` : task.subject;\n return [\n this.theme.fg('dim', editing ? SUBJECT_HINT : 'SUBJECT'),\n this.theme.bg(editing ? 'selectedBg' : 'userMessageBg', fitRow(` ${value}`, width)),\n ];\n }\n\n private detailLines(width: number): string[] {\n const task = this.selectedTask();\n if (!task) return [this.theme.fg('dim', 'No task selected')];\n\n const editing = this.mode === 'subject' || this.mode === 'status';\n const badge = editing ? this.badge('EDIT', 'toolSuccessBg', 'warning') : '';\n const lines = [rightAligned(this.theme.bold(this.theme.fg('accent', `TASK #${task.id}`)), badge, width), ''];\n lines.push(...this.subjectField(task, width));\n lines.push('', ...this.statusLines(task, width), '');\n\n if (task.activeForm) lines.push(this.theme.fg('dim', 'ACTIVE FORM'), task.activeForm, '');\n const owner = task.owner ?? task.delegation?.agent ?? NO_VALUE;\n const delegated = task.delegation?.runId ? this.theme.fg('success', `delegated run ${task.delegation.runId}`) : '';\n lines.push(\n this.theme.fg('dim', 'OWNER'),\n delegated ? `${owner} ${this.theme.fg('dim', '·')} ${delegated}` : owner,\n '',\n );\n lines.push(\n this.theme.fg('dim', 'BLOCKED BY'),\n task.blockedBy?.length ? task.blockedBy.map((id) => `#${id}`).join(' · ') : NO_VALUE,\n );\n if (task.description) lines.push('', this.theme.fg('dim', 'DESCRIPTION'), ...wrap(task.description, width));\n if (this.notice) lines.push('', this.theme.fg('error', this.notice));\n return lines;\n }\n\n private headerSummary(): string {\n const counts = countTasks(this.options.store.snapshot.tasks);\n const summary = [`${counts.completed}/${counts.total} completed`];\n if (counts.inProgress > 0) summary.push(`${counts.inProgress} in progress`);\n if (counts.blocked > 0) summary.push(`${counts.blocked} blocked`);\n if (counts.failed > 0) summary.push(`${counts.failed} failed`);\n return summary.join(' · ');\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${this.headerSummary()} · ${LIST_HEADER_RIGHT}`,\n footer: this.hintLine(LIST_HINTS),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * LIST_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n // Each column gives up one column to the gutter, so content never abuts the\n // divider: a long store path used to run straight into the detail heading.\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.listLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.listLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n dispose(): void {\n this.unwatch();\n }\n}\n\nexport async function openTaskSpace(ctx: ExtensionContext, options: TaskSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new TaskSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-task",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.12",
|
|
4
4
|
"description": "Pi task tracking extension with file-backed tasks and subagent delegation",
|
|
5
5
|
"homepage": "https://agimon.ai",
|
|
6
6
|
"license": "MIT",
|
|
@@ -142,9 +142,9 @@
|
|
|
142
142
|
},
|
|
143
143
|
"dependencies": {
|
|
144
144
|
"typebox": "1.1.38",
|
|
145
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
146
|
-
"@agimon-ai/doompi-
|
|
147
|
-
"@agimon-ai/doompi-
|
|
145
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.12",
|
|
146
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.12",
|
|
147
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.12"
|
|
148
148
|
},
|
|
149
149
|
"devDependencies": {
|
|
150
150
|
"@earendil-works/pi-coding-agent": "0.84.1",
|