@agimon-ai/doompi-notification 0.0.1-alpha.2 → 0.0.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/pi/extension.cjs +1 -1
- package/dist/adapters/pi/extension.cjs.map +1 -1
- package/dist/adapters/pi/extension.d.cts +1 -7
- package/dist/adapters/pi/extension.d.cts.map +1 -1
- package/dist/adapters/pi/extension.d.mts +1 -7
- package/dist/adapters/pi/extension.d.mts.map +1 -1
- package/dist/adapters/pi/extension.mjs +1 -1
- package/dist/adapters/pi/extension.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../services/notificationPolicy.cjs"),t=require("../../services/notificationText.cjs"),n=require("../shellTitleController.cjs"),r=require("../systemNotification.cjs");let i=require("@agimon-ai/doompi-extension-contracts"),a=require("@agimon-ai/doompi-extension-contracts/child-process"),o=require("@
|
|
1
|
+
const e=require("../../services/notificationPolicy.cjs"),t=require("../../services/notificationText.cjs"),n=require("../shellTitleController.cjs"),r=require("../systemNotification.cjs");let i=require("@agimon-ai/doompi-extension-contracts/ask-user"),a=require("@agimon-ai/doompi-extension-contracts/child-process"),o=require("@agimon-ai/doompi-extension-contracts/cordis-host");const s=`@agimon-ai/doompi-notification`;function c(e,t){return e[t]}function l(e,t,n){let r=c(e,`confirm`),i=(i,a,o)=>(t()&&n(`${i}: ${a}`),r.call(e,i,a,o));e.confirm=i;let a=c(e,`select`),o=(r,i,o)=>(t()&&n(r),a.call(e,r,i,o));e.select=o;let s=c(e,`input`),l=(r,i,a)=>(t()&&n(r),s.call(e,r,i,a));e.input=l;let u=c(e,`editor`),d=(r,i)=>(t()&&n(r),u.call(e,r,i));return e.editor=d,()=>{e.confirm===i&&(e.confirm=r),e.select===o&&(e.select=a),e.input===l&&(e.input=s),e.editor===d&&(e.editor=u)}}function u(a,{pi:o,options:c}){a.effect(function*(){let s=c.titleController??n.createWorkerTitleController(),u=new Map,d=!0,f=!1,p=!1,m,h,g=e=>t.shellTabTitle({cwd:e,sessionName:o.getSessionName(),prompt:m}),_=t=>{if(!(!d||!e.supportsShellTitle(t)))return h={cwd:t.cwd,write:e=>t.ui.setTitle(e)},h},v=e=>{d&&r.sendSystemNotification(o,t.attentionNotification(e))};yield a.on(i.DOOM_ASK_USER_PROMPT_EVENT,t=>{v(e.askUserPromptBody(t.questions))}),yield a.on(i.DOOM_ASK_USER_BLOCKED_EVENT,e=>{p=e.active}),yield()=>{h&&s.dispose(g(h.cwd),h.write),h=void 0},yield()=>{for(let e of u.values())e();u.clear()},yield()=>{d=!1,f=!1,p=!1},o.on(`input`,(e,n)=>{if(!d||m||e.source===`extension`)return;m=t.promptTitle(e.text);let r=m?_(n):void 0;r&&s.set(g(r.cwd),r.write)}),o.on(`session_start`,(t,n)=>{!d||u.has(n.ui)||u.set(n.ui,l(n.ui,()=>d&&e.warrantsAttentionNotification({agentRunning:f,askUserBlocked:p}),v))}),o.on(`agent_start`,(e,t)=>{if(!d)return;f=!0;let n=_(t);n&&s.start(g(n.cwd),n.write)}),o.on(`agent_settled`,async(n,i)=>{if(!d)return;f=!1,p=!1;let a=_(i);a&&s.stop(g(a.cwd),a.write),e.warrantsSettledNotification(i.hasPendingMessages())&&await r.sendSystemNotification(o,t.settledNotification({cwd:i.cwd,sessionName:o.getSessionName()}))})},s)}async function d(e,t={}){if((t.environment??process.env)[a.SUBAGENT_CHILD_ENV])return;let n=await(0,o.connectDoomCordisHost)(e,s,{environment:t.environment??process.env}),r=n.root.plugin(u,{pi:e,options:t});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}exports.default=d,exports.notificationExtension=d;
|
|
2
2
|
//# sourceMappingURL=extension.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.cjs","names":["SUBAGENT_CHILD_ENV","Context","createWorkerTitleController","shellTabTitle","supportsShellTitle","sendSystemNotification","attentionNotification","subscribeToAskUserEvents","askUserPromptBody","promptTitle","warrantsAttentionNotification","warrantsSettledNotification","settledNotification"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { subscribeToAskUserEvents } from '@agimon-ai/doompi-extension-contracts';\nimport { SUBAGENT_CHILD_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport {\n askUserPromptBody,\n supportsShellTitle,\n warrantsAttentionNotification,\n warrantsSettledNotification,\n} from '../../services/notificationPolicy.ts';\nimport {\n attentionNotification,\n promptTitle,\n settledNotification,\n shellTabTitle,\n} from '../../services/notificationText.ts';\nimport type { ShellTitleController, WriteTitle } from '../../types/notifications.ts';\nimport { createWorkerTitleController } from '../shellTitleController.ts';\nimport { sendSystemNotification } from '../systemNotification.ts';\n\nconst AGENT_SETTLED_EVENT = 'agent_settled';\nconst AGENT_START_EVENT = 'agent_start';\nconst EXTENSION_INPUT_SOURCE = 'extension';\nconst INPUT_EVENT = 'input';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-notification';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst SESSION_START_EVENT = 'session_start';\n\nexport interface NotificationExtensionOptions {\n /** Replaces the worker-thread animator, which a host without worker threads cannot start. */\n titleController?: ShellTitleController;\n environment?: NodeJS.ProcessEnv;\n}\n\n/** Where the tab title is written, remembered so shutdown can restore it without an event. */\ninterface TitleSurface {\n cwd: string;\n write: WriteTitle;\n}\n\n/**\n * Announces every dialog the agent opens on its own initiative.\n *\n * Pi has no event for \"a dialog is showing\", so the only way to catch one is to\n * wrap the UI context the session hands out. Each wrapper delegates to the\n * original, so nothing about the dialog itself changes.\n */\nfunction wrapAttentionDialogs(\n ui: ExtensionUIContext,\n shouldNotify: () => boolean,\n notify: (body: string) => void,\n): void {\n const originalConfirm = ui.confirm.bind(ui);\n ui.confirm = (title, message, options) => {\n if (shouldNotify()) notify(`${title}: ${message}`);\n return originalConfirm(title, message, options);\n };\n\n const originalSelect = ui.select.bind(ui);\n ui.select = (title, options, dialogOptions) => {\n if (shouldNotify()) notify(title);\n return originalSelect(title, options, dialogOptions);\n };\n\n const originalInput = ui.input.bind(ui);\n ui.input = (title, placeholder, options) => {\n if (shouldNotify()) notify(title);\n return originalInput(title, placeholder, options);\n };\n\n const originalEditor = ui.editor.bind(ui);\n ui.editor = (title, prefill) => {\n if (shouldNotify()) notify(title);\n return originalEditor(title, prefill);\n };\n}\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse: the handlers\n * go quiet, the tab title is restored, then the event subscriptions are dropped.\n */\nexport async function notificationExtension(\n pi: ExtensionAPI,\n options: NotificationExtensionOptions = {},\n): Promise<void> {\n // A detached subagent shares its parent's terminal and desktop, so anything it\n // announced would be a second voice reporting on the same run.\n if ((options.environment ?? process.env)[SUBAGENT_CHILD_ENV]) return;\n\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n const titles = options.titleController ?? createWorkerTitleController();\n const wrappedUis = new WeakSet<ExtensionUIContext>();\n let active = true;\n let agentRunning = false;\n let askUserBlocked = false;\n let firstPrompt: string | undefined;\n let surface: TitleSurface | undefined;\n\n const idleTitle = (cwd: string): string =>\n shellTabTitle({ cwd, sessionName: pi.getSessionName(), prompt: firstPrompt });\n const titleSurface = (context: ExtensionContext): TitleSurface | undefined => {\n if (!active || !supportsShellTitle(context)) return undefined;\n surface = { cwd: context.cwd, write: (title) => context.ui.setTitle(title) };\n return surface;\n };\n const notifyAttention = (body: string): void => {\n if (active) void sendSystemNotification(pi, attentionNotification(body));\n };\n\n const askUserSubscriptions = subscribeToAskUserEvents(pi.events, {\n onPrompt(prompt) {\n notifyAttention(askUserPromptBody(prompt.questions));\n },\n onBlocked(payload) {\n askUserBlocked = payload.active;\n },\n });\n yield () => askUserSubscriptions.dispose();\n\n // The fiber owns the animator, so the tab lands on its idle title however\n // the session ends — including a reload that fires no settle event.\n yield () => {\n if (surface) titles.dispose(idleTitle(surface.cwd), surface.write);\n surface = undefined;\n };\n yield () => {\n active = false;\n agentRunning = false;\n askUserBlocked = false;\n };\n\n pi.on(INPUT_EVENT, (event, context) => {\n if (!active || firstPrompt || event.source === EXTENSION_INPUT_SOURCE) return;\n\n firstPrompt = promptTitle(event.text);\n const target = firstPrompt ? titleSurface(context) : undefined;\n if (target) titles.set(idleTitle(target.cwd), target.write);\n });\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active || wrappedUis.has(context.ui)) return;\n wrappedUis.add(context.ui);\n wrapAttentionDialogs(\n context.ui,\n () => active && warrantsAttentionNotification({ agentRunning, askUserBlocked }),\n notifyAttention,\n );\n });\n\n pi.on(AGENT_START_EVENT, (_event, context) => {\n if (!active) return;\n agentRunning = true;\n const target = titleSurface(context);\n if (target) titles.start(idleTitle(target.cwd), target.write);\n });\n\n pi.on(AGENT_SETTLED_EVENT, async (_event, context) => {\n if (!active) return;\n agentRunning = false;\n askUserBlocked = false;\n const target = titleSurface(context);\n if (target) titles.stop(idleTitle(target.cwd), target.write);\n if (!warrantsSettledNotification(context.hasPendingMessages())) return;\n\n await sendSystemNotification(pi, settledNotification({ cwd: context.cwd, sessionName: pi.getSessionName() }));\n });\n }, PACKAGE_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default notificationExtension;\n"],"mappings":"mVA+CA,SAAS,EACP,EACA,EACA,EACM,CACN,IAAM,EAAkB,EAAG,QAAQ,KAAK,CAAE,EAC1C,EAAG,SAAW,EAAO,EAAS,KACxB,EAAa,GAAG,EAAO,GAAG,EAAM,IAAI,GAAS,EAC1C,EAAgB,EAAO,EAAS,CAAO,GAGhD,IAAM,EAAiB,EAAG,OAAO,KAAK,CAAE,EACxC,EAAG,QAAU,EAAO,EAAS,KACvB,EAAa,GAAG,EAAO,CAAK,EACzB,EAAe,EAAO,EAAS,CAAa,GAGrD,IAAM,EAAgB,EAAG,MAAM,KAAK,CAAE,EACtC,EAAG,OAAS,EAAO,EAAa,KAC1B,EAAa,GAAG,EAAO,CAAK,EACzB,EAAc,EAAO,EAAa,CAAO,GAGlD,IAAM,EAAiB,EAAG,OAAO,KAAK,CAAE,EACxC,EAAG,QAAU,EAAO,KACd,EAAa,GAAG,EAAO,CAAK,EACzB,EAAe,EAAO,CAAO,EAExC,CASA,eAAsB,EACpB,EACA,EAAwC,CAAC,EAC1B,CAGf,IAAK,EAAQ,aAAe,QAAQ,IAAA,CAAKA,EAAAA,oBAAqB,OAE9D,IAAM,EAAS,IAAIC,EAAAA,QACf,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,IAAM,EAAS,EAAQ,iBAAmBC,EAAAA,4BAA4B,EAChE,EAAa,IAAI,QACnB,EAAS,GACT,EAAe,GACf,EAAiB,GACjB,EACA,EAEE,EAAa,GACjBC,EAAAA,cAAc,CAAE,MAAK,YAAa,EAAG,eAAe,EAAG,OAAQ,CAAY,CAAC,EACxE,EAAgB,GAAwD,CACxE,MAAC,GAAU,CAACC,EAAAA,mBAAmB,CAAO,GAE1C,MADA,GAAU,CAAE,IAAK,EAAQ,IAAK,MAAQ,GAAU,EAAQ,GAAG,SAAS,CAAK,CAAE,EACpE,CACT,EACM,EAAmB,GAAuB,CAC1C,GAAQ,EAAKC,uBAAuB,EAAIC,EAAAA,sBAAsB,CAAI,CAAC,CACzE,EAEM,GAAA,EAAuBC,EAAAA,yBAAAA,CAAyB,EAAG,OAAQ,CAC/D,SAAS,EAAQ,CACf,EAAgBC,EAAAA,kBAAkB,EAAO,SAAS,CAAC,CACrD,EACA,UAAU,EAAS,CACjB,EAAiB,EAAQ,MAC3B,CACF,CAAC,EACD,SAAY,EAAqB,QAAQ,EAIzC,SAAY,CACN,GAAS,EAAO,QAAQ,EAAU,EAAQ,GAAG,EAAG,EAAQ,KAAK,EACjE,EAAU,IAAA,EACZ,EACA,SAAY,CACV,EAAS,GACT,EAAe,GACf,EAAiB,EACnB,EAEA,EAAG,GAAG,SAAc,EAAO,IAAY,CACrC,GAAI,CAAC,GAAU,GAAe,EAAM,SAAW,YAAwB,OAEvE,EAAcC,EAAAA,YAAY,EAAM,IAAI,EACpC,IAAM,EAAS,EAAc,EAAa,CAAO,EAAI,IAAA,GACjD,GAAQ,EAAO,IAAI,EAAU,EAAO,GAAG,EAAG,EAAO,KAAK,CAC5D,CAAC,EAED,EAAG,GAAG,iBAAsB,EAAQ,IAAY,CAC1C,CAAC,GAAU,EAAW,IAAI,EAAQ,EAAE,IACxC,EAAW,IAAI,EAAQ,EAAE,EACzB,EACE,EAAQ,OACF,GAAUC,EAAAA,8BAA8B,CAAE,eAAc,gBAAe,CAAC,EAC9E,CACF,EACF,CAAC,EAED,EAAG,GAAG,eAAoB,EAAQ,IAAY,CAC5C,GAAI,CAAC,EAAQ,OACb,EAAe,GACf,IAAM,EAAS,EAAa,CAAO,EAC/B,GAAQ,EAAO,MAAM,EAAU,EAAO,GAAG,EAAG,EAAO,KAAK,CAC9D,CAAC,EAED,EAAG,GAAG,gBAAqB,MAAO,EAAQ,IAAY,CACpD,GAAI,CAAC,EAAQ,OACb,EAAe,GACf,EAAiB,GACjB,IAAM,EAAS,EAAa,CAAO,EAC/B,GAAQ,EAAO,KAAK,EAAU,EAAO,GAAG,EAAG,EAAO,KAAK,EACtDC,EAAAA,4BAA4B,EAAQ,mBAAmB,CAAC,GAE7D,MAAMN,EAAAA,uBAAuB,EAAIO,EAAAA,oBAAoB,CAAE,IAAK,EAAQ,IAAK,YAAa,EAAG,eAAe,CAAE,CAAC,CAAC,CAC9G,CAAC,CACH,EAAG,gCAAc,CACnB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
|
|
1
|
+
{"version":3,"file":"extension.cjs","names":["createWorkerTitleController","shellTabTitle","supportsShellTitle","sendSystemNotification","attentionNotification","DOOM_ASK_USER_PROMPT_EVENT","askUserPromptBody","DOOM_ASK_USER_BLOCKED_EVENT","promptTitle","warrantsAttentionNotification","warrantsSettledNotification","settledNotification","SUBAGENT_CHILD_ENV","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_ASK_USER_BLOCKED_EVENT,\n DOOM_ASK_USER_PROMPT_EVENT,\n} from '@agimon-ai/doompi-extension-contracts/ask-user';\nimport { SUBAGENT_CHILD_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport {\n askUserPromptBody,\n supportsShellTitle,\n warrantsAttentionNotification,\n warrantsSettledNotification,\n} from '../../services/notificationPolicy.ts';\nimport {\n attentionNotification,\n promptTitle,\n settledNotification,\n shellTabTitle,\n} from '../../services/notificationText.ts';\nimport type { ShellTitleController, WriteTitle } from '../../types/notifications.ts';\nimport { createWorkerTitleController } from '../shellTitleController.ts';\nimport { sendSystemNotification } from '../systemNotification.ts';\n\nconst AGENT_SETTLED_EVENT = 'agent_settled';\nconst AGENT_START_EVENT = 'agent_start';\nconst EXTENSION_INPUT_SOURCE = 'extension';\nconst INPUT_EVENT = 'input';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-notification';\nconst SESSION_START_EVENT = 'session_start';\n\nexport interface NotificationExtensionOptions {\n /** Replaces the worker-thread animator, which a host without worker threads cannot start. */\n titleController?: ShellTitleController;\n environment?: NodeJS.ProcessEnv;\n}\n\n/** Where the tab title is written, remembered so shutdown can restore it without an event. */\ninterface TitleSurface {\n cwd: string;\n write: WriteTitle;\n}\n\ntype AttentionDialogMethod = 'confirm' | 'editor' | 'input' | 'select';\n\n/** Capture a host method whose original identity must be restored after reload. */\nfunction captureUiMethod<Method extends AttentionDialogMethod>(\n ui: ExtensionUIContext,\n method: Method,\n): ExtensionUIContext[Method] {\n return ui[method];\n}\n\n/**\n * Announces every dialog the agent opens on its own initiative.\n *\n * Pi has no event for \"a dialog is showing\", so the only way to catch one is to\n * wrap the UI context the session hands out. Each wrapper delegates to the\n * original, so nothing about the dialog itself changes.\n */\nfunction wrapAttentionDialogs(\n ui: ExtensionUIContext,\n shouldNotify: () => boolean,\n notify: (body: string) => void,\n): () => void {\n const originalConfirm = captureUiMethod(ui, 'confirm');\n const wrappedConfirm: ExtensionUIContext['confirm'] = (title, message, options) => {\n if (shouldNotify()) notify(`${title}: ${message}`);\n return originalConfirm.call(ui, title, message, options);\n };\n ui.confirm = wrappedConfirm;\n\n const originalSelect = captureUiMethod(ui, 'select');\n const wrappedSelect: ExtensionUIContext['select'] = (title, options, dialogOptions) => {\n if (shouldNotify()) notify(title);\n return originalSelect.call(ui, title, options, dialogOptions);\n };\n ui.select = wrappedSelect;\n\n const originalInput = captureUiMethod(ui, 'input');\n const wrappedInput: ExtensionUIContext['input'] = (title, placeholder, options) => {\n if (shouldNotify()) notify(title);\n return originalInput.call(ui, title, placeholder, options);\n };\n ui.input = wrappedInput;\n\n const originalEditor = captureUiMethod(ui, 'editor');\n const wrappedEditor: ExtensionUIContext['editor'] = (title, prefill) => {\n if (shouldNotify()) notify(title);\n return originalEditor.call(ui, title, prefill);\n };\n ui.editor = wrappedEditor;\n\n return () => {\n if (ui.confirm === wrappedConfirm) ui.confirm = originalConfirm;\n if (ui.select === wrappedSelect) ui.select = originalSelect;\n if (ui.input === wrappedInput) ui.input = originalInput;\n if (ui.editor === wrappedEditor) ui.editor = originalEditor;\n };\n}\n\ninterface NotificationPluginConfig {\n readonly pi: ExtensionAPI;\n readonly options: NotificationExtensionOptions;\n}\n\nfunction notificationPlugin(cordis: Context, { pi, options }: NotificationPluginConfig): void {\n cordis.effect(function* () {\n const titles = options.titleController ?? createWorkerTitleController();\n const wrappedUis = new Map<ExtensionUIContext, () => void>();\n let active = true;\n let agentRunning = false;\n let askUserBlocked = false;\n let firstPrompt: string | undefined;\n let surface: TitleSurface | undefined;\n\n const idleTitle = (cwd: string): string =>\n shellTabTitle({ cwd, sessionName: pi.getSessionName(), prompt: firstPrompt });\n const titleSurface = (context: ExtensionContext): TitleSurface | undefined => {\n if (!active || !supportsShellTitle(context)) return undefined;\n surface = { cwd: context.cwd, write: (title) => context.ui.setTitle(title) };\n return surface;\n };\n const notifyAttention = (body: string): void => {\n if (active) void sendSystemNotification(pi, attentionNotification(body));\n };\n\n yield cordis.on(DOOM_ASK_USER_PROMPT_EVENT, (prompt) => {\n notifyAttention(askUserPromptBody(prompt.questions));\n });\n yield cordis.on(DOOM_ASK_USER_BLOCKED_EVENT, (payload) => {\n askUserBlocked = payload.active;\n });\n\n yield () => {\n if (surface) titles.dispose(idleTitle(surface.cwd), surface.write);\n surface = undefined;\n };\n yield () => {\n for (const restore of wrappedUis.values()) restore();\n wrappedUis.clear();\n };\n yield () => {\n active = false;\n agentRunning = false;\n askUserBlocked = false;\n };\n\n pi.on(INPUT_EVENT, (event, context) => {\n if (!active || firstPrompt || event.source === EXTENSION_INPUT_SOURCE) return;\n firstPrompt = promptTitle(event.text);\n const target = firstPrompt ? titleSurface(context) : undefined;\n if (target) titles.set(idleTitle(target.cwd), target.write);\n });\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active || wrappedUis.has(context.ui)) return;\n wrappedUis.set(\n context.ui,\n wrapAttentionDialogs(\n context.ui,\n () => active && warrantsAttentionNotification({ agentRunning, askUserBlocked }),\n notifyAttention,\n ),\n );\n });\n\n pi.on(AGENT_START_EVENT, (_event, context) => {\n if (!active) return;\n agentRunning = true;\n const target = titleSurface(context);\n if (target) titles.start(idleTitle(target.cwd), target.write);\n });\n\n pi.on(AGENT_SETTLED_EVENT, async (_event, context) => {\n if (!active) return;\n agentRunning = false;\n askUserBlocked = false;\n const target = titleSurface(context);\n if (target) titles.stop(idleTitle(target.cwd), target.write);\n if (!warrantsSettledNotification(context.hasPendingMessages())) return;\n await sendSystemNotification(pi, settledNotification({ cwd: context.cwd, sessionName: pi.getSessionName() }));\n });\n }, PACKAGE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function notificationExtension(\n pi: ExtensionAPI,\n options: NotificationExtensionOptions = {},\n): Promise<void> {\n if ((options.environment ?? process.env)[SUBAGENT_CHILD_ENV]) return;\n\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE, {\n environment: options.environment ?? process.env,\n });\n const fiber = connection.root.plugin(notificationPlugin, { pi, options });\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\nexport default notificationExtension;\n"],"mappings":"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"}
|
|
@@ -6,13 +6,7 @@ interface NotificationExtensionOptions {
|
|
|
6
6
|
titleController?: ShellTitleController;
|
|
7
7
|
environment?: NodeJS.ProcessEnv;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
* The package's single standard Pi factory, and the owner of the cordis Context.
|
|
11
|
-
*
|
|
12
|
-
* Pi can reload an extension in-process, so every registration is yielded to the
|
|
13
|
-
* fiber and one dispose on session shutdown unwinds them in reverse: the handlers
|
|
14
|
-
* go quiet, the tab title is restored, then the event subscriptions are dropped.
|
|
15
|
-
*/
|
|
9
|
+
/** The package's single standard Pi factory. */
|
|
16
10
|
declare function notificationExtension(pi: ExtensionAPI, options?: NotificationExtensionOptions): Promise<void>;
|
|
17
11
|
//#endregion
|
|
18
12
|
export { NotificationExtensionOptions, notificationExtension as default, notificationExtension };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;UA+BiB;;EAEf,kBAAkB;EAClB,cAAc,OAAO;;;iBAyJD,sBACpB,IAAI,cACJ,UAAS,+BACR"}
|
|
@@ -6,13 +6,7 @@ interface NotificationExtensionOptions {
|
|
|
6
6
|
titleController?: ShellTitleController;
|
|
7
7
|
environment?: NodeJS.ProcessEnv;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
* The package's single standard Pi factory, and the owner of the cordis Context.
|
|
11
|
-
*
|
|
12
|
-
* Pi can reload an extension in-process, so every registration is yielded to the
|
|
13
|
-
* fiber and one dispose on session shutdown unwinds them in reverse: the handlers
|
|
14
|
-
* go quiet, the tab title is restored, then the event subscriptions are dropped.
|
|
15
|
-
*/
|
|
9
|
+
/** The package's single standard Pi factory. */
|
|
16
10
|
declare function notificationExtension(pi: ExtensionAPI, options?: NotificationExtensionOptions): Promise<void>;
|
|
17
11
|
//#endregion
|
|
18
12
|
export { NotificationExtensionOptions, notificationExtension as default, notificationExtension };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;UA+BiB;;EAEf,kBAAkB;EAClB,cAAc,OAAO;;;iBAyJD,sBACpB,IAAI,cACJ,UAAS,+BACR"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{askUserPromptBody as e,supportsShellTitle as t,warrantsAttentionNotification as n,warrantsSettledNotification as r}from"../../services/notificationPolicy.mjs";import{attentionNotification as i,promptTitle as a,settledNotification as o,shellTabTitle as s}from"../../services/notificationText.mjs";import{createWorkerTitleController as c}from"../shellTitleController.mjs";import{sendSystemNotification as l}from"../systemNotification.mjs";import{
|
|
1
|
+
import{askUserPromptBody as e,supportsShellTitle as t,warrantsAttentionNotification as n,warrantsSettledNotification as r}from"../../services/notificationPolicy.mjs";import{attentionNotification as i,promptTitle as a,settledNotification as o,shellTabTitle as s}from"../../services/notificationText.mjs";import{createWorkerTitleController as c}from"../shellTitleController.mjs";import{sendSystemNotification as l}from"../systemNotification.mjs";import{DOOM_ASK_USER_BLOCKED_EVENT as u,DOOM_ASK_USER_PROMPT_EVENT as d}from"@agimon-ai/doompi-extension-contracts/ask-user";import{SUBAGENT_CHILD_ENV as f}from"@agimon-ai/doompi-extension-contracts/child-process";import{connectDoomCordisHost as p}from"@agimon-ai/doompi-extension-contracts/cordis-host";const m=`@agimon-ai/doompi-notification`;function h(e,t){return e[t]}function g(e,t,n){let r=h(e,`confirm`),i=(i,a,o)=>(t()&&n(`${i}: ${a}`),r.call(e,i,a,o));e.confirm=i;let a=h(e,`select`),o=(r,i,o)=>(t()&&n(r),a.call(e,r,i,o));e.select=o;let s=h(e,`input`),c=(r,i,a)=>(t()&&n(r),s.call(e,r,i,a));e.input=c;let l=h(e,`editor`),u=(r,i)=>(t()&&n(r),l.call(e,r,i));return e.editor=u,()=>{e.confirm===i&&(e.confirm=r),e.select===o&&(e.select=a),e.input===c&&(e.input=s),e.editor===u&&(e.editor=l)}}function _(f,{pi:p,options:h}){f.effect(function*(){let m=h.titleController??c(),_=new Map,v=!0,y=!1,b=!1,x,S,C=e=>s({cwd:e,sessionName:p.getSessionName(),prompt:x}),w=e=>{if(!(!v||!t(e)))return S={cwd:e.cwd,write:t=>e.ui.setTitle(t)},S},T=e=>{v&&l(p,i(e))};yield f.on(d,t=>{T(e(t.questions))}),yield f.on(u,e=>{b=e.active}),yield()=>{S&&m.dispose(C(S.cwd),S.write),S=void 0},yield()=>{for(let e of _.values())e();_.clear()},yield()=>{v=!1,y=!1,b=!1},p.on(`input`,(e,t)=>{if(!v||x||e.source===`extension`)return;x=a(e.text);let n=x?w(t):void 0;n&&m.set(C(n.cwd),n.write)}),p.on(`session_start`,(e,t)=>{!v||_.has(t.ui)||_.set(t.ui,g(t.ui,()=>v&&n({agentRunning:y,askUserBlocked:b}),T))}),p.on(`agent_start`,(e,t)=>{if(!v)return;y=!0;let n=w(t);n&&m.start(C(n.cwd),n.write)}),p.on(`agent_settled`,async(e,t)=>{if(!v)return;y=!1,b=!1;let n=w(t);n&&m.stop(C(n.cwd),n.write),r(t.hasPendingMessages())&&await l(p,o({cwd:t.cwd,sessionName:p.getSessionName()}))})},m)}async function v(e,t={}){if((t.environment??process.env)[f])return;let n=await p(e,m,{environment:t.environment??process.env}),r=n.root.plugin(_,{pi:e,options:t});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}export{v as default,v as notificationExtension};
|
|
2
2
|
//# sourceMappingURL=extension.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { subscribeToAskUserEvents } from '@agimon-ai/doompi-extension-contracts';\nimport { SUBAGENT_CHILD_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport {\n askUserPromptBody,\n supportsShellTitle,\n warrantsAttentionNotification,\n warrantsSettledNotification,\n} from '../../services/notificationPolicy.ts';\nimport {\n attentionNotification,\n promptTitle,\n settledNotification,\n shellTabTitle,\n} from '../../services/notificationText.ts';\nimport type { ShellTitleController, WriteTitle } from '../../types/notifications.ts';\nimport { createWorkerTitleController } from '../shellTitleController.ts';\nimport { sendSystemNotification } from '../systemNotification.ts';\n\nconst AGENT_SETTLED_EVENT = 'agent_settled';\nconst AGENT_START_EVENT = 'agent_start';\nconst EXTENSION_INPUT_SOURCE = 'extension';\nconst INPUT_EVENT = 'input';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-notification';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst SESSION_START_EVENT = 'session_start';\n\nexport interface NotificationExtensionOptions {\n /** Replaces the worker-thread animator, which a host without worker threads cannot start. */\n titleController?: ShellTitleController;\n environment?: NodeJS.ProcessEnv;\n}\n\n/** Where the tab title is written, remembered so shutdown can restore it without an event. */\ninterface TitleSurface {\n cwd: string;\n write: WriteTitle;\n}\n\n/**\n * Announces every dialog the agent opens on its own initiative.\n *\n * Pi has no event for \"a dialog is showing\", so the only way to catch one is to\n * wrap the UI context the session hands out. Each wrapper delegates to the\n * original, so nothing about the dialog itself changes.\n */\nfunction wrapAttentionDialogs(\n ui: ExtensionUIContext,\n shouldNotify: () => boolean,\n notify: (body: string) => void,\n): void {\n const originalConfirm = ui.confirm.bind(ui);\n ui.confirm = (title, message, options) => {\n if (shouldNotify()) notify(`${title}: ${message}`);\n return originalConfirm(title, message, options);\n };\n\n const originalSelect = ui.select.bind(ui);\n ui.select = (title, options, dialogOptions) => {\n if (shouldNotify()) notify(title);\n return originalSelect(title, options, dialogOptions);\n };\n\n const originalInput = ui.input.bind(ui);\n ui.input = (title, placeholder, options) => {\n if (shouldNotify()) notify(title);\n return originalInput(title, placeholder, options);\n };\n\n const originalEditor = ui.editor.bind(ui);\n ui.editor = (title, prefill) => {\n if (shouldNotify()) notify(title);\n return originalEditor(title, prefill);\n };\n}\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse: the handlers\n * go quiet, the tab title is restored, then the event subscriptions are dropped.\n */\nexport async function notificationExtension(\n pi: ExtensionAPI,\n options: NotificationExtensionOptions = {},\n): Promise<void> {\n // A detached subagent shares its parent's terminal and desktop, so anything it\n // announced would be a second voice reporting on the same run.\n if ((options.environment ?? process.env)[SUBAGENT_CHILD_ENV]) return;\n\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n const titles = options.titleController ?? createWorkerTitleController();\n const wrappedUis = new WeakSet<ExtensionUIContext>();\n let active = true;\n let agentRunning = false;\n let askUserBlocked = false;\n let firstPrompt: string | undefined;\n let surface: TitleSurface | undefined;\n\n const idleTitle = (cwd: string): string =>\n shellTabTitle({ cwd, sessionName: pi.getSessionName(), prompt: firstPrompt });\n const titleSurface = (context: ExtensionContext): TitleSurface | undefined => {\n if (!active || !supportsShellTitle(context)) return undefined;\n surface = { cwd: context.cwd, write: (title) => context.ui.setTitle(title) };\n return surface;\n };\n const notifyAttention = (body: string): void => {\n if (active) void sendSystemNotification(pi, attentionNotification(body));\n };\n\n const askUserSubscriptions = subscribeToAskUserEvents(pi.events, {\n onPrompt(prompt) {\n notifyAttention(askUserPromptBody(prompt.questions));\n },\n onBlocked(payload) {\n askUserBlocked = payload.active;\n },\n });\n yield () => askUserSubscriptions.dispose();\n\n // The fiber owns the animator, so the tab lands on its idle title however\n // the session ends — including a reload that fires no settle event.\n yield () => {\n if (surface) titles.dispose(idleTitle(surface.cwd), surface.write);\n surface = undefined;\n };\n yield () => {\n active = false;\n agentRunning = false;\n askUserBlocked = false;\n };\n\n pi.on(INPUT_EVENT, (event, context) => {\n if (!active || firstPrompt || event.source === EXTENSION_INPUT_SOURCE) return;\n\n firstPrompt = promptTitle(event.text);\n const target = firstPrompt ? titleSurface(context) : undefined;\n if (target) titles.set(idleTitle(target.cwd), target.write);\n });\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active || wrappedUis.has(context.ui)) return;\n wrappedUis.add(context.ui);\n wrapAttentionDialogs(\n context.ui,\n () => active && warrantsAttentionNotification({ agentRunning, askUserBlocked }),\n notifyAttention,\n );\n });\n\n pi.on(AGENT_START_EVENT, (_event, context) => {\n if (!active) return;\n agentRunning = true;\n const target = titleSurface(context);\n if (target) titles.start(idleTitle(target.cwd), target.write);\n });\n\n pi.on(AGENT_SETTLED_EVENT, async (_event, context) => {\n if (!active) return;\n agentRunning = false;\n askUserBlocked = false;\n const target = titleSurface(context);\n if (target) titles.stop(idleTitle(target.cwd), target.write);\n if (!warrantsSettledNotification(context.hasPendingMessages())) return;\n\n await sendSystemNotification(pi, settledNotification({ cwd: context.cwd, sessionName: pi.getSessionName() }));\n });\n }, PACKAGE_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default notificationExtension;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_ASK_USER_BLOCKED_EVENT,\n DOOM_ASK_USER_PROMPT_EVENT,\n} from '@agimon-ai/doompi-extension-contracts/ask-user';\nimport { SUBAGENT_CHILD_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport {\n askUserPromptBody,\n supportsShellTitle,\n warrantsAttentionNotification,\n warrantsSettledNotification,\n} from '../../services/notificationPolicy.ts';\nimport {\n attentionNotification,\n promptTitle,\n settledNotification,\n shellTabTitle,\n} from '../../services/notificationText.ts';\nimport type { ShellTitleController, WriteTitle } from '../../types/notifications.ts';\nimport { createWorkerTitleController } from '../shellTitleController.ts';\nimport { sendSystemNotification } from '../systemNotification.ts';\n\nconst AGENT_SETTLED_EVENT = 'agent_settled';\nconst AGENT_START_EVENT = 'agent_start';\nconst EXTENSION_INPUT_SOURCE = 'extension';\nconst INPUT_EVENT = 'input';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-notification';\nconst SESSION_START_EVENT = 'session_start';\n\nexport interface NotificationExtensionOptions {\n /** Replaces the worker-thread animator, which a host without worker threads cannot start. */\n titleController?: ShellTitleController;\n environment?: NodeJS.ProcessEnv;\n}\n\n/** Where the tab title is written, remembered so shutdown can restore it without an event. */\ninterface TitleSurface {\n cwd: string;\n write: WriteTitle;\n}\n\ntype AttentionDialogMethod = 'confirm' | 'editor' | 'input' | 'select';\n\n/** Capture a host method whose original identity must be restored after reload. */\nfunction captureUiMethod<Method extends AttentionDialogMethod>(\n ui: ExtensionUIContext,\n method: Method,\n): ExtensionUIContext[Method] {\n return ui[method];\n}\n\n/**\n * Announces every dialog the agent opens on its own initiative.\n *\n * Pi has no event for \"a dialog is showing\", so the only way to catch one is to\n * wrap the UI context the session hands out. Each wrapper delegates to the\n * original, so nothing about the dialog itself changes.\n */\nfunction wrapAttentionDialogs(\n ui: ExtensionUIContext,\n shouldNotify: () => boolean,\n notify: (body: string) => void,\n): () => void {\n const originalConfirm = captureUiMethod(ui, 'confirm');\n const wrappedConfirm: ExtensionUIContext['confirm'] = (title, message, options) => {\n if (shouldNotify()) notify(`${title}: ${message}`);\n return originalConfirm.call(ui, title, message, options);\n };\n ui.confirm = wrappedConfirm;\n\n const originalSelect = captureUiMethod(ui, 'select');\n const wrappedSelect: ExtensionUIContext['select'] = (title, options, dialogOptions) => {\n if (shouldNotify()) notify(title);\n return originalSelect.call(ui, title, options, dialogOptions);\n };\n ui.select = wrappedSelect;\n\n const originalInput = captureUiMethod(ui, 'input');\n const wrappedInput: ExtensionUIContext['input'] = (title, placeholder, options) => {\n if (shouldNotify()) notify(title);\n return originalInput.call(ui, title, placeholder, options);\n };\n ui.input = wrappedInput;\n\n const originalEditor = captureUiMethod(ui, 'editor');\n const wrappedEditor: ExtensionUIContext['editor'] = (title, prefill) => {\n if (shouldNotify()) notify(title);\n return originalEditor.call(ui, title, prefill);\n };\n ui.editor = wrappedEditor;\n\n return () => {\n if (ui.confirm === wrappedConfirm) ui.confirm = originalConfirm;\n if (ui.select === wrappedSelect) ui.select = originalSelect;\n if (ui.input === wrappedInput) ui.input = originalInput;\n if (ui.editor === wrappedEditor) ui.editor = originalEditor;\n };\n}\n\ninterface NotificationPluginConfig {\n readonly pi: ExtensionAPI;\n readonly options: NotificationExtensionOptions;\n}\n\nfunction notificationPlugin(cordis: Context, { pi, options }: NotificationPluginConfig): void {\n cordis.effect(function* () {\n const titles = options.titleController ?? createWorkerTitleController();\n const wrappedUis = new Map<ExtensionUIContext, () => void>();\n let active = true;\n let agentRunning = false;\n let askUserBlocked = false;\n let firstPrompt: string | undefined;\n let surface: TitleSurface | undefined;\n\n const idleTitle = (cwd: string): string =>\n shellTabTitle({ cwd, sessionName: pi.getSessionName(), prompt: firstPrompt });\n const titleSurface = (context: ExtensionContext): TitleSurface | undefined => {\n if (!active || !supportsShellTitle(context)) return undefined;\n surface = { cwd: context.cwd, write: (title) => context.ui.setTitle(title) };\n return surface;\n };\n const notifyAttention = (body: string): void => {\n if (active) void sendSystemNotification(pi, attentionNotification(body));\n };\n\n yield cordis.on(DOOM_ASK_USER_PROMPT_EVENT, (prompt) => {\n notifyAttention(askUserPromptBody(prompt.questions));\n });\n yield cordis.on(DOOM_ASK_USER_BLOCKED_EVENT, (payload) => {\n askUserBlocked = payload.active;\n });\n\n yield () => {\n if (surface) titles.dispose(idleTitle(surface.cwd), surface.write);\n surface = undefined;\n };\n yield () => {\n for (const restore of wrappedUis.values()) restore();\n wrappedUis.clear();\n };\n yield () => {\n active = false;\n agentRunning = false;\n askUserBlocked = false;\n };\n\n pi.on(INPUT_EVENT, (event, context) => {\n if (!active || firstPrompt || event.source === EXTENSION_INPUT_SOURCE) return;\n firstPrompt = promptTitle(event.text);\n const target = firstPrompt ? titleSurface(context) : undefined;\n if (target) titles.set(idleTitle(target.cwd), target.write);\n });\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active || wrappedUis.has(context.ui)) return;\n wrappedUis.set(\n context.ui,\n wrapAttentionDialogs(\n context.ui,\n () => active && warrantsAttentionNotification({ agentRunning, askUserBlocked }),\n notifyAttention,\n ),\n );\n });\n\n pi.on(AGENT_START_EVENT, (_event, context) => {\n if (!active) return;\n agentRunning = true;\n const target = titleSurface(context);\n if (target) titles.start(idleTitle(target.cwd), target.write);\n });\n\n pi.on(AGENT_SETTLED_EVENT, async (_event, context) => {\n if (!active) return;\n agentRunning = false;\n askUserBlocked = false;\n const target = titleSurface(context);\n if (target) titles.stop(idleTitle(target.cwd), target.write);\n if (!warrantsSettledNotification(context.hasPendingMessages())) return;\n await sendSystemNotification(pi, settledNotification({ cwd: context.cwd, sessionName: pi.getSessionName() }));\n });\n }, PACKAGE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function notificationExtension(\n pi: ExtensionAPI,\n options: NotificationExtensionOptions = {},\n): Promise<void> {\n if ((options.environment ?? process.env)[SUBAGENT_CHILD_ENV]) return;\n\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE, {\n environment: options.environment ?? process.env,\n });\n const fiber = connection.root.plugin(notificationPlugin, { pi, options });\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\nexport default notificationExtension;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-notification",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.4",
|
|
4
4
|
"description": "Desktop notifications and an animated shell-tab title for DoomPi sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@deepseek-ai/cordis": "4.0.1",
|
|
50
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
50
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.26"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"tsdown": "0.22.14",
|
|
57
57
|
"typescript": "6.0.3",
|
|
58
58
|
"vitest": "4.1.10",
|
|
59
|
-
"@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.
|
|
59
|
+
"@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.26"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@earendil-works/pi-coding-agent": "0.84.2"
|