@agimon-ai/doompi-task 0.0.1-alpha.32 → 0.0.1-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,7 +72,7 @@ is freed.
72
72
 
73
73
  ## TUI
74
74
 
75
- Use `/tasks` or `SPC t t` to open the interactive task view. These surfaces require a TUI. The
75
+ Use `/tasks` or `SPC t l` to open the interactive task view. These surfaces require a TUI. The
76
76
  `task` tool remains available in headless sessions.
77
77
 
78
78
  ## Storage and cleanup
@@ -1,3 +1,3 @@
1
- const e=require("../../schemas/task.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../services/delegation/manager.cjs"),r=require("../../commands/index.cjs"),i=require("../../types/config.cjs"),a=require("../../commands/task/taskTool.cjs"),o=require("../../services/contextContribution.cjs"),s=require("../../services/narration/taskNarration.cjs"),c=require("../store/paths.cjs"),l=require("../store/taskStore.cjs"),u=require("../../tui/taskOverlay2.cjs"),d=require("../node/delegationPlatform.cjs"),f=require("../telemetry/logSinkTelemetry.cjs");let p=require("@earendil-works/pi-tui"),m=require("@agimon-ai/doompi-extension-contracts/delegation"),h=require("@agimon-ai/doompi-extension-contracts/background-work"),g=require("@agimon-ai/doompi-extension-contracts/cordis-host"),_=require("@agimon-ai/doompi-extension-contracts/context-contributions"),v=require("@agimon-ai/doompi-extension-contracts/narration"),y=require("@agimon-ai/doompi-extension-contracts/readiness"),b=require("@agimon-ai/doompi-extension-contracts/ui-hub");const x=o.TASK_CONTEXT_CONTRIBUTION_SOURCE;function S(g,S){let C=f.createTaskErrorReporter(),w=f.toFailureReporter(C),T=!0,E=0,D,O,k,A,j,M,N=!1,P,F,I,L=e=>{C.recordWarning(`doom_task.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-task could not record a narration failure: ${String(e)}`)})},R={narrate(e){let t=D,n=(0,v.createNarrationRequest)(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(L)}catch(e){L(e)}}};g.inject([v.DOOM_NARRATION_SERVICE],e=>{let t=(0,v.requireDoomNarrationService)(e);return D=t,()=>{D===t&&(D=void 0)}});let z=async(e,t)=>{t?.throwIfAborted();let n=I;if(!n)throw Error(`doom-task session initialization has not started`);if(n.sessionManager!==e.sessionManager)throw Error(`doom-task readiness belongs to a stale Pi session`);if(await n.operation,t?.throwIfAborted(),!T||n!==I||!F)throw Error(`doom-task readiness belongs to a stale extension generation`)},B=()=>{j?.(),j=void 0,N=!1,M?.update(),k?.reset(),A?.dispose(),F=void 0};g.effect(()=>async()=>{T=!1,E+=1;let e=I?.operation;I=void 0;try{e&&await Promise.allSettled([e]),B(),k?.dispose(),P?.(),P=void 0,O?.dispose()}finally{await C.shutdown()}},x);let V=new l.TaskStore({report:w,onCommitted:(e,t)=>s.narrateTaskCommit(R,e,t)});O=V,g.inject([_.DOOM_CONTEXT_CONTRIBUTIONS_SERVICE],e=>{let t=(0,_.requireDoomContextContributions)(e).register(o.createTaskContextContribution(()=>V.snapshot.tasks));return()=>t.dispose()});let H=()=>{T&&(A?.update(),M?.update())},U=new n.DelegationManager({store:V,cwd:process.cwd(),platform:d.createNodeDelegationPlatform(),notify:(e,t)=>S.sendMessage(e,t),getSessionId:()=>F,onChange:H,runTimeoutMs:i.getDelegationTimeoutMs(),report:w,onNotifyError:(e,t)=>{C.recordNotificationError(e,t)}});k=U,g.inject([m.DOOM_DELEGATION_SERVICE],e=>{let t=(0,m.readDoomDelegationService)(e);if(t)return U.bind(e,t)}),g.inject([h.DOOM_BACKGROUND_WORK_SERVICE],e=>{let t=(0,h.readDoomBackgroundWorkService)(e);if(!t)return;let r=t.register({provider:n.BACKGROUND_WORK_PROVIDER,listActiveWork:()=>N?U.listActiveWork():[]});return M=r,()=>{r.dispose(),M===r&&(M=void 0)}});let W=new u.TaskOverlay({getTasks:()=>V.snapshot.tasks,delegation:U});A=W,a.registerTaskTool(S,{store:V,delegation:U,maxTasks:i.getMaxTasks(),onChange:H,report:w,waitUntilReady:z}),r.registerTasksCommand(S,V,z),g.inject([b.DOOM_UI_HUB_SERVICE],t=>{let n=(0,b.requireDoomUiHub)(t).registerLeader({source:x,bindings:[{id:`tasks.open`,path:[{key:`t`,label:`tasks`,order:65},{key:`t`,label:`tasks`}],command:{name:e.COMMAND_NAME}}]}),r=()=>n.dispose();return P=r,()=>{r(),P===r&&(P=void 0)}}),S.registerMessageRenderer(n.NOTIFY_CUSTOM_TYPE,(e,t,n)=>{let r=typeof e.content==`string`?e.content:``;return new p.Text(n.fg(`muted`,r),0,0)});let G=i.resolveCollapseKey();G!==`off`&&S.registerShortcut(G,{description:`Collapse or expand the task overlay`,handler:async e=>{!T||!e.hasUI||(await z(e),!(!T||!W.isRegistered())&&W.toggleCollapse())}}),S.on(`session_start`,async(e,n)=>{if(!T)return;let r=++E;F!==void 0&&B();let a=n,o=async e=>{let n=()=>!e?.aborted&&T&&r===E;try{let e=a.sessionManager?.getSessionId();if(!e)throw Error(`doom-task requires a session id`);F=e,N=!a.hasUI,M?.update(),V.configureSession(c.resolveSessionKey(e));let r=await c.removeLegacyStoreDirectoryAsync(V.storePath);if(!n())return;let o=c.hasStorePathOverride()?{removed:[],errors:[]}:await c.sweepStoreFilesAsync(V.storePath,i.getStoreTtlMs());if(!n())return;let s=[...r.errors,...o.errors];for(let e of s)w.warn(t.TASK_EVENT.storeSweepFailed,Error(e));if(s.length>0&&a.hasUI&&a.ui.notify(s.join(`
1
+ const e=require("../../schemas/task.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../services/delegation/manager.cjs"),r=require("../../commands/index.cjs"),i=require("../../types/config.cjs"),a=require("../../commands/task/taskTool.cjs"),o=require("../../services/contextContribution.cjs"),s=require("../../services/narration/taskNarration.cjs"),c=require("../store/paths.cjs"),l=require("../store/taskStore.cjs"),u=require("../../tui/taskOverlay2.cjs"),d=require("../node/delegationPlatform.cjs"),f=require("../telemetry/logSinkTelemetry.cjs");let p=require("@earendil-works/pi-tui"),m=require("@agimon-ai/doompi-extension-contracts/delegation"),h=require("@agimon-ai/doompi-extension-contracts/background-work"),g=require("@agimon-ai/doompi-extension-contracts/cordis-host"),_=require("@agimon-ai/doompi-extension-contracts/context-contributions"),v=require("@agimon-ai/doompi-extension-contracts/narration"),y=require("@agimon-ai/doompi-extension-contracts/readiness"),b=require("@agimon-ai/doompi-extension-contracts/ui-hub");const x=o.TASK_CONTEXT_CONTRIBUTION_SOURCE;function S(g,S){let C=f.createTaskErrorReporter(),w=f.toFailureReporter(C),T=!0,E=0,D,O,k,A,j,M,N=!1,P,F,I,L=e=>{C.recordWarning(`doom_task.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-task could not record a narration failure: ${String(e)}`)})},R={narrate(e){let t=D,n=(0,v.createNarrationRequest)(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(L)}catch(e){L(e)}}};g.inject([v.DOOM_NARRATION_SERVICE],e=>{let t=(0,v.requireDoomNarrationService)(e);return D=t,()=>{D===t&&(D=void 0)}});let z=async(e,t)=>{t?.throwIfAborted();let n=I;if(!n)throw Error(`doom-task session initialization has not started`);if(n.sessionManager!==e.sessionManager)throw Error(`doom-task readiness belongs to a stale Pi session`);if(await n.operation,t?.throwIfAborted(),!T||n!==I||!F)throw Error(`doom-task readiness belongs to a stale extension generation`)},B=()=>{j?.(),j=void 0,N=!1,M?.update(),k?.reset(),A?.dispose(),F=void 0};g.effect(()=>async()=>{T=!1,E+=1;let e=I?.operation;I=void 0;try{e&&await Promise.allSettled([e]),B(),k?.dispose(),P?.(),P=void 0,O?.dispose()}finally{await C.shutdown()}},x);let V=new l.TaskStore({report:w,onCommitted:(e,t)=>s.narrateTaskCommit(R,e,t)});O=V,g.inject([_.DOOM_CONTEXT_CONTRIBUTIONS_SERVICE],e=>{let t=(0,_.requireDoomContextContributions)(e).register(o.createTaskContextContribution(()=>V.snapshot.tasks));return()=>t.dispose()});let H=()=>{T&&(A?.update(),M?.update())},U=new n.DelegationManager({store:V,cwd:process.cwd(),platform:d.createNodeDelegationPlatform(),notify:(e,t)=>S.sendMessage(e,t),getSessionId:()=>F,onChange:H,runTimeoutMs:i.getDelegationTimeoutMs(),report:w,onNotifyError:(e,t)=>{C.recordNotificationError(e,t)}});k=U,g.inject([m.DOOM_DELEGATION_SERVICE],e=>{let t=(0,m.readDoomDelegationService)(e);if(t)return U.bind(e,t)}),g.inject([h.DOOM_BACKGROUND_WORK_SERVICE],e=>{let t=(0,h.readDoomBackgroundWorkService)(e);if(!t)return;let r=t.register({provider:n.BACKGROUND_WORK_PROVIDER,listActiveWork:()=>N?U.listActiveWork():[]});return M=r,()=>{r.dispose(),M===r&&(M=void 0)}});let W=new u.TaskOverlay({getTasks:()=>V.snapshot.tasks,delegation:U});A=W,a.registerTaskTool(S,{store:V,delegation:U,maxTasks:i.getMaxTasks(),onChange:H,report:w,waitUntilReady:z}),r.registerTasksCommand(S,V,z),g.inject([b.DOOM_UI_HUB_SERVICE],t=>{let n=(0,b.requireDoomUiHub)(t).registerLeader({source:x,bindings:[{id:`tasks.open`,path:[{key:`t`,label:`tasks`,detail:`session task list`,order:65},{key:`l`,label:`list`,detail:`tasks in this session`}],command:{name:e.COMMAND_NAME}}]}),r=()=>n.dispose();return P=r,()=>{r(),P===r&&(P=void 0)}}),S.registerMessageRenderer(n.NOTIFY_CUSTOM_TYPE,(e,t,n)=>{let r=typeof e.content==`string`?e.content:``;return new p.Text(n.fg(`muted`,r),0,0)});let G=i.resolveCollapseKey();G!==`off`&&S.registerShortcut(G,{description:`Collapse or expand the task overlay`,handler:async e=>{!T||!e.hasUI||(await z(e),!(!T||!W.isRegistered())&&W.toggleCollapse())}}),S.on(`session_start`,async(e,n)=>{if(!T)return;let r=++E;F!==void 0&&B();let a=n,o=async e=>{let n=()=>!e?.aborted&&T&&r===E;try{let e=a.sessionManager?.getSessionId();if(!e)throw Error(`doom-task requires a session id`);F=e,N=!a.hasUI,M?.update(),V.configureSession(c.resolveSessionKey(e));let r=await c.removeLegacyStoreDirectoryAsync(V.storePath);if(!n())return;let o=c.hasStorePathOverride()?{removed:[],errors:[]}:await c.sweepStoreFilesAsync(V.storePath,i.getStoreTtlMs());if(!n())return;let s=[...r.errors,...o.errors];for(let e of s)w.warn(t.TASK_EVENT.storeSweepFailed,Error(e));if(s.length>0&&a.hasUI&&a.ui.notify(s.join(`
2
2
  `),`warning`),a.hasUI&&W.setUICtx(a.ui),await V.readAsync(n),!n()||(j=V.onExternalChange(H),await U.reconcile(n),!n()))return;H()}catch(e){if(!n())return;throw B(),w.error(t.TASK_EVENT.sessionStartFailed,e),e}},s=(0,y.readDoomReadinessCoordinator)(g);if(!s){let e=o();return I={sessionManager:n.sessionManager,operation:e},e}let l=I?.operation,u=(async()=>{l&&await Promise.allSettled([l]),!(!T||r!==E)&&await s.start(x,`${n.sessionManager.getSessionId()}:${r}`,async e=>(await o(e),{value:void 0})).wait()})();I={sessionManager:n.sessionManager,operation:u},Promise.allSettled([u])}),S.on(`session_compact`,H),S.on(`session_tree`,H),S.on(`tool_execution_end`,e=>{T&&e.toolName===`task`&&H()})}async function C(e){let t=await(0,g.connectDoomCordisHost)(e,x),n=t.root.plugin(w,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function w(e,t){S(e,t.pi)}exports.default=C,exports.taskExtension=C,exports.installTaskRuntime=S;
3
3
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["TASK_CONTEXT_CONTRIBUTION_SOURCE","createTaskErrorReporter","toFailureReporter","createNarrationRequest","DOOM_NARRATION_SERVICE","requireDoomNarrationService","TaskStore","narrateTaskCommit","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","requireDoomContextContributions","createTaskContextContribution","DelegationManager","createNodeDelegationPlatform","getDelegationTimeoutMs","DOOM_DELEGATION_SERVICE","readDoomDelegationService","DOOM_BACKGROUND_WORK_SERVICE","readDoomBackgroundWorkService","BACKGROUND_WORK_PROVIDER","TaskOverlay","getMaxTasks","DOOM_UI_HUB_SERVICE","requireDoomUiHub","COMMAND_NAME","NOTIFY_CUSTOM_TYPE","Text","resolveCollapseKey","resolveSessionKey","removeLegacyStoreDirectoryAsync","hasStorePathOverride","sweepStoreFilesAsync","getStoreTtlMs","TASK_EVENT","readDoomReadinessCoordinator","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type BackgroundWorkProviderHandle,\n readDoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport { DOOM_DELEGATION_SERVICE, readDoomDelegationService } from '@agimon-ai/doompi-extension-contracts/delegation';\nimport {\n createNarrationRequest,\n DOOM_NARRATION_SERVICE,\n type DoomNarrationService,\n requireDoomNarrationService,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport { readDoomReadinessCoordinator } from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { KeyId } from '@earendil-works/pi-tui';\nimport { Text } from '@earendil-works/pi-tui';\nimport { registerTasksCommand } from '../../commands/index.ts';\nimport { registerTaskTool } from '../../commands/task/taskTool.ts';\nimport { COMMAND_NAME, TOOL_NAME } from '../../schemas/task.ts';\nimport { BACKGROUND_WORK_PROVIDER, DelegationManager, NOTIFY_CUSTOM_TYPE } from '../../services/delegation/manager.ts';\nimport { createTaskContextContribution, TASK_CONTEXT_CONTRIBUTION_SOURCE } from '../../services/contextContribution.ts';\nimport { narrateTaskCommit, type TaskNarrationSink } from '../../services/narration/taskNarration.ts';\nimport {\n hasStorePathOverride,\n removeLegacyStoreDirectoryAsync,\n resolveSessionKey,\n sweepStoreFilesAsync,\n} from '../../adapters/store/paths';\nimport { TaskStore } from '../../adapters/store/taskStore';\nimport { TaskOverlay } from '../../tui/taskOverlay.ts';\nimport {\n COLLAPSE_KEY_OFF,\n getDelegationTimeoutMs,\n getMaxTasks,\n getStoreTtlMs,\n resolveCollapseKey,\n} from '../../types/config.ts';\nimport { createNodeDelegationPlatform } from '../node/delegationPlatform.ts';\nimport { createTaskErrorReporter, TASK_EVENT, toFailureReporter } from '../telemetry/logSinkTelemetry.ts';\n\ninterface SessionContextLike {\n hasUI: boolean;\n ui: Parameters<TaskOverlay['setUICtx']>[0];\n sessionManager?: { getSessionId(): string };\n}\n\nconst LEADER_SOURCE = TASK_CONTEXT_CONTRIBUTION_SOURCE;\nconst NARRATION_FAILED_EVENT = 'doom_task.narration_failed';\n/** Between doom-plan's 60 and the core help group's 70, so `t` sits after `p`. */\nconst LEADER_GROUP_ORDER = 65;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_COMPACT_EVENT = 'session_compact';\nconst SESSION_TREE_EVENT = 'session_tree';\nconst TOOL_EXECUTION_END_EVENT = 'tool_execution_end';\n\n/** Install Task state and external resources into its host-owned Cordis plugin fiber. */\nexport function installTaskRuntime(cordis: Context, pi: ExtensionAPI): void {\n const errorReporter = createTaskErrorReporter();\n const report = toFailureReporter(errorReporter);\n let active = true;\n let sessionGeneration = 0;\n let narrationService: DoomNarrationService | undefined;\n let store: TaskStore | undefined;\n let delegation: DelegationManager | undefined;\n let overlay: TaskOverlay | undefined;\n let unwatch: (() => void) | undefined;\n let backgroundWork: BackgroundWorkProviderHandle | undefined;\n let backgroundWorkEnabled = false;\n let disposeLeader: (() => void) | undefined;\n let sessionId: string | undefined;\n let sessionInitialization:\n | {\n readonly sessionManager: object;\n readonly operation: Promise<void>;\n }\n | undefined;\n\n const reportNarrationFailure = (error: unknown): void => {\n void errorReporter.recordWarning(NARRATION_FAILED_EVENT, error).catch((telemetryError: unknown) => {\n process.emitWarning(`Doom-task could not record a narration failure: ${String(telemetryError)}`);\n });\n };\n const narrationSink: TaskNarrationSink = {\n narrate(text) {\n const service = narrationService;\n const request = createNarrationRequest(text);\n if (!service || !request) return;\n try {\n void Promise.resolve(service.request(request)).catch(reportNarrationFailure);\n } catch (error) {\n reportNarrationFailure(error);\n }\n },\n };\n\n cordis.inject([DOOM_NARRATION_SERVICE], (serviceContext) => {\n const service = requireDoomNarrationService(serviceContext);\n narrationService = service;\n return () => {\n if (narrationService === service) narrationService = undefined;\n };\n });\n\n const waitForSessionReadiness = async (\n context: { readonly sessionManager: object },\n signal?: AbortSignal,\n ): Promise<void> => {\n signal?.throwIfAborted();\n const current = sessionInitialization;\n if (!current) throw new Error('doom-task session initialization has not started');\n if (current.sessionManager !== context.sessionManager) {\n throw new Error('doom-task readiness belongs to a stale Pi session');\n }\n await current.operation;\n signal?.throwIfAborted();\n if (!active || current !== sessionInitialization || !sessionId) {\n throw new Error('doom-task readiness belongs to a stale extension generation');\n }\n };\n\n const clearSessionResources = (): void => {\n unwatch?.();\n unwatch = undefined;\n backgroundWorkEnabled = false;\n backgroundWork?.update();\n delegation?.reset();\n overlay?.dispose();\n sessionId = undefined;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingInitialization = sessionInitialization?.operation;\n sessionInitialization = undefined;\n try {\n if (pendingInitialization) await Promise.allSettled([pendingInitialization]);\n clearSessionResources();\n delegation?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n store?.dispose();\n } finally {\n await errorReporter.shutdown();\n }\n },\n LEADER_SOURCE,\n );\n\n const taskStore = new TaskStore({\n report,\n onCommitted: (previous, committed) => narrateTaskCommit(narrationSink, previous, committed),\n });\n store = taskStore;\n\n cordis.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contextContributionsContext) => {\n const registration = requireDoomContextContributions(contextContributionsContext).register(\n createTaskContextContribution(() => taskStore.snapshot.tasks),\n );\n return () => registration.dispose();\n });\n\n const refresh = (): void => {\n if (!active) return;\n overlay?.update();\n backgroundWork?.update();\n };\n\n const delegationManager = new DelegationManager({\n store: taskStore,\n cwd: process.cwd(),\n platform: createNodeDelegationPlatform(),\n notify: (message, options) => pi.sendMessage(message, options),\n getSessionId: () => sessionId,\n onChange: refresh,\n runTimeoutMs: getDelegationTimeoutMs(),\n report,\n onNotifyError: (error, taskId) => {\n void errorReporter.recordNotificationError(error, taskId);\n },\n });\n delegation = delegationManager;\n\n cordis.inject([DOOM_DELEGATION_SERVICE], (serviceContext) => {\n const service = readDoomDelegationService(serviceContext);\n if (!service) return undefined;\n return delegationManager.bind(serviceContext, service);\n });\n\n cordis.inject([DOOM_BACKGROUND_WORK_SERVICE], (serviceContext) => {\n const service = readDoomBackgroundWorkService(serviceContext);\n if (!service) return undefined;\n const handle = service.register({\n provider: BACKGROUND_WORK_PROVIDER,\n listActiveWork: () => (backgroundWorkEnabled ? delegationManager.listActiveWork() : []),\n });\n backgroundWork = handle;\n return () => {\n handle.dispose();\n if (backgroundWork === handle) backgroundWork = undefined;\n };\n });\n\n const taskOverlay = new TaskOverlay({ getTasks: () => taskStore.snapshot.tasks, delegation: delegationManager });\n overlay = taskOverlay;\n\n registerTaskTool(pi, {\n store: taskStore,\n delegation: delegationManager,\n maxTasks: getMaxTasks(),\n onChange: refresh,\n report,\n waitUntilReady: waitForSessionReadiness,\n });\n registerTasksCommand(pi, taskStore, waitForSessionReadiness);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'tasks.open',\n path: [\n { key: 't', label: 'tasks', order: LEADER_GROUP_ORDER },\n { key: 't', label: 'tasks' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n const dispose = (): void => contribution.dispose();\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n pi.registerMessageRenderer(NOTIFY_CUSTOM_TYPE, (message, _options, theme) => {\n const content = typeof message.content === 'string' ? message.content : '';\n return new Text(theme.fg('muted', content), 0, 0);\n });\n\n const collapseKey = resolveCollapseKey();\n if (collapseKey !== COLLAPSE_KEY_OFF) {\n pi.registerShortcut(collapseKey as KeyId, {\n description: 'Collapse or expand the task overlay',\n handler: async (ctx) => {\n if (!active || !ctx.hasUI) return;\n await waitForSessionReadiness(ctx);\n if (!active || !taskOverlay.isRegistered()) return;\n taskOverlay.toggleCollapse();\n },\n });\n }\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n if (!active) return;\n const ownGeneration = ++sessionGeneration;\n if (sessionId !== undefined) clearSessionResources();\n const context = ctx as unknown as SessionContextLike;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n const isCurrent = (): boolean => !signal?.aborted && active && ownGeneration === sessionGeneration;\n try {\n const currentSessionId = context.sessionManager?.getSessionId();\n if (!currentSessionId) throw new Error('doom-task requires a session id');\n sessionId = currentSessionId;\n backgroundWorkEnabled = !context.hasUI;\n backgroundWork?.update();\n taskStore.configureSession(resolveSessionKey(currentSessionId));\n const cleanup = await removeLegacyStoreDirectoryAsync(taskStore.storePath);\n if (!isCurrent()) return;\n const sweep = hasStorePathOverride()\n ? { removed: [], errors: [] }\n : await sweepStoreFilesAsync(taskStore.storePath, getStoreTtlMs());\n if (!isCurrent()) return;\n const storeErrors = [...cleanup.errors, ...sweep.errors];\n for (const failure of storeErrors) report.warn(TASK_EVENT.storeSweepFailed, new Error(failure));\n if (storeErrors.length > 0 && context.hasUI) context.ui.notify(storeErrors.join('\\n'), 'warning');\n if (context.hasUI) taskOverlay.setUICtx(context.ui);\n await taskStore.readAsync(isCurrent);\n if (!isCurrent()) return;\n unwatch = taskStore.onExternalChange(refresh);\n await delegationManager.reconcile(isCurrent);\n if (!isCurrent()) return;\n refresh();\n } catch (error) {\n if (!isCurrent()) return;\n clearSessionResources();\n report.error(TASK_EVENT.sessionStartFailed, error);\n throw error;\n }\n };\n\n const coordinator = readDoomReadinessCoordinator(cordis);\n if (!coordinator) {\n const operation = initialize();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n return operation;\n }\n\n const previous = sessionInitialization?.operation;\n const operation = (async (): Promise<void> => {\n if (previous) await Promise.allSettled([previous]);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n LEADER_SOURCE,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n // Config's coordinator owns the single failure notification. This detached\n // observer only keeps a failed background generation from going unhandled.\n void Promise.allSettled([operation]);\n return undefined;\n });\n\n pi.on(SESSION_COMPACT_EVENT, refresh);\n pi.on(SESSION_TREE_EVENT, refresh);\n pi.on(TOOL_EXECUTION_END_EVENT, (event) => {\n if (active && event.toolName === TOOL_NAME) refresh();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function taskExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(taskPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface TaskPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction taskPlugin(cordis: Context, config: TaskPluginConfig): void {\n installTaskRuntime(cordis, config.pi);\n}\n\nexport default taskExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["TASK_CONTEXT_CONTRIBUTION_SOURCE","createTaskErrorReporter","toFailureReporter","createNarrationRequest","DOOM_NARRATION_SERVICE","requireDoomNarrationService","TaskStore","narrateTaskCommit","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","requireDoomContextContributions","createTaskContextContribution","DelegationManager","createNodeDelegationPlatform","getDelegationTimeoutMs","DOOM_DELEGATION_SERVICE","readDoomDelegationService","DOOM_BACKGROUND_WORK_SERVICE","readDoomBackgroundWorkService","BACKGROUND_WORK_PROVIDER","TaskOverlay","getMaxTasks","DOOM_UI_HUB_SERVICE","requireDoomUiHub","COMMAND_NAME","NOTIFY_CUSTOM_TYPE","Text","resolveCollapseKey","resolveSessionKey","removeLegacyStoreDirectoryAsync","hasStorePathOverride","sweepStoreFilesAsync","getStoreTtlMs","TASK_EVENT","readDoomReadinessCoordinator","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type BackgroundWorkProviderHandle,\n readDoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport { DOOM_DELEGATION_SERVICE, readDoomDelegationService } from '@agimon-ai/doompi-extension-contracts/delegation';\nimport {\n createNarrationRequest,\n DOOM_NARRATION_SERVICE,\n type DoomNarrationService,\n requireDoomNarrationService,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport { readDoomReadinessCoordinator } from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { KeyId } from '@earendil-works/pi-tui';\nimport { Text } from '@earendil-works/pi-tui';\nimport { registerTasksCommand } from '../../commands/index.ts';\nimport { registerTaskTool } from '../../commands/task/taskTool.ts';\nimport { COMMAND_NAME, TOOL_NAME } from '../../schemas/task.ts';\nimport { BACKGROUND_WORK_PROVIDER, DelegationManager, NOTIFY_CUSTOM_TYPE } from '../../services/delegation/manager.ts';\nimport { createTaskContextContribution, TASK_CONTEXT_CONTRIBUTION_SOURCE } from '../../services/contextContribution.ts';\nimport { narrateTaskCommit, type TaskNarrationSink } from '../../services/narration/taskNarration.ts';\nimport {\n hasStorePathOverride,\n removeLegacyStoreDirectoryAsync,\n resolveSessionKey,\n sweepStoreFilesAsync,\n} from '../../adapters/store/paths';\nimport { TaskStore } from '../../adapters/store/taskStore';\nimport { TaskOverlay } from '../../tui/taskOverlay.ts';\nimport {\n COLLAPSE_KEY_OFF,\n getDelegationTimeoutMs,\n getMaxTasks,\n getStoreTtlMs,\n resolveCollapseKey,\n} from '../../types/config.ts';\nimport { createNodeDelegationPlatform } from '../node/delegationPlatform.ts';\nimport { createTaskErrorReporter, TASK_EVENT, toFailureReporter } from '../telemetry/logSinkTelemetry.ts';\n\ninterface SessionContextLike {\n hasUI: boolean;\n ui: Parameters<TaskOverlay['setUICtx']>[0];\n sessionManager?: { getSessionId(): string };\n}\n\nconst LEADER_SOURCE = TASK_CONTEXT_CONTRIBUTION_SOURCE;\nconst NARRATION_FAILED_EVENT = 'doom_task.narration_failed';\n/** Between doom-plan's 60 and the core help group's 70, so `t` sits after `p`. */\nconst LEADER_GROUP_ORDER = 65;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_COMPACT_EVENT = 'session_compact';\nconst SESSION_TREE_EVENT = 'session_tree';\nconst TOOL_EXECUTION_END_EVENT = 'tool_execution_end';\n\n/** Install Task state and external resources into its host-owned Cordis plugin fiber. */\nexport function installTaskRuntime(cordis: Context, pi: ExtensionAPI): void {\n const errorReporter = createTaskErrorReporter();\n const report = toFailureReporter(errorReporter);\n let active = true;\n let sessionGeneration = 0;\n let narrationService: DoomNarrationService | undefined;\n let store: TaskStore | undefined;\n let delegation: DelegationManager | undefined;\n let overlay: TaskOverlay | undefined;\n let unwatch: (() => void) | undefined;\n let backgroundWork: BackgroundWorkProviderHandle | undefined;\n let backgroundWorkEnabled = false;\n let disposeLeader: (() => void) | undefined;\n let sessionId: string | undefined;\n let sessionInitialization:\n | {\n readonly sessionManager: object;\n readonly operation: Promise<void>;\n }\n | undefined;\n\n const reportNarrationFailure = (error: unknown): void => {\n void errorReporter.recordWarning(NARRATION_FAILED_EVENT, error).catch((telemetryError: unknown) => {\n process.emitWarning(`Doom-task could not record a narration failure: ${String(telemetryError)}`);\n });\n };\n const narrationSink: TaskNarrationSink = {\n narrate(text) {\n const service = narrationService;\n const request = createNarrationRequest(text);\n if (!service || !request) return;\n try {\n void Promise.resolve(service.request(request)).catch(reportNarrationFailure);\n } catch (error) {\n reportNarrationFailure(error);\n }\n },\n };\n\n cordis.inject([DOOM_NARRATION_SERVICE], (serviceContext) => {\n const service = requireDoomNarrationService(serviceContext);\n narrationService = service;\n return () => {\n if (narrationService === service) narrationService = undefined;\n };\n });\n\n const waitForSessionReadiness = async (\n context: { readonly sessionManager: object },\n signal?: AbortSignal,\n ): Promise<void> => {\n signal?.throwIfAborted();\n const current = sessionInitialization;\n if (!current) throw new Error('doom-task session initialization has not started');\n if (current.sessionManager !== context.sessionManager) {\n throw new Error('doom-task readiness belongs to a stale Pi session');\n }\n await current.operation;\n signal?.throwIfAborted();\n if (!active || current !== sessionInitialization || !sessionId) {\n throw new Error('doom-task readiness belongs to a stale extension generation');\n }\n };\n\n const clearSessionResources = (): void => {\n unwatch?.();\n unwatch = undefined;\n backgroundWorkEnabled = false;\n backgroundWork?.update();\n delegation?.reset();\n overlay?.dispose();\n sessionId = undefined;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingInitialization = sessionInitialization?.operation;\n sessionInitialization = undefined;\n try {\n if (pendingInitialization) await Promise.allSettled([pendingInitialization]);\n clearSessionResources();\n delegation?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n store?.dispose();\n } finally {\n await errorReporter.shutdown();\n }\n },\n LEADER_SOURCE,\n );\n\n const taskStore = new TaskStore({\n report,\n onCommitted: (previous, committed) => narrateTaskCommit(narrationSink, previous, committed),\n });\n store = taskStore;\n\n cordis.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contextContributionsContext) => {\n const registration = requireDoomContextContributions(contextContributionsContext).register(\n createTaskContextContribution(() => taskStore.snapshot.tasks),\n );\n return () => registration.dispose();\n });\n\n const refresh = (): void => {\n if (!active) return;\n overlay?.update();\n backgroundWork?.update();\n };\n\n const delegationManager = new DelegationManager({\n store: taskStore,\n cwd: process.cwd(),\n platform: createNodeDelegationPlatform(),\n notify: (message, options) => pi.sendMessage(message, options),\n getSessionId: () => sessionId,\n onChange: refresh,\n runTimeoutMs: getDelegationTimeoutMs(),\n report,\n onNotifyError: (error, taskId) => {\n void errorReporter.recordNotificationError(error, taskId);\n },\n });\n delegation = delegationManager;\n\n cordis.inject([DOOM_DELEGATION_SERVICE], (serviceContext) => {\n const service = readDoomDelegationService(serviceContext);\n if (!service) return undefined;\n return delegationManager.bind(serviceContext, service);\n });\n\n cordis.inject([DOOM_BACKGROUND_WORK_SERVICE], (serviceContext) => {\n const service = readDoomBackgroundWorkService(serviceContext);\n if (!service) return undefined;\n const handle = service.register({\n provider: BACKGROUND_WORK_PROVIDER,\n listActiveWork: () => (backgroundWorkEnabled ? delegationManager.listActiveWork() : []),\n });\n backgroundWork = handle;\n return () => {\n handle.dispose();\n if (backgroundWork === handle) backgroundWork = undefined;\n };\n });\n\n const taskOverlay = new TaskOverlay({ getTasks: () => taskStore.snapshot.tasks, delegation: delegationManager });\n overlay = taskOverlay;\n\n registerTaskTool(pi, {\n store: taskStore,\n delegation: delegationManager,\n maxTasks: getMaxTasks(),\n onChange: refresh,\n report,\n waitUntilReady: waitForSessionReadiness,\n });\n registerTasksCommand(pi, taskStore, waitForSessionReadiness);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'tasks.open',\n path: [\n { key: 't', label: 'tasks', detail: 'session task list', order: LEADER_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'tasks in this session' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n const dispose = (): void => contribution.dispose();\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n pi.registerMessageRenderer(NOTIFY_CUSTOM_TYPE, (message, _options, theme) => {\n const content = typeof message.content === 'string' ? message.content : '';\n return new Text(theme.fg('muted', content), 0, 0);\n });\n\n const collapseKey = resolveCollapseKey();\n if (collapseKey !== COLLAPSE_KEY_OFF) {\n pi.registerShortcut(collapseKey as KeyId, {\n description: 'Collapse or expand the task overlay',\n handler: async (ctx) => {\n if (!active || !ctx.hasUI) return;\n await waitForSessionReadiness(ctx);\n if (!active || !taskOverlay.isRegistered()) return;\n taskOverlay.toggleCollapse();\n },\n });\n }\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n if (!active) return;\n const ownGeneration = ++sessionGeneration;\n if (sessionId !== undefined) clearSessionResources();\n const context = ctx as unknown as SessionContextLike;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n const isCurrent = (): boolean => !signal?.aborted && active && ownGeneration === sessionGeneration;\n try {\n const currentSessionId = context.sessionManager?.getSessionId();\n if (!currentSessionId) throw new Error('doom-task requires a session id');\n sessionId = currentSessionId;\n backgroundWorkEnabled = !context.hasUI;\n backgroundWork?.update();\n taskStore.configureSession(resolveSessionKey(currentSessionId));\n const cleanup = await removeLegacyStoreDirectoryAsync(taskStore.storePath);\n if (!isCurrent()) return;\n const sweep = hasStorePathOverride()\n ? { removed: [], errors: [] }\n : await sweepStoreFilesAsync(taskStore.storePath, getStoreTtlMs());\n if (!isCurrent()) return;\n const storeErrors = [...cleanup.errors, ...sweep.errors];\n for (const failure of storeErrors) report.warn(TASK_EVENT.storeSweepFailed, new Error(failure));\n if (storeErrors.length > 0 && context.hasUI) context.ui.notify(storeErrors.join('\\n'), 'warning');\n if (context.hasUI) taskOverlay.setUICtx(context.ui);\n await taskStore.readAsync(isCurrent);\n if (!isCurrent()) return;\n unwatch = taskStore.onExternalChange(refresh);\n await delegationManager.reconcile(isCurrent);\n if (!isCurrent()) return;\n refresh();\n } catch (error) {\n if (!isCurrent()) return;\n clearSessionResources();\n report.error(TASK_EVENT.sessionStartFailed, error);\n throw error;\n }\n };\n\n const coordinator = readDoomReadinessCoordinator(cordis);\n if (!coordinator) {\n const operation = initialize();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n return operation;\n }\n\n const previous = sessionInitialization?.operation;\n const operation = (async (): Promise<void> => {\n if (previous) await Promise.allSettled([previous]);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n LEADER_SOURCE,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n // Config's coordinator owns the single failure notification. This detached\n // observer only keeps a failed background generation from going unhandled.\n void Promise.allSettled([operation]);\n return undefined;\n });\n\n pi.on(SESSION_COMPACT_EVENT, refresh);\n pi.on(SESSION_TREE_EVENT, refresh);\n pi.on(TOOL_EXECUTION_END_EVENT, (event) => {\n if (active && event.toolName === TOOL_NAME) refresh();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function taskExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(taskPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface TaskPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction taskPlugin(cordis: Context, config: TaskPluginConfig): void {\n installTaskRuntime(cordis, config.pi);\n}\n\nexport default taskExtension;\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import{COMMAND_NAME as e}from"../../schemas/task.mjs";import{TASK_EVENT as t}from"../../types/telemetry.mjs";import{BACKGROUND_WORK_PROVIDER as n,DelegationManager as r,NOTIFY_CUSTOM_TYPE as i}from"../../services/delegation/manager.mjs";import{registerTasksCommand as a}from"../../commands/index.mjs";import{getDelegationTimeoutMs as o,getMaxTasks as s,getStoreTtlMs as c,resolveCollapseKey as l}from"../../types/config.mjs";import{registerTaskTool as u}from"../../commands/task/taskTool.mjs";import{TASK_CONTEXT_CONTRIBUTION_SOURCE as d,createTaskContextContribution as f}from"../../services/contextContribution.mjs";import{narrateTaskCommit as p}from"../../services/narration/taskNarration.mjs";import{hasStorePathOverride as m,removeLegacyStoreDirectoryAsync as h,resolveSessionKey as g,sweepStoreFilesAsync as _}from"../store/paths.mjs";import{TaskStore as v}from"../store/taskStore.mjs";import{TaskOverlay as y}from"../../tui/taskOverlay2.mjs";import{createNodeDelegationPlatform as b}from"../node/delegationPlatform.mjs";import{createTaskErrorReporter as ee,toFailureReporter as te}from"../telemetry/logSinkTelemetry.mjs";import{Text as ne}from"@earendil-works/pi-tui";import{DOOM_DELEGATION_SERVICE as re,readDoomDelegationService as x}from"@agimon-ai/doompi-extension-contracts/delegation";import{DOOM_BACKGROUND_WORK_SERVICE as S,readDoomBackgroundWorkService as C}from"@agimon-ai/doompi-extension-contracts/background-work";import{connectDoomCordisHost as w}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_CONTEXT_CONTRIBUTIONS_SERVICE as T,requireDoomContextContributions as E}from"@agimon-ai/doompi-extension-contracts/context-contributions";import{DOOM_NARRATION_SERVICE as ie,createNarrationRequest as ae,requireDoomNarrationService as D}from"@agimon-ai/doompi-extension-contracts/narration";import{readDoomReadinessCoordinator as O}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as k,requireDoomUiHub as A}from"@agimon-ai/doompi-extension-contracts/ui-hub";const j=d;function M(d,w){let M=ee(),N=te(M),P=!0,F=0,I,L,R,z,B,V,H=!1,U,W,G,K=e=>{M.recordWarning(`doom_task.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-task could not record a narration failure: ${String(e)}`)})},oe={narrate(e){let t=I,n=ae(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(K)}catch(e){K(e)}}};d.inject([ie],e=>{let t=D(e);return I=t,()=>{I===t&&(I=void 0)}});let q=async(e,t)=>{t?.throwIfAborted();let n=G;if(!n)throw Error(`doom-task session initialization has not started`);if(n.sessionManager!==e.sessionManager)throw Error(`doom-task readiness belongs to a stale Pi session`);if(await n.operation,t?.throwIfAborted(),!P||n!==G||!W)throw Error(`doom-task readiness belongs to a stale extension generation`)},J=()=>{B?.(),B=void 0,H=!1,V?.update(),R?.reset(),z?.dispose(),W=void 0};d.effect(()=>async()=>{P=!1,F+=1;let e=G?.operation;G=void 0;try{e&&await Promise.allSettled([e]),J(),R?.dispose(),U?.(),U=void 0,L?.dispose()}finally{await M.shutdown()}},j);let Y=new v({report:N,onCommitted:(e,t)=>p(oe,e,t)});L=Y,d.inject([T],e=>{let t=E(e).register(f(()=>Y.snapshot.tasks));return()=>t.dispose()});let X=()=>{P&&(z?.update(),V?.update())},Z=new r({store:Y,cwd:process.cwd(),platform:b(),notify:(e,t)=>w.sendMessage(e,t),getSessionId:()=>W,onChange:X,runTimeoutMs:o(),report:N,onNotifyError:(e,t)=>{M.recordNotificationError(e,t)}});R=Z,d.inject([re],e=>{let t=x(e);if(t)return Z.bind(e,t)}),d.inject([S],e=>{let t=C(e);if(!t)return;let r=t.register({provider:n,listActiveWork:()=>H?Z.listActiveWork():[]});return V=r,()=>{r.dispose(),V===r&&(V=void 0)}});let Q=new y({getTasks:()=>Y.snapshot.tasks,delegation:Z});z=Q,u(w,{store:Y,delegation:Z,maxTasks:s(),onChange:X,report:N,waitUntilReady:q}),a(w,Y,q),d.inject([k],t=>{let n=A(t).registerLeader({source:j,bindings:[{id:`tasks.open`,path:[{key:`t`,label:`tasks`,order:65},{key:`t`,label:`tasks`}],command:{name:e}}]}),r=()=>n.dispose();return U=r,()=>{r(),U===r&&(U=void 0)}}),w.registerMessageRenderer(i,(e,t,n)=>{let r=typeof e.content==`string`?e.content:``;return new ne(n.fg(`muted`,r),0,0)});let $=l();$!==`off`&&w.registerShortcut($,{description:`Collapse or expand the task overlay`,handler:async e=>{!P||!e.hasUI||(await q(e),!(!P||!Q.isRegistered())&&Q.toggleCollapse())}}),w.on(`session_start`,async(e,n)=>{if(!P)return;let r=++F;W!==void 0&&J();let i=n,a=async e=>{let n=()=>!e?.aborted&&P&&r===F;try{let e=i.sessionManager?.getSessionId();if(!e)throw Error(`doom-task requires a session id`);W=e,H=!i.hasUI,V?.update(),Y.configureSession(g(e));let r=await h(Y.storePath);if(!n())return;let a=m()?{removed:[],errors:[]}:await _(Y.storePath,c());if(!n())return;let o=[...r.errors,...a.errors];for(let e of o)N.warn(t.storeSweepFailed,Error(e));if(o.length>0&&i.hasUI&&i.ui.notify(o.join(`
1
+ import{COMMAND_NAME as e}from"../../schemas/task.mjs";import{TASK_EVENT as t}from"../../types/telemetry.mjs";import{BACKGROUND_WORK_PROVIDER as n,DelegationManager as r,NOTIFY_CUSTOM_TYPE as i}from"../../services/delegation/manager.mjs";import{registerTasksCommand as a}from"../../commands/index.mjs";import{getDelegationTimeoutMs as o,getMaxTasks as s,getStoreTtlMs as c,resolveCollapseKey as l}from"../../types/config.mjs";import{registerTaskTool as u}from"../../commands/task/taskTool.mjs";import{TASK_CONTEXT_CONTRIBUTION_SOURCE as d,createTaskContextContribution as f}from"../../services/contextContribution.mjs";import{narrateTaskCommit as p}from"../../services/narration/taskNarration.mjs";import{hasStorePathOverride as m,removeLegacyStoreDirectoryAsync as h,resolveSessionKey as g,sweepStoreFilesAsync as _}from"../store/paths.mjs";import{TaskStore as v}from"../store/taskStore.mjs";import{TaskOverlay as y}from"../../tui/taskOverlay2.mjs";import{createNodeDelegationPlatform as b}from"../node/delegationPlatform.mjs";import{createTaskErrorReporter as ee,toFailureReporter as te}from"../telemetry/logSinkTelemetry.mjs";import{Text as ne}from"@earendil-works/pi-tui";import{DOOM_DELEGATION_SERVICE as re,readDoomDelegationService as x}from"@agimon-ai/doompi-extension-contracts/delegation";import{DOOM_BACKGROUND_WORK_SERVICE as S,readDoomBackgroundWorkService as C}from"@agimon-ai/doompi-extension-contracts/background-work";import{connectDoomCordisHost as w}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_CONTEXT_CONTRIBUTIONS_SERVICE as T,requireDoomContextContributions as E}from"@agimon-ai/doompi-extension-contracts/context-contributions";import{DOOM_NARRATION_SERVICE as ie,createNarrationRequest as ae,requireDoomNarrationService as D}from"@agimon-ai/doompi-extension-contracts/narration";import{readDoomReadinessCoordinator as O}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as k,requireDoomUiHub as A}from"@agimon-ai/doompi-extension-contracts/ui-hub";const j=d;function M(d,w){let M=ee(),N=te(M),P=!0,F=0,I,L,R,z,B,V,H=!1,U,W,G,K=e=>{M.recordWarning(`doom_task.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-task could not record a narration failure: ${String(e)}`)})},oe={narrate(e){let t=I,n=ae(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(K)}catch(e){K(e)}}};d.inject([ie],e=>{let t=D(e);return I=t,()=>{I===t&&(I=void 0)}});let q=async(e,t)=>{t?.throwIfAborted();let n=G;if(!n)throw Error(`doom-task session initialization has not started`);if(n.sessionManager!==e.sessionManager)throw Error(`doom-task readiness belongs to a stale Pi session`);if(await n.operation,t?.throwIfAborted(),!P||n!==G||!W)throw Error(`doom-task readiness belongs to a stale extension generation`)},J=()=>{B?.(),B=void 0,H=!1,V?.update(),R?.reset(),z?.dispose(),W=void 0};d.effect(()=>async()=>{P=!1,F+=1;let e=G?.operation;G=void 0;try{e&&await Promise.allSettled([e]),J(),R?.dispose(),U?.(),U=void 0,L?.dispose()}finally{await M.shutdown()}},j);let Y=new v({report:N,onCommitted:(e,t)=>p(oe,e,t)});L=Y,d.inject([T],e=>{let t=E(e).register(f(()=>Y.snapshot.tasks));return()=>t.dispose()});let X=()=>{P&&(z?.update(),V?.update())},Z=new r({store:Y,cwd:process.cwd(),platform:b(),notify:(e,t)=>w.sendMessage(e,t),getSessionId:()=>W,onChange:X,runTimeoutMs:o(),report:N,onNotifyError:(e,t)=>{M.recordNotificationError(e,t)}});R=Z,d.inject([re],e=>{let t=x(e);if(t)return Z.bind(e,t)}),d.inject([S],e=>{let t=C(e);if(!t)return;let r=t.register({provider:n,listActiveWork:()=>H?Z.listActiveWork():[]});return V=r,()=>{r.dispose(),V===r&&(V=void 0)}});let Q=new y({getTasks:()=>Y.snapshot.tasks,delegation:Z});z=Q,u(w,{store:Y,delegation:Z,maxTasks:s(),onChange:X,report:N,waitUntilReady:q}),a(w,Y,q),d.inject([k],t=>{let n=A(t).registerLeader({source:j,bindings:[{id:`tasks.open`,path:[{key:`t`,label:`tasks`,detail:`session task list`,order:65},{key:`l`,label:`list`,detail:`tasks in this session`}],command:{name:e}}]}),r=()=>n.dispose();return U=r,()=>{r(),U===r&&(U=void 0)}}),w.registerMessageRenderer(i,(e,t,n)=>{let r=typeof e.content==`string`?e.content:``;return new ne(n.fg(`muted`,r),0,0)});let $=l();$!==`off`&&w.registerShortcut($,{description:`Collapse or expand the task overlay`,handler:async e=>{!P||!e.hasUI||(await q(e),!(!P||!Q.isRegistered())&&Q.toggleCollapse())}}),w.on(`session_start`,async(e,n)=>{if(!P)return;let r=++F;W!==void 0&&J();let i=n,a=async e=>{let n=()=>!e?.aborted&&P&&r===F;try{let e=i.sessionManager?.getSessionId();if(!e)throw Error(`doom-task requires a session id`);W=e,H=!i.hasUI,V?.update(),Y.configureSession(g(e));let r=await h(Y.storePath);if(!n())return;let a=m()?{removed:[],errors:[]}:await _(Y.storePath,c());if(!n())return;let o=[...r.errors,...a.errors];for(let e of o)N.warn(t.storeSweepFailed,Error(e));if(o.length>0&&i.hasUI&&i.ui.notify(o.join(`
2
2
  `),`warning`),i.hasUI&&Q.setUICtx(i.ui),await Y.readAsync(n),!n()||(B=Y.onExternalChange(X),await Z.reconcile(n),!n()))return;X()}catch(e){if(!n())return;throw J(),N.error(t.sessionStartFailed,e),e}},o=O(d);if(!o){let e=a();return G={sessionManager:n.sessionManager,operation:e},e}let s=G?.operation,l=(async()=>{s&&await Promise.allSettled([s]),!(!P||r!==F)&&await o.start(j,`${n.sessionManager.getSessionId()}:${r}`,async e=>(await a(e),{value:void 0})).wait()})();G={sessionManager:n.sessionManager,operation:l},Promise.allSettled([l])}),w.on(`session_compact`,X),w.on(`session_tree`,X),w.on(`tool_execution_end`,e=>{P&&e.toolName===`task`&&X()})}async function N(e){let t=await w(e,j),n=t.root.plugin(P,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function P(e,t){M(e,t.pi)}export{N as default,N as taskExtension,M as installTaskRuntime};
3
3
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type BackgroundWorkProviderHandle,\n readDoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport { DOOM_DELEGATION_SERVICE, readDoomDelegationService } from '@agimon-ai/doompi-extension-contracts/delegation';\nimport {\n createNarrationRequest,\n DOOM_NARRATION_SERVICE,\n type DoomNarrationService,\n requireDoomNarrationService,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport { readDoomReadinessCoordinator } from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { KeyId } from '@earendil-works/pi-tui';\nimport { Text } from '@earendil-works/pi-tui';\nimport { registerTasksCommand } from '../../commands/index.ts';\nimport { registerTaskTool } from '../../commands/task/taskTool.ts';\nimport { COMMAND_NAME, TOOL_NAME } from '../../schemas/task.ts';\nimport { BACKGROUND_WORK_PROVIDER, DelegationManager, NOTIFY_CUSTOM_TYPE } from '../../services/delegation/manager.ts';\nimport { createTaskContextContribution, TASK_CONTEXT_CONTRIBUTION_SOURCE } from '../../services/contextContribution.ts';\nimport { narrateTaskCommit, type TaskNarrationSink } from '../../services/narration/taskNarration.ts';\nimport {\n hasStorePathOverride,\n removeLegacyStoreDirectoryAsync,\n resolveSessionKey,\n sweepStoreFilesAsync,\n} from '../../adapters/store/paths';\nimport { TaskStore } from '../../adapters/store/taskStore';\nimport { TaskOverlay } from '../../tui/taskOverlay.ts';\nimport {\n COLLAPSE_KEY_OFF,\n getDelegationTimeoutMs,\n getMaxTasks,\n getStoreTtlMs,\n resolveCollapseKey,\n} from '../../types/config.ts';\nimport { createNodeDelegationPlatform } from '../node/delegationPlatform.ts';\nimport { createTaskErrorReporter, TASK_EVENT, toFailureReporter } from '../telemetry/logSinkTelemetry.ts';\n\ninterface SessionContextLike {\n hasUI: boolean;\n ui: Parameters<TaskOverlay['setUICtx']>[0];\n sessionManager?: { getSessionId(): string };\n}\n\nconst LEADER_SOURCE = TASK_CONTEXT_CONTRIBUTION_SOURCE;\nconst NARRATION_FAILED_EVENT = 'doom_task.narration_failed';\n/** Between doom-plan's 60 and the core help group's 70, so `t` sits after `p`. */\nconst LEADER_GROUP_ORDER = 65;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_COMPACT_EVENT = 'session_compact';\nconst SESSION_TREE_EVENT = 'session_tree';\nconst TOOL_EXECUTION_END_EVENT = 'tool_execution_end';\n\n/** Install Task state and external resources into its host-owned Cordis plugin fiber. */\nexport function installTaskRuntime(cordis: Context, pi: ExtensionAPI): void {\n const errorReporter = createTaskErrorReporter();\n const report = toFailureReporter(errorReporter);\n let active = true;\n let sessionGeneration = 0;\n let narrationService: DoomNarrationService | undefined;\n let store: TaskStore | undefined;\n let delegation: DelegationManager | undefined;\n let overlay: TaskOverlay | undefined;\n let unwatch: (() => void) | undefined;\n let backgroundWork: BackgroundWorkProviderHandle | undefined;\n let backgroundWorkEnabled = false;\n let disposeLeader: (() => void) | undefined;\n let sessionId: string | undefined;\n let sessionInitialization:\n | {\n readonly sessionManager: object;\n readonly operation: Promise<void>;\n }\n | undefined;\n\n const reportNarrationFailure = (error: unknown): void => {\n void errorReporter.recordWarning(NARRATION_FAILED_EVENT, error).catch((telemetryError: unknown) => {\n process.emitWarning(`Doom-task could not record a narration failure: ${String(telemetryError)}`);\n });\n };\n const narrationSink: TaskNarrationSink = {\n narrate(text) {\n const service = narrationService;\n const request = createNarrationRequest(text);\n if (!service || !request) return;\n try {\n void Promise.resolve(service.request(request)).catch(reportNarrationFailure);\n } catch (error) {\n reportNarrationFailure(error);\n }\n },\n };\n\n cordis.inject([DOOM_NARRATION_SERVICE], (serviceContext) => {\n const service = requireDoomNarrationService(serviceContext);\n narrationService = service;\n return () => {\n if (narrationService === service) narrationService = undefined;\n };\n });\n\n const waitForSessionReadiness = async (\n context: { readonly sessionManager: object },\n signal?: AbortSignal,\n ): Promise<void> => {\n signal?.throwIfAborted();\n const current = sessionInitialization;\n if (!current) throw new Error('doom-task session initialization has not started');\n if (current.sessionManager !== context.sessionManager) {\n throw new Error('doom-task readiness belongs to a stale Pi session');\n }\n await current.operation;\n signal?.throwIfAborted();\n if (!active || current !== sessionInitialization || !sessionId) {\n throw new Error('doom-task readiness belongs to a stale extension generation');\n }\n };\n\n const clearSessionResources = (): void => {\n unwatch?.();\n unwatch = undefined;\n backgroundWorkEnabled = false;\n backgroundWork?.update();\n delegation?.reset();\n overlay?.dispose();\n sessionId = undefined;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingInitialization = sessionInitialization?.operation;\n sessionInitialization = undefined;\n try {\n if (pendingInitialization) await Promise.allSettled([pendingInitialization]);\n clearSessionResources();\n delegation?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n store?.dispose();\n } finally {\n await errorReporter.shutdown();\n }\n },\n LEADER_SOURCE,\n );\n\n const taskStore = new TaskStore({\n report,\n onCommitted: (previous, committed) => narrateTaskCommit(narrationSink, previous, committed),\n });\n store = taskStore;\n\n cordis.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contextContributionsContext) => {\n const registration = requireDoomContextContributions(contextContributionsContext).register(\n createTaskContextContribution(() => taskStore.snapshot.tasks),\n );\n return () => registration.dispose();\n });\n\n const refresh = (): void => {\n if (!active) return;\n overlay?.update();\n backgroundWork?.update();\n };\n\n const delegationManager = new DelegationManager({\n store: taskStore,\n cwd: process.cwd(),\n platform: createNodeDelegationPlatform(),\n notify: (message, options) => pi.sendMessage(message, options),\n getSessionId: () => sessionId,\n onChange: refresh,\n runTimeoutMs: getDelegationTimeoutMs(),\n report,\n onNotifyError: (error, taskId) => {\n void errorReporter.recordNotificationError(error, taskId);\n },\n });\n delegation = delegationManager;\n\n cordis.inject([DOOM_DELEGATION_SERVICE], (serviceContext) => {\n const service = readDoomDelegationService(serviceContext);\n if (!service) return undefined;\n return delegationManager.bind(serviceContext, service);\n });\n\n cordis.inject([DOOM_BACKGROUND_WORK_SERVICE], (serviceContext) => {\n const service = readDoomBackgroundWorkService(serviceContext);\n if (!service) return undefined;\n const handle = service.register({\n provider: BACKGROUND_WORK_PROVIDER,\n listActiveWork: () => (backgroundWorkEnabled ? delegationManager.listActiveWork() : []),\n });\n backgroundWork = handle;\n return () => {\n handle.dispose();\n if (backgroundWork === handle) backgroundWork = undefined;\n };\n });\n\n const taskOverlay = new TaskOverlay({ getTasks: () => taskStore.snapshot.tasks, delegation: delegationManager });\n overlay = taskOverlay;\n\n registerTaskTool(pi, {\n store: taskStore,\n delegation: delegationManager,\n maxTasks: getMaxTasks(),\n onChange: refresh,\n report,\n waitUntilReady: waitForSessionReadiness,\n });\n registerTasksCommand(pi, taskStore, waitForSessionReadiness);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'tasks.open',\n path: [\n { key: 't', label: 'tasks', order: LEADER_GROUP_ORDER },\n { key: 't', label: 'tasks' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n const dispose = (): void => contribution.dispose();\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n pi.registerMessageRenderer(NOTIFY_CUSTOM_TYPE, (message, _options, theme) => {\n const content = typeof message.content === 'string' ? message.content : '';\n return new Text(theme.fg('muted', content), 0, 0);\n });\n\n const collapseKey = resolveCollapseKey();\n if (collapseKey !== COLLAPSE_KEY_OFF) {\n pi.registerShortcut(collapseKey as KeyId, {\n description: 'Collapse or expand the task overlay',\n handler: async (ctx) => {\n if (!active || !ctx.hasUI) return;\n await waitForSessionReadiness(ctx);\n if (!active || !taskOverlay.isRegistered()) return;\n taskOverlay.toggleCollapse();\n },\n });\n }\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n if (!active) return;\n const ownGeneration = ++sessionGeneration;\n if (sessionId !== undefined) clearSessionResources();\n const context = ctx as unknown as SessionContextLike;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n const isCurrent = (): boolean => !signal?.aborted && active && ownGeneration === sessionGeneration;\n try {\n const currentSessionId = context.sessionManager?.getSessionId();\n if (!currentSessionId) throw new Error('doom-task requires a session id');\n sessionId = currentSessionId;\n backgroundWorkEnabled = !context.hasUI;\n backgroundWork?.update();\n taskStore.configureSession(resolveSessionKey(currentSessionId));\n const cleanup = await removeLegacyStoreDirectoryAsync(taskStore.storePath);\n if (!isCurrent()) return;\n const sweep = hasStorePathOverride()\n ? { removed: [], errors: [] }\n : await sweepStoreFilesAsync(taskStore.storePath, getStoreTtlMs());\n if (!isCurrent()) return;\n const storeErrors = [...cleanup.errors, ...sweep.errors];\n for (const failure of storeErrors) report.warn(TASK_EVENT.storeSweepFailed, new Error(failure));\n if (storeErrors.length > 0 && context.hasUI) context.ui.notify(storeErrors.join('\\n'), 'warning');\n if (context.hasUI) taskOverlay.setUICtx(context.ui);\n await taskStore.readAsync(isCurrent);\n if (!isCurrent()) return;\n unwatch = taskStore.onExternalChange(refresh);\n await delegationManager.reconcile(isCurrent);\n if (!isCurrent()) return;\n refresh();\n } catch (error) {\n if (!isCurrent()) return;\n clearSessionResources();\n report.error(TASK_EVENT.sessionStartFailed, error);\n throw error;\n }\n };\n\n const coordinator = readDoomReadinessCoordinator(cordis);\n if (!coordinator) {\n const operation = initialize();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n return operation;\n }\n\n const previous = sessionInitialization?.operation;\n const operation = (async (): Promise<void> => {\n if (previous) await Promise.allSettled([previous]);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n LEADER_SOURCE,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n // Config's coordinator owns the single failure notification. This detached\n // observer only keeps a failed background generation from going unhandled.\n void Promise.allSettled([operation]);\n return undefined;\n });\n\n pi.on(SESSION_COMPACT_EVENT, refresh);\n pi.on(SESSION_TREE_EVENT, refresh);\n pi.on(TOOL_EXECUTION_END_EVENT, (event) => {\n if (active && event.toolName === TOOL_NAME) refresh();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function taskExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(taskPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface TaskPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction taskPlugin(cordis: Context, config: TaskPluginConfig): void {\n installTaskRuntime(cordis, config.pi);\n}\n\nexport default taskExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type BackgroundWorkProviderHandle,\n readDoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport { DOOM_DELEGATION_SERVICE, readDoomDelegationService } from '@agimon-ai/doompi-extension-contracts/delegation';\nimport {\n createNarrationRequest,\n DOOM_NARRATION_SERVICE,\n type DoomNarrationService,\n requireDoomNarrationService,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport { readDoomReadinessCoordinator } from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { KeyId } from '@earendil-works/pi-tui';\nimport { Text } from '@earendil-works/pi-tui';\nimport { registerTasksCommand } from '../../commands/index.ts';\nimport { registerTaskTool } from '../../commands/task/taskTool.ts';\nimport { COMMAND_NAME, TOOL_NAME } from '../../schemas/task.ts';\nimport { BACKGROUND_WORK_PROVIDER, DelegationManager, NOTIFY_CUSTOM_TYPE } from '../../services/delegation/manager.ts';\nimport { createTaskContextContribution, TASK_CONTEXT_CONTRIBUTION_SOURCE } from '../../services/contextContribution.ts';\nimport { narrateTaskCommit, type TaskNarrationSink } from '../../services/narration/taskNarration.ts';\nimport {\n hasStorePathOverride,\n removeLegacyStoreDirectoryAsync,\n resolveSessionKey,\n sweepStoreFilesAsync,\n} from '../../adapters/store/paths';\nimport { TaskStore } from '../../adapters/store/taskStore';\nimport { TaskOverlay } from '../../tui/taskOverlay.ts';\nimport {\n COLLAPSE_KEY_OFF,\n getDelegationTimeoutMs,\n getMaxTasks,\n getStoreTtlMs,\n resolveCollapseKey,\n} from '../../types/config.ts';\nimport { createNodeDelegationPlatform } from '../node/delegationPlatform.ts';\nimport { createTaskErrorReporter, TASK_EVENT, toFailureReporter } from '../telemetry/logSinkTelemetry.ts';\n\ninterface SessionContextLike {\n hasUI: boolean;\n ui: Parameters<TaskOverlay['setUICtx']>[0];\n sessionManager?: { getSessionId(): string };\n}\n\nconst LEADER_SOURCE = TASK_CONTEXT_CONTRIBUTION_SOURCE;\nconst NARRATION_FAILED_EVENT = 'doom_task.narration_failed';\n/** Between doom-plan's 60 and the core help group's 70, so `t` sits after `p`. */\nconst LEADER_GROUP_ORDER = 65;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_COMPACT_EVENT = 'session_compact';\nconst SESSION_TREE_EVENT = 'session_tree';\nconst TOOL_EXECUTION_END_EVENT = 'tool_execution_end';\n\n/** Install Task state and external resources into its host-owned Cordis plugin fiber. */\nexport function installTaskRuntime(cordis: Context, pi: ExtensionAPI): void {\n const errorReporter = createTaskErrorReporter();\n const report = toFailureReporter(errorReporter);\n let active = true;\n let sessionGeneration = 0;\n let narrationService: DoomNarrationService | undefined;\n let store: TaskStore | undefined;\n let delegation: DelegationManager | undefined;\n let overlay: TaskOverlay | undefined;\n let unwatch: (() => void) | undefined;\n let backgroundWork: BackgroundWorkProviderHandle | undefined;\n let backgroundWorkEnabled = false;\n let disposeLeader: (() => void) | undefined;\n let sessionId: string | undefined;\n let sessionInitialization:\n | {\n readonly sessionManager: object;\n readonly operation: Promise<void>;\n }\n | undefined;\n\n const reportNarrationFailure = (error: unknown): void => {\n void errorReporter.recordWarning(NARRATION_FAILED_EVENT, error).catch((telemetryError: unknown) => {\n process.emitWarning(`Doom-task could not record a narration failure: ${String(telemetryError)}`);\n });\n };\n const narrationSink: TaskNarrationSink = {\n narrate(text) {\n const service = narrationService;\n const request = createNarrationRequest(text);\n if (!service || !request) return;\n try {\n void Promise.resolve(service.request(request)).catch(reportNarrationFailure);\n } catch (error) {\n reportNarrationFailure(error);\n }\n },\n };\n\n cordis.inject([DOOM_NARRATION_SERVICE], (serviceContext) => {\n const service = requireDoomNarrationService(serviceContext);\n narrationService = service;\n return () => {\n if (narrationService === service) narrationService = undefined;\n };\n });\n\n const waitForSessionReadiness = async (\n context: { readonly sessionManager: object },\n signal?: AbortSignal,\n ): Promise<void> => {\n signal?.throwIfAborted();\n const current = sessionInitialization;\n if (!current) throw new Error('doom-task session initialization has not started');\n if (current.sessionManager !== context.sessionManager) {\n throw new Error('doom-task readiness belongs to a stale Pi session');\n }\n await current.operation;\n signal?.throwIfAborted();\n if (!active || current !== sessionInitialization || !sessionId) {\n throw new Error('doom-task readiness belongs to a stale extension generation');\n }\n };\n\n const clearSessionResources = (): void => {\n unwatch?.();\n unwatch = undefined;\n backgroundWorkEnabled = false;\n backgroundWork?.update();\n delegation?.reset();\n overlay?.dispose();\n sessionId = undefined;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingInitialization = sessionInitialization?.operation;\n sessionInitialization = undefined;\n try {\n if (pendingInitialization) await Promise.allSettled([pendingInitialization]);\n clearSessionResources();\n delegation?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n store?.dispose();\n } finally {\n await errorReporter.shutdown();\n }\n },\n LEADER_SOURCE,\n );\n\n const taskStore = new TaskStore({\n report,\n onCommitted: (previous, committed) => narrateTaskCommit(narrationSink, previous, committed),\n });\n store = taskStore;\n\n cordis.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contextContributionsContext) => {\n const registration = requireDoomContextContributions(contextContributionsContext).register(\n createTaskContextContribution(() => taskStore.snapshot.tasks),\n );\n return () => registration.dispose();\n });\n\n const refresh = (): void => {\n if (!active) return;\n overlay?.update();\n backgroundWork?.update();\n };\n\n const delegationManager = new DelegationManager({\n store: taskStore,\n cwd: process.cwd(),\n platform: createNodeDelegationPlatform(),\n notify: (message, options) => pi.sendMessage(message, options),\n getSessionId: () => sessionId,\n onChange: refresh,\n runTimeoutMs: getDelegationTimeoutMs(),\n report,\n onNotifyError: (error, taskId) => {\n void errorReporter.recordNotificationError(error, taskId);\n },\n });\n delegation = delegationManager;\n\n cordis.inject([DOOM_DELEGATION_SERVICE], (serviceContext) => {\n const service = readDoomDelegationService(serviceContext);\n if (!service) return undefined;\n return delegationManager.bind(serviceContext, service);\n });\n\n cordis.inject([DOOM_BACKGROUND_WORK_SERVICE], (serviceContext) => {\n const service = readDoomBackgroundWorkService(serviceContext);\n if (!service) return undefined;\n const handle = service.register({\n provider: BACKGROUND_WORK_PROVIDER,\n listActiveWork: () => (backgroundWorkEnabled ? delegationManager.listActiveWork() : []),\n });\n backgroundWork = handle;\n return () => {\n handle.dispose();\n if (backgroundWork === handle) backgroundWork = undefined;\n };\n });\n\n const taskOverlay = new TaskOverlay({ getTasks: () => taskStore.snapshot.tasks, delegation: delegationManager });\n overlay = taskOverlay;\n\n registerTaskTool(pi, {\n store: taskStore,\n delegation: delegationManager,\n maxTasks: getMaxTasks(),\n onChange: refresh,\n report,\n waitUntilReady: waitForSessionReadiness,\n });\n registerTasksCommand(pi, taskStore, waitForSessionReadiness);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'tasks.open',\n path: [\n { key: 't', label: 'tasks', detail: 'session task list', order: LEADER_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'tasks in this session' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n const dispose = (): void => contribution.dispose();\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n pi.registerMessageRenderer(NOTIFY_CUSTOM_TYPE, (message, _options, theme) => {\n const content = typeof message.content === 'string' ? message.content : '';\n return new Text(theme.fg('muted', content), 0, 0);\n });\n\n const collapseKey = resolveCollapseKey();\n if (collapseKey !== COLLAPSE_KEY_OFF) {\n pi.registerShortcut(collapseKey as KeyId, {\n description: 'Collapse or expand the task overlay',\n handler: async (ctx) => {\n if (!active || !ctx.hasUI) return;\n await waitForSessionReadiness(ctx);\n if (!active || !taskOverlay.isRegistered()) return;\n taskOverlay.toggleCollapse();\n },\n });\n }\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n if (!active) return;\n const ownGeneration = ++sessionGeneration;\n if (sessionId !== undefined) clearSessionResources();\n const context = ctx as unknown as SessionContextLike;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n const isCurrent = (): boolean => !signal?.aborted && active && ownGeneration === sessionGeneration;\n try {\n const currentSessionId = context.sessionManager?.getSessionId();\n if (!currentSessionId) throw new Error('doom-task requires a session id');\n sessionId = currentSessionId;\n backgroundWorkEnabled = !context.hasUI;\n backgroundWork?.update();\n taskStore.configureSession(resolveSessionKey(currentSessionId));\n const cleanup = await removeLegacyStoreDirectoryAsync(taskStore.storePath);\n if (!isCurrent()) return;\n const sweep = hasStorePathOverride()\n ? { removed: [], errors: [] }\n : await sweepStoreFilesAsync(taskStore.storePath, getStoreTtlMs());\n if (!isCurrent()) return;\n const storeErrors = [...cleanup.errors, ...sweep.errors];\n for (const failure of storeErrors) report.warn(TASK_EVENT.storeSweepFailed, new Error(failure));\n if (storeErrors.length > 0 && context.hasUI) context.ui.notify(storeErrors.join('\\n'), 'warning');\n if (context.hasUI) taskOverlay.setUICtx(context.ui);\n await taskStore.readAsync(isCurrent);\n if (!isCurrent()) return;\n unwatch = taskStore.onExternalChange(refresh);\n await delegationManager.reconcile(isCurrent);\n if (!isCurrent()) return;\n refresh();\n } catch (error) {\n if (!isCurrent()) return;\n clearSessionResources();\n report.error(TASK_EVENT.sessionStartFailed, error);\n throw error;\n }\n };\n\n const coordinator = readDoomReadinessCoordinator(cordis);\n if (!coordinator) {\n const operation = initialize();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n return operation;\n }\n\n const previous = sessionInitialization?.operation;\n const operation = (async (): Promise<void> => {\n if (previous) await Promise.allSettled([previous]);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n LEADER_SOURCE,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n sessionInitialization = { sessionManager: ctx.sessionManager, operation };\n // Config's coordinator owns the single failure notification. This detached\n // observer only keeps a failed background generation from going unhandled.\n void Promise.allSettled([operation]);\n return undefined;\n });\n\n pi.on(SESSION_COMPACT_EVENT, refresh);\n pi.on(SESSION_TREE_EVENT, refresh);\n pi.on(TOOL_EXECUTION_END_EVENT, (event) => {\n if (active && event.toolName === TOOL_NAME) refresh();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function taskExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(taskPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface TaskPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction taskPlugin(cordis: Context, config: TaskPluginConfig): void {\n installTaskRuntime(cordis, config.pi);\n}\n\nexport default taskExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["COMMAND_NAME","ERR_REQUIRES_INTERACTIVE","openTaskSpace"],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME, ERR_REQUIRES_INTERACTIVE } from '../schemas/task.ts';\nimport type { TaskStore } from '../adapters/store/taskStore';\nimport { openTaskSpace } from '../tui/taskSpace.ts';\n\n/**\n * `/tasks`, reachable from the leader palette as `SPC t t`.\n *\n * The command opens the Task Space overlay rather than printing a listing: the\n * overlay is the readable surface and it can edit, while a transcript dump could\n * only be read.\n */\nexport function registerTasksCommand(\n pi: ExtensionAPI,\n store: TaskStore,\n waitUntilReady?: (context: ExtensionContext) => Promise<void>,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Task Space for this session tree',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n\n await waitUntilReady?.(ctx);\n store.read();\n await openTaskSpace(ctx, { store });\n },\n });\n}\n"],"mappings":"0EAYA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,gBAAgBA,EAAAA,aAAc,CAC/B,YAAa,wCACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,MAAO,CACd,EAAI,GAAG,OAAOC,EAAAA,yBAA0B,OAAO,EAC/C,MACF,CAEA,MAAM,IAAiB,CAAG,EAC1B,EAAM,KAAK,EACX,MAAMC,EAAAA,cAAc,EAAK,CAAE,OAAM,CAAC,CACpC,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"index.cjs","names":["COMMAND_NAME","ERR_REQUIRES_INTERACTIVE","openTaskSpace"],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME, ERR_REQUIRES_INTERACTIVE } from '../schemas/task.ts';\nimport type { TaskStore } from '../adapters/store/taskStore';\nimport { openTaskSpace } from '../tui/taskSpace.ts';\n\n/**\n * `/tasks`, reachable from the leader palette as `SPC t l`.\n *\n * The command opens the Task Space overlay rather than printing a listing: the\n * overlay is the readable surface and it can edit, while a transcript dump could\n * only be read.\n */\nexport function registerTasksCommand(\n pi: ExtensionAPI,\n store: TaskStore,\n waitUntilReady?: (context: ExtensionContext) => Promise<void>,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Task Space for this session tree',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n\n await waitUntilReady?.(ctx);\n store.read();\n await openTaskSpace(ctx, { store });\n },\n });\n}\n"],"mappings":"0EAYA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,gBAAgBA,EAAAA,aAAc,CAC/B,YAAa,wCACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,MAAO,CACd,EAAI,GAAG,OAAOC,EAAAA,yBAA0B,OAAO,EAC/C,MACF,CAEA,MAAM,IAAiB,CAAG,EAC1B,EAAM,KAAK,EACX,MAAMC,EAAAA,cAAc,EAAK,CAAE,OAAM,CAAC,CACpC,CACF,CAAC,CACH"}
@@ -2,7 +2,7 @@ import { TaskStore } from "../adapters/store/taskStore.cjs";
2
2
  import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/commands/index.d.ts
4
4
  /**
5
- * `/tasks`, reachable from the leader palette as `SPC t t`.
5
+ * `/tasks`, reachable from the leader palette as `SPC t l`.
6
6
  *
7
7
  * The command opens the Task Space overlay rather than printing a listing: the
8
8
  * overlay is the readable surface and it can edit, while a transcript dump could
@@ -2,7 +2,7 @@ import { TaskStore } from "../adapters/store/taskStore.mjs";
2
2
  import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/commands/index.d.ts
4
4
  /**
5
- * `/tasks`, reachable from the leader palette as `SPC t t`.
5
+ * `/tasks`, reachable from the leader palette as `SPC t l`.
6
6
  *
7
7
  * The command opens the Task Space overlay rather than printing a listing: the
8
8
  * overlay is the readable surface and it can edit, while a transcript dump could
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME, ERR_REQUIRES_INTERACTIVE } from '../schemas/task.ts';\nimport type { TaskStore } from '../adapters/store/taskStore';\nimport { openTaskSpace } from '../tui/taskSpace.ts';\n\n/**\n * `/tasks`, reachable from the leader palette as `SPC t t`.\n *\n * The command opens the Task Space overlay rather than printing a listing: the\n * overlay is the readable surface and it can edit, while a transcript dump could\n * only be read.\n */\nexport function registerTasksCommand(\n pi: ExtensionAPI,\n store: TaskStore,\n waitUntilReady?: (context: ExtensionContext) => Promise<void>,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Task Space for this session tree',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n\n await waitUntilReady?.(ctx);\n store.read();\n await openTaskSpace(ctx, { store });\n },\n });\n}\n"],"mappings":"uIAYA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,gBAAgB,EAAc,CAC/B,YAAa,wCACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,MAAO,CACd,EAAI,GAAG,OAAO,EAA0B,OAAO,EAC/C,MACF,CAEA,MAAM,IAAiB,CAAG,EAC1B,EAAM,KAAK,EACX,MAAM,EAAc,EAAK,CAAE,OAAM,CAAC,CACpC,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME, ERR_REQUIRES_INTERACTIVE } from '../schemas/task.ts';\nimport type { TaskStore } from '../adapters/store/taskStore';\nimport { openTaskSpace } from '../tui/taskSpace.ts';\n\n/**\n * `/tasks`, reachable from the leader palette as `SPC t l`.\n *\n * The command opens the Task Space overlay rather than printing a listing: the\n * overlay is the readable surface and it can edit, while a transcript dump could\n * only be read.\n */\nexport function registerTasksCommand(\n pi: ExtensionAPI,\n store: TaskStore,\n waitUntilReady?: (context: ExtensionContext) => Promise<void>,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Task Space for this session tree',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n\n await waitUntilReady?.(ctx);\n store.read();\n await openTaskSpace(ctx, { store });\n },\n });\n}\n"],"mappings":"uIAYA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,gBAAgB,EAAc,CAC/B,YAAa,wCACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,MAAO,CACd,EAAI,GAAG,OAAO,EAA0B,OAAO,EAC/C,MACF,CAEA,MAAM,IAAiB,CAAG,EAC1B,EAAM,KAAK,EACX,MAAM,EAAc,EAAK,CAAE,OAAM,CAAC,CACpC,CACF,CAAC,CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"taskSpace2.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 '../adapters/store/taskStore';\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
+ {"version":3,"file":"taskSpace2.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 l` 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 '../adapters/store/taskStore';\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":"taskSpace2.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 '../adapters/store/taskStore';\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
+ {"version":3,"file":"taskSpace2.mjs","names":[],"sources":["../../src/tui/taskSpace.ts"],"sourcesContent":["/**\n * Task Space: the `SPC t l` 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 '../adapters/store/taskStore';\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.32",
3
+ "version": "0.0.1-alpha.34",
4
4
  "description": "Persistent, dependency-aware task graphs and subagent delegation for Pi coding sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -150,9 +150,9 @@
150
150
  "dependencies": {
151
151
  "@deepseek-ai/cordis": "4.0.1",
152
152
  "typebox": "1.3.16",
153
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.32",
154
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.32",
155
- "@agimon-ai/doompi-ui": "0.0.1-alpha.32"
153
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.34",
154
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.34",
155
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.34"
156
156
  },
157
157
  "devDependencies": {
158
158
  "@earendil-works/pi-coding-agent": "0.84.2",