@agimon-ai/doompi-autostop 0.0.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/adapters/pi/extension.cjs +2 -0
  4. package/dist/adapters/pi/extension.cjs.map +1 -0
  5. package/dist/adapters/pi/extension.d.cts +14 -0
  6. package/dist/adapters/pi/extension.d.cts.map +1 -0
  7. package/dist/adapters/pi/extension.d.mts +14 -0
  8. package/dist/adapters/pi/extension.d.mts.map +1 -0
  9. package/dist/adapters/pi/extension.mjs +2 -0
  10. package/dist/adapters/pi/extension.mjs.map +1 -0
  11. package/dist/adapters/pi/idleShutdown.cjs +2 -0
  12. package/dist/adapters/pi/idleShutdown.cjs.map +1 -0
  13. package/dist/adapters/pi/idleShutdown.d.cts +14 -0
  14. package/dist/adapters/pi/idleShutdown.d.cts.map +1 -0
  15. package/dist/adapters/pi/idleShutdown.d.mts +14 -0
  16. package/dist/adapters/pi/idleShutdown.d.mts.map +1 -0
  17. package/dist/adapters/pi/idleShutdown.mjs +2 -0
  18. package/dist/adapters/pi/idleShutdown.mjs.map +1 -0
  19. package/dist/extensions/pi.cjs +1 -0
  20. package/dist/extensions/pi.d.cts +2 -0
  21. package/dist/extensions/pi.d.mts +2 -0
  22. package/dist/extensions/pi.mjs +1 -0
  23. package/dist/index.cjs +1 -0
  24. package/dist/index.d.cts +4 -0
  25. package/dist/index.d.mts +4 -0
  26. package/dist/index.mjs +1 -0
  27. package/dist/services/idlePolicy.cjs +2 -0
  28. package/dist/services/idlePolicy.cjs.map +1 -0
  29. package/dist/services/idlePolicy.d.cts +49 -0
  30. package/dist/services/idlePolicy.d.cts.map +1 -0
  31. package/dist/services/idlePolicy.d.mts +49 -0
  32. package/dist/services/idlePolicy.d.mts.map +1 -0
  33. package/dist/services/idlePolicy.mjs +2 -0
  34. package/dist/services/idlePolicy.mjs.map +1 -0
  35. package/llms.txt +7 -0
  36. package/package.json +82 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @agimon-ai/doompi-autostop
2
+
3
+ Shuts a [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi) session down once the agent
4
+ settles and stays idle.
5
+
6
+ A scripted run wants the session to exit on its own when the work is done. Waiting for
7
+ `agent_settled` alone is not enough: the agent can settle and immediately pick up a queued message,
8
+ and it can settle while the last response is still streaming. So this package settles, waits, looks
9
+ again, and only then stops.
10
+
11
+ ## The policy
12
+
13
+ | Moment | Session state | Outcome |
14
+ | ------------------ | ----------------- | -------------------- |
15
+ | `agent_settled` | messages queued | stand down |
16
+ | `agent_settled` | queue empty | look again in 5s |
17
+ | the scheduled look | messages queued | stand down |
18
+ | the scheduled look | still streaming | look again in 100ms |
19
+ | the scheduled look | idle, queue empty | `context.shutdown()` |
20
+
21
+ Any `input` or `agent_start` disarms a pending stop, so a user who starts typing during the cooldown
22
+ keeps their session. The cooldown is the grace period; the 100ms recheck is the poll that waits out
23
+ a stream that has not finished draining.
24
+
25
+ `decideOnSettled` and `decideOnRecheck` are the whole policy and take the session state as a plain
26
+ value, so the timing rules are testable without a Pi host.
27
+
28
+ ## What it registers
29
+
30
+ | Entry | Surface |
31
+ | ----------------- | ------------------------------------------------------------------------------------- |
32
+ | `./extensions/pi` | `input`, `agent_start` and `agent_settled` hooks, plus the shutdown that disarms them |
33
+
34
+ The extension owns a cordis fiber; `session_shutdown` disposes it, which cancels any armed stop.
35
+ That matters because Pi reloads extensions in-process — a timer left armed by the previous load
36
+ would stop the session it was reloaded into.
37
+
38
+ ## Installation
39
+
40
+ DoomPi depends on this package and activates it when a run asks for `--auto-stop`. It is not
41
+ selectable from `.doom/modes.yaml`.
42
+
43
+ ## License
44
+
45
+ MIT
@@ -0,0 +1,2 @@
1
+ const e=require("../../services/idlePolicy.cjs"),t=require("./idleShutdown.cjs");let n=require("@deepseek-ai/cordis");async function r(r,i=e.DEFAULT_AUTO_STOP_DELAYS){let a=new n.Context,o,s=()=>(o??=a.fiber.dispose(),o);try{a.effect(function*(){yield t.registerIdleShutdown(r,i)},`@agimon-ai/doompi-autostop`)}catch(e){throw await s(),e}r.on(`session_shutdown`,s)}exports.autoStopExtension=r,exports.default=r;
2
+ //# sourceMappingURL=extension.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.cjs","names":["DEFAULT_AUTO_STOP_DELAYS","Context","registerIdleShutdown"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { type AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS } from '../../services/idlePolicy.ts';\nimport { registerIdleShutdown } from './idleShutdown.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-autostop';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\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 the idle watch is yielded to the\n * fiber and one dispose on session shutdown unwinds it. That dispose is also\n * how a shutting-down session disarms a pending stop.\n */\nexport async function autoStopExtension(\n pi: ExtensionAPI,\n delays: AutoStopDelays = DEFAULT_AUTO_STOP_DELAYS,\n): Promise<void> {\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 yield registerIdleShutdown(pi, delays);\n }, PACKAGE_SOURCE);\n } catch (error: unknown) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default autoStopExtension;\n"],"mappings":"sHAeA,eAAsB,EACpB,EACA,EAAyBA,EAAAA,yBACV,CACf,IAAM,EAAS,IAAIC,EAAAA,QACf,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,MAAMC,EAAAA,qBAAqB,EAAI,CAAM,CACvC,EAAG,4BAAc,CACnB,OAAS,EAAgB,CAEvB,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
@@ -0,0 +1,14 @@
1
+ import { AutoStopDelays } from "../../services/idlePolicy.cjs";
2
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/extension.d.ts
4
+ /**
5
+ * The package's single standard Pi factory, and the owner of the cordis Context.
6
+ *
7
+ * Pi can reload an extension in-process, so the idle watch is yielded to the
8
+ * fiber and one dispose on session shutdown unwinds it. That dispose is also
9
+ * how a shutting-down session disarms a pending stop.
10
+ */
11
+ declare function autoStopExtension(pi: ExtensionAPI, delays?: AutoStopDelays): Promise<void>;
12
+ //#endregion
13
+ export { autoStopExtension, autoStopExtension as default };
14
+ //# sourceMappingURL=extension.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;;iBAesB,kBACpB,IAAI,cACJ,SAAQ,iBACP"}
@@ -0,0 +1,14 @@
1
+ import { AutoStopDelays } from "../../services/idlePolicy.mjs";
2
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/extension.d.ts
4
+ /**
5
+ * The package's single standard Pi factory, and the owner of the cordis Context.
6
+ *
7
+ * Pi can reload an extension in-process, so the idle watch is yielded to the
8
+ * fiber and one dispose on session shutdown unwinds it. That dispose is also
9
+ * how a shutting-down session disarms a pending stop.
10
+ */
11
+ declare function autoStopExtension(pi: ExtensionAPI, delays?: AutoStopDelays): Promise<void>;
12
+ //#endregion
13
+ export { autoStopExtension, autoStopExtension as default };
14
+ //# sourceMappingURL=extension.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;;iBAesB,kBACpB,IAAI,cACJ,SAAQ,iBACP"}
@@ -0,0 +1,2 @@
1
+ import{DEFAULT_AUTO_STOP_DELAYS as e}from"../../services/idlePolicy.mjs";import{registerIdleShutdown as t}from"./idleShutdown.mjs";import{Context as n}from"@deepseek-ai/cordis";async function r(r,i=e){let a=new n,o,s=()=>(o??=a.fiber.dispose(),o);try{a.effect(function*(){yield t(r,i)},`@agimon-ai/doompi-autostop`)}catch(e){throw await s(),e}r.on(`session_shutdown`,s)}export{r as autoStopExtension,r as default};
2
+ //# sourceMappingURL=extension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { type AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS } from '../../services/idlePolicy.ts';\nimport { registerIdleShutdown } from './idleShutdown.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-autostop';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\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 the idle watch is yielded to the\n * fiber and one dispose on session shutdown unwinds it. That dispose is also\n * how a shutting-down session disarms a pending stop.\n */\nexport async function autoStopExtension(\n pi: ExtensionAPI,\n delays: AutoStopDelays = DEFAULT_AUTO_STOP_DELAYS,\n): Promise<void> {\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 yield registerIdleShutdown(pi, delays);\n }, PACKAGE_SOURCE);\n } catch (error: unknown) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default autoStopExtension;\n"],"mappings":"iLAeA,eAAsB,EACpB,EACA,EAAyB,EACV,CACf,IAAM,EAAS,IAAI,EACf,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,MAAM,EAAqB,EAAI,CAAM,CACvC,EAAG,4BAAc,CACnB,OAAS,EAAgB,CAEvB,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../services/idlePolicy.cjs");function t(e){return{hasPendingMessages:e.hasPendingMessages(),isIdle:e.isIdle()}}function n(n,r){let i,a=()=>{i&&=(clearTimeout(i),void 0)},o=(n,a)=>{i=setTimeout(()=>{i=void 0;let a=e.decideOnRecheck(t(n),r);if(a.action===e.AUTO_STOP_ACTION.shutdown){n.shutdown();return}a.action===e.AUTO_STOP_ACTION.recheck&&o(n,a.delayMs)},a)};return n.on(`input`,a),n.on(`agent_start`,a),n.on(`agent_settled`,(n,i)=>{a();let s=e.decideOnSettled(t(i),r);s.action===e.AUTO_STOP_ACTION.recheck&&o(i,s.delayMs)}),a}exports.registerIdleShutdown=n;
2
+ //# sourceMappingURL=idleShutdown.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idleShutdown.cjs","names":["decideOnRecheck","AUTO_STOP_ACTION","decideOnSettled"],"sources":["../../../src/adapters/pi/idleShutdown.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n AUTO_STOP_ACTION,\n type AutoStopDelays,\n type SessionActivity,\n decideOnRecheck,\n decideOnSettled,\n} from '../../services/idlePolicy.ts';\n\nfunction readActivity(context: ExtensionContext): SessionActivity {\n return { hasPendingMessages: context.hasPendingMessages(), isIdle: context.isIdle() };\n}\n\n/**\n * Watches the session for idleness and returns the disposer for that watch.\n *\n * Pi's `on` hands back nothing to unsubscribe with, so the only resource this\n * owns across a reload is the armed timer — and an armed timer is the one that\n * matters, because firing it would stop the session Pi has just reloaded into.\n */\nexport function registerIdleShutdown(pi: ExtensionAPI, delays: AutoStopDelays): () => void {\n let shutdownTimer: NodeJS.Timeout | undefined;\n\n const cancelScheduledShutdown = (): void => {\n if (!shutdownTimer) return;\n clearTimeout(shutdownTimer);\n shutdownTimer = undefined;\n };\n\n const scheduleShutdown = (context: ExtensionContext, delayMs: number): void => {\n shutdownTimer = setTimeout(() => {\n shutdownTimer = undefined;\n const decision = decideOnRecheck(readActivity(context), delays);\n if (decision.action === AUTO_STOP_ACTION.shutdown) {\n context.shutdown();\n return;\n }\n if (decision.action === AUTO_STOP_ACTION.recheck) scheduleShutdown(context, decision.delayMs);\n }, delayMs);\n };\n\n pi.on('input', cancelScheduledShutdown);\n pi.on('agent_start', cancelScheduledShutdown);\n\n pi.on('agent_settled', (_event, context) => {\n cancelScheduledShutdown();\n const decision = decideOnSettled(readActivity(context), delays);\n if (decision.action === AUTO_STOP_ACTION.recheck) scheduleShutdown(context, decision.delayMs);\n });\n\n return cancelScheduledShutdown;\n}\n"],"mappings":"iDASA,SAAS,EAAa,EAA4C,CAChE,MAAO,CAAE,mBAAoB,EAAQ,mBAAmB,EAAG,OAAQ,EAAQ,OAAO,CAAE,CACtF,CASA,SAAgB,EAAqB,EAAkB,EAAoC,CACzF,IAAI,EAEE,MAAsC,CACrC,AAEL,KADA,aAAa,CAAa,EACV,IAAA,GAClB,EAEM,GAAoB,EAA2B,IAA0B,CAC7E,EAAgB,eAAiB,CAC/B,EAAgB,IAAA,GAChB,IAAM,EAAWA,EAAAA,gBAAgB,EAAa,CAAO,EAAG,CAAM,EAC9D,GAAI,EAAS,SAAWC,EAAAA,iBAAiB,SAAU,CACjD,EAAQ,SAAS,EACjB,MACF,CACI,EAAS,SAAWA,EAAAA,iBAAiB,SAAS,EAAiB,EAAS,EAAS,OAAO,CAC9F,EAAG,CAAO,CACZ,EAWA,OATA,EAAG,GAAG,QAAS,CAAuB,EACtC,EAAG,GAAG,cAAe,CAAuB,EAE5C,EAAG,GAAG,iBAAkB,EAAQ,IAAY,CAC1C,EAAwB,EACxB,IAAM,EAAWC,EAAAA,gBAAgB,EAAa,CAAO,EAAG,CAAM,EAC1D,EAAS,SAAWD,EAAAA,iBAAiB,SAAS,EAAiB,EAAS,EAAS,OAAO,CAC9F,CAAC,EAEM,CACT"}
@@ -0,0 +1,14 @@
1
+ import { AutoStopDelays } from "../../services/idlePolicy.cjs";
2
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/idleShutdown.d.ts
4
+ /**
5
+ * Watches the session for idleness and returns the disposer for that watch.
6
+ *
7
+ * Pi's `on` hands back nothing to unsubscribe with, so the only resource this
8
+ * owns across a reload is the armed timer — and an armed timer is the one that
9
+ * matters, because firing it would stop the session Pi has just reloaded into.
10
+ */
11
+ declare function registerIdleShutdown(pi: ExtensionAPI, delays: AutoStopDelays): () => void;
12
+ //#endregion
13
+ export { registerIdleShutdown };
14
+ //# sourceMappingURL=idleShutdown.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idleShutdown.d.cts","names":[],"sources":["../../../src/adapters/pi/idleShutdown.ts"],"mappings":";;;;;;;;;;iBAoBgB,qBAAqB,IAAI,cAAc,QAAQ"}
@@ -0,0 +1,14 @@
1
+ import { AutoStopDelays } from "../../services/idlePolicy.mjs";
2
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/idleShutdown.d.ts
4
+ /**
5
+ * Watches the session for idleness and returns the disposer for that watch.
6
+ *
7
+ * Pi's `on` hands back nothing to unsubscribe with, so the only resource this
8
+ * owns across a reload is the armed timer — and an armed timer is the one that
9
+ * matters, because firing it would stop the session Pi has just reloaded into.
10
+ */
11
+ declare function registerIdleShutdown(pi: ExtensionAPI, delays: AutoStopDelays): () => void;
12
+ //#endregion
13
+ export { registerIdleShutdown };
14
+ //# sourceMappingURL=idleShutdown.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idleShutdown.d.mts","names":[],"sources":["../../../src/adapters/pi/idleShutdown.ts"],"mappings":";;;;;;;;;;iBAoBgB,qBAAqB,IAAI,cAAc,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import{AUTO_STOP_ACTION as e,decideOnRecheck as t,decideOnSettled as n}from"../../services/idlePolicy.mjs";function r(e){return{hasPendingMessages:e.hasPendingMessages(),isIdle:e.isIdle()}}function i(i,a){let o,s=()=>{o&&=(clearTimeout(o),void 0)},c=(n,i)=>{o=setTimeout(()=>{o=void 0;let i=t(r(n),a);if(i.action===e.shutdown){n.shutdown();return}i.action===e.recheck&&c(n,i.delayMs)},i)};return i.on(`input`,s),i.on(`agent_start`,s),i.on(`agent_settled`,(t,i)=>{s();let o=n(r(i),a);o.action===e.recheck&&c(i,o.delayMs)}),s}export{i as registerIdleShutdown};
2
+ //# sourceMappingURL=idleShutdown.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idleShutdown.mjs","names":[],"sources":["../../../src/adapters/pi/idleShutdown.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n AUTO_STOP_ACTION,\n type AutoStopDelays,\n type SessionActivity,\n decideOnRecheck,\n decideOnSettled,\n} from '../../services/idlePolicy.ts';\n\nfunction readActivity(context: ExtensionContext): SessionActivity {\n return { hasPendingMessages: context.hasPendingMessages(), isIdle: context.isIdle() };\n}\n\n/**\n * Watches the session for idleness and returns the disposer for that watch.\n *\n * Pi's `on` hands back nothing to unsubscribe with, so the only resource this\n * owns across a reload is the armed timer — and an armed timer is the one that\n * matters, because firing it would stop the session Pi has just reloaded into.\n */\nexport function registerIdleShutdown(pi: ExtensionAPI, delays: AutoStopDelays): () => void {\n let shutdownTimer: NodeJS.Timeout | undefined;\n\n const cancelScheduledShutdown = (): void => {\n if (!shutdownTimer) return;\n clearTimeout(shutdownTimer);\n shutdownTimer = undefined;\n };\n\n const scheduleShutdown = (context: ExtensionContext, delayMs: number): void => {\n shutdownTimer = setTimeout(() => {\n shutdownTimer = undefined;\n const decision = decideOnRecheck(readActivity(context), delays);\n if (decision.action === AUTO_STOP_ACTION.shutdown) {\n context.shutdown();\n return;\n }\n if (decision.action === AUTO_STOP_ACTION.recheck) scheduleShutdown(context, decision.delayMs);\n }, delayMs);\n };\n\n pi.on('input', cancelScheduledShutdown);\n pi.on('agent_start', cancelScheduledShutdown);\n\n pi.on('agent_settled', (_event, context) => {\n cancelScheduledShutdown();\n const decision = decideOnSettled(readActivity(context), delays);\n if (decision.action === AUTO_STOP_ACTION.recheck) scheduleShutdown(context, decision.delayMs);\n });\n\n return cancelScheduledShutdown;\n}\n"],"mappings":"2GASA,SAAS,EAAa,EAA4C,CAChE,MAAO,CAAE,mBAAoB,EAAQ,mBAAmB,EAAG,OAAQ,EAAQ,OAAO,CAAE,CACtF,CASA,SAAgB,EAAqB,EAAkB,EAAoC,CACzF,IAAI,EAEE,MAAsC,CACrC,AAEL,KADA,aAAa,CAAa,EACV,IAAA,GAClB,EAEM,GAAoB,EAA2B,IAA0B,CAC7E,EAAgB,eAAiB,CAC/B,EAAgB,IAAA,GAChB,IAAM,EAAW,EAAgB,EAAa,CAAO,EAAG,CAAM,EAC9D,GAAI,EAAS,SAAW,EAAiB,SAAU,CACjD,EAAQ,SAAS,EACjB,MACF,CACI,EAAS,SAAW,EAAiB,SAAS,EAAiB,EAAS,EAAS,OAAO,CAC9F,EAAG,CAAO,CACZ,EAWA,OATA,EAAG,GAAG,QAAS,CAAuB,EACtC,EAAG,GAAG,cAAe,CAAuB,EAE5C,EAAG,GAAG,iBAAkB,EAAQ,IAAY,CAC1C,EAAwB,EACxB,IAAM,EAAW,EAAgB,EAAa,CAAO,EAAG,CAAM,EAC1D,EAAS,SAAW,EAAiB,SAAS,EAAiB,EAAS,EAAS,OAAO,CAC9F,CAAC,EAEM,CACT"}
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../adapters/pi/extension.cjs");exports.autoStopExtension=e.autoStopExtension,exports.default=e.autoStopExtension;
@@ -0,0 +1,2 @@
1
+ import { autoStopExtension } from "../adapters/pi/extension.cjs";
2
+ export { autoStopExtension, autoStopExtension as default };
@@ -0,0 +1,2 @@
1
+ import { autoStopExtension } from "../adapters/pi/extension.mjs";
2
+ export { autoStopExtension, autoStopExtension as default };
@@ -0,0 +1 @@
1
+ import{autoStopExtension as e}from"../adapters/pi/extension.mjs";export{e as autoStopExtension,e as default};
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/idlePolicy.cjs"),t=require("./adapters/pi/idleShutdown.cjs"),n=require("./adapters/pi/extension.cjs");exports.AUTO_STOP_ACTION=e.AUTO_STOP_ACTION,exports.DEFAULT_AUTO_STOP_DELAYS=e.DEFAULT_AUTO_STOP_DELAYS,exports.autoStopExtension=n.autoStopExtension,exports.decideOnRecheck=e.decideOnRecheck,exports.decideOnSettled=e.decideOnSettled,exports.registerIdleShutdown=t.registerIdleShutdown;
@@ -0,0 +1,4 @@
1
+ import { AUTO_STOP_ACTION, AutoStopAction, AutoStopDecision, AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, SessionActivity, decideOnRecheck, decideOnSettled } from "./services/idlePolicy.cjs";
2
+ import { autoStopExtension } from "./adapters/pi/extension.cjs";
3
+ import { registerIdleShutdown } from "./adapters/pi/idleShutdown.cjs";
4
+ export { AUTO_STOP_ACTION, type AutoStopAction, type AutoStopDecision, type AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, type SessionActivity, autoStopExtension, decideOnRecheck, decideOnSettled, registerIdleShutdown };
@@ -0,0 +1,4 @@
1
+ import { AUTO_STOP_ACTION, AutoStopAction, AutoStopDecision, AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, SessionActivity, decideOnRecheck, decideOnSettled } from "./services/idlePolicy.mjs";
2
+ import { autoStopExtension } from "./adapters/pi/extension.mjs";
3
+ import { registerIdleShutdown } from "./adapters/pi/idleShutdown.mjs";
4
+ export { AUTO_STOP_ACTION, type AutoStopAction, type AutoStopDecision, type AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, type SessionActivity, autoStopExtension, decideOnRecheck, decideOnSettled, registerIdleShutdown };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{AUTO_STOP_ACTION as e,DEFAULT_AUTO_STOP_DELAYS as t,decideOnRecheck as n,decideOnSettled as r}from"./services/idlePolicy.mjs";import{registerIdleShutdown as i}from"./adapters/pi/idleShutdown.mjs";import{autoStopExtension as a}from"./adapters/pi/extension.mjs";export{e as AUTO_STOP_ACTION,t as DEFAULT_AUTO_STOP_DELAYS,a as autoStopExtension,n as decideOnRecheck,r as decideOnSettled,i as registerIdleShutdown};
@@ -0,0 +1,2 @@
1
+ const e={cooldownMs:5e3,recheckMs:100},t={shutdown:`shutdown`,recheck:`recheck`,standDown:`stand-down`};function n(e,n){return e.hasPendingMessages?{action:t.standDown}:{action:t.recheck,delayMs:n.cooldownMs}}function r(e,n){return e.hasPendingMessages?{action:t.standDown}:e.isIdle?{action:t.shutdown}:{action:t.recheck,delayMs:n.recheckMs}}exports.AUTO_STOP_ACTION=t,exports.DEFAULT_AUTO_STOP_DELAYS=e,exports.decideOnRecheck=r,exports.decideOnSettled=n;
2
+ //# sourceMappingURL=idlePolicy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idlePolicy.cjs","names":[],"sources":["../../src/services/idlePolicy.ts"],"sourcesContent":["/** What the session was doing the moment the policy was asked. */\nexport interface SessionActivity {\n readonly hasPendingMessages: boolean;\n readonly isIdle: boolean;\n}\n\n/** How long the policy waits before each look at the session. */\nexport interface AutoStopDelays {\n /** Grace period after the agent settles, so a follow-up prompt still lands. */\n readonly cooldownMs: number;\n /** Gap between looks while the agent is settled but still streaming. */\n readonly recheckMs: number;\n}\n\nexport const DEFAULT_AUTO_STOP_DELAYS: AutoStopDelays = {\n cooldownMs: 5_000,\n recheckMs: 100,\n};\n\nexport const AUTO_STOP_ACTION = {\n /** The session has nothing left to do and can exit. */\n shutdown: 'shutdown',\n /** Look again after the returned delay. */\n recheck: 'recheck',\n /** The user is back; stop watching until the agent settles again. */\n standDown: 'stand-down',\n} as const;\n\nexport type AutoStopAction = (typeof AUTO_STOP_ACTION)[keyof typeof AUTO_STOP_ACTION];\n\nexport type AutoStopDecision =\n | { readonly action: typeof AUTO_STOP_ACTION.shutdown }\n | { readonly action: typeof AUTO_STOP_ACTION.recheck; readonly delayMs: number }\n | { readonly action: typeof AUTO_STOP_ACTION.standDown };\n\n/**\n * Taken when the agent reports it has settled.\n *\n * Settling is not enough on its own: a queued message means the session is\n * about to run again, and even an empty queue gets the cooldown so a user\n * typing their next prompt is not cut off mid-word.\n */\nexport function decideOnSettled(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision {\n if (activity.hasPendingMessages) return { action: AUTO_STOP_ACTION.standDown };\n return { action: AUTO_STOP_ACTION.recheck, delayMs: delays.cooldownMs };\n}\n\n/**\n * Taken when a scheduled look comes due.\n *\n * The agent can still be streaming after it settled, so a session that is not\n * yet idle is polled rather than stopped.\n */\nexport function decideOnRecheck(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision {\n if (activity.hasPendingMessages) return { action: AUTO_STOP_ACTION.standDown };\n if (activity.isIdle) return { action: AUTO_STOP_ACTION.shutdown };\n return { action: AUTO_STOP_ACTION.recheck, delayMs: delays.recheckMs };\n}\n"],"mappings":"AAcA,MAAa,EAA2C,CACtD,WAAY,IACZ,UAAW,GACb,EAEa,EAAmB,CAE9B,SAAU,WAEV,QAAS,UAET,UAAW,YACb,EAgBA,SAAgB,EAAgB,EAA2B,EAA0C,CAEnG,OADI,EAAS,mBAA2B,CAAE,OAAQ,EAAiB,SAAU,EACtE,CAAE,OAAQ,EAAiB,QAAS,QAAS,EAAO,UAAW,CACxE,CAQA,SAAgB,EAAgB,EAA2B,EAA0C,CAGnG,OAFI,EAAS,mBAA2B,CAAE,OAAQ,EAAiB,SAAU,EACzE,EAAS,OAAe,CAAE,OAAQ,EAAiB,QAAS,EACzD,CAAE,OAAQ,EAAiB,QAAS,QAAS,EAAO,SAAU,CACvE"}
@@ -0,0 +1,49 @@
1
+ //#region src/services/idlePolicy.d.ts
2
+ /** What the session was doing the moment the policy was asked. */
3
+ interface SessionActivity {
4
+ readonly hasPendingMessages: boolean;
5
+ readonly isIdle: boolean;
6
+ }
7
+ /** How long the policy waits before each look at the session. */
8
+ interface AutoStopDelays {
9
+ /** Grace period after the agent settles, so a follow-up prompt still lands. */
10
+ readonly cooldownMs: number;
11
+ /** Gap between looks while the agent is settled but still streaming. */
12
+ readonly recheckMs: number;
13
+ }
14
+ declare const DEFAULT_AUTO_STOP_DELAYS: AutoStopDelays;
15
+ declare const AUTO_STOP_ACTION: {
16
+ /** The session has nothing left to do and can exit. */
17
+ readonly shutdown: "shutdown";
18
+ /** Look again after the returned delay. */
19
+ readonly recheck: "recheck";
20
+ /** The user is back; stop watching until the agent settles again. */
21
+ readonly standDown: "stand-down";
22
+ };
23
+ type AutoStopAction = (typeof AUTO_STOP_ACTION)[keyof typeof AUTO_STOP_ACTION];
24
+ type AutoStopDecision = {
25
+ readonly action: typeof AUTO_STOP_ACTION.shutdown;
26
+ } | {
27
+ readonly action: typeof AUTO_STOP_ACTION.recheck;
28
+ readonly delayMs: number;
29
+ } | {
30
+ readonly action: typeof AUTO_STOP_ACTION.standDown;
31
+ };
32
+ /**
33
+ * Taken when the agent reports it has settled.
34
+ *
35
+ * Settling is not enough on its own: a queued message means the session is
36
+ * about to run again, and even an empty queue gets the cooldown so a user
37
+ * typing their next prompt is not cut off mid-word.
38
+ */
39
+ declare function decideOnSettled(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision;
40
+ /**
41
+ * Taken when a scheduled look comes due.
42
+ *
43
+ * The agent can still be streaming after it settled, so a session that is not
44
+ * yet idle is polled rather than stopped.
45
+ */
46
+ declare function decideOnRecheck(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision;
47
+ //#endregion
48
+ export { AUTO_STOP_ACTION, AutoStopAction, AutoStopDecision, AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, SessionActivity, decideOnRecheck, decideOnSettled };
49
+ //# sourceMappingURL=idlePolicy.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idlePolicy.d.cts","names":[],"sources":["../../src/services/idlePolicy.ts"],"mappings":";;UACiB;WACN;WACA;;;UAIM;;WAEN;;WAEA;;cAGE,0BAA0B;cAK1B;;;;;;;;KASD,yBAAyB,+BAA+B;KAExD;WACG,eAAe,iBAAiB;;WAChC,eAAe,iBAAiB;WAAkB;;WAClD,eAAe,iBAAiB;;;;;;;;;iBAS/B,gBAAgB,UAAU,iBAAiB,QAAQ,iBAAiB;;;;;;;iBAWpE,gBAAgB,UAAU,iBAAiB,QAAQ,iBAAiB"}
@@ -0,0 +1,49 @@
1
+ //#region src/services/idlePolicy.d.ts
2
+ /** What the session was doing the moment the policy was asked. */
3
+ interface SessionActivity {
4
+ readonly hasPendingMessages: boolean;
5
+ readonly isIdle: boolean;
6
+ }
7
+ /** How long the policy waits before each look at the session. */
8
+ interface AutoStopDelays {
9
+ /** Grace period after the agent settles, so a follow-up prompt still lands. */
10
+ readonly cooldownMs: number;
11
+ /** Gap between looks while the agent is settled but still streaming. */
12
+ readonly recheckMs: number;
13
+ }
14
+ declare const DEFAULT_AUTO_STOP_DELAYS: AutoStopDelays;
15
+ declare const AUTO_STOP_ACTION: {
16
+ /** The session has nothing left to do and can exit. */
17
+ readonly shutdown: "shutdown";
18
+ /** Look again after the returned delay. */
19
+ readonly recheck: "recheck";
20
+ /** The user is back; stop watching until the agent settles again. */
21
+ readonly standDown: "stand-down";
22
+ };
23
+ type AutoStopAction = (typeof AUTO_STOP_ACTION)[keyof typeof AUTO_STOP_ACTION];
24
+ type AutoStopDecision = {
25
+ readonly action: typeof AUTO_STOP_ACTION.shutdown;
26
+ } | {
27
+ readonly action: typeof AUTO_STOP_ACTION.recheck;
28
+ readonly delayMs: number;
29
+ } | {
30
+ readonly action: typeof AUTO_STOP_ACTION.standDown;
31
+ };
32
+ /**
33
+ * Taken when the agent reports it has settled.
34
+ *
35
+ * Settling is not enough on its own: a queued message means the session is
36
+ * about to run again, and even an empty queue gets the cooldown so a user
37
+ * typing their next prompt is not cut off mid-word.
38
+ */
39
+ declare function decideOnSettled(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision;
40
+ /**
41
+ * Taken when a scheduled look comes due.
42
+ *
43
+ * The agent can still be streaming after it settled, so a session that is not
44
+ * yet idle is polled rather than stopped.
45
+ */
46
+ declare function decideOnRecheck(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision;
47
+ //#endregion
48
+ export { AUTO_STOP_ACTION, AutoStopAction, AutoStopDecision, AutoStopDelays, DEFAULT_AUTO_STOP_DELAYS, SessionActivity, decideOnRecheck, decideOnSettled };
49
+ //# sourceMappingURL=idlePolicy.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idlePolicy.d.mts","names":[],"sources":["../../src/services/idlePolicy.ts"],"mappings":";;UACiB;WACN;WACA;;;UAIM;;WAEN;;WAEA;;cAGE,0BAA0B;cAK1B;;;;;;;;KASD,yBAAyB,+BAA+B;KAExD;WACG,eAAe,iBAAiB;;WAChC,eAAe,iBAAiB;WAAkB;;WAClD,eAAe,iBAAiB;;;;;;;;;iBAS/B,gBAAgB,UAAU,iBAAiB,QAAQ,iBAAiB;;;;;;;iBAWpE,gBAAgB,UAAU,iBAAiB,QAAQ,iBAAiB"}
@@ -0,0 +1,2 @@
1
+ const e={cooldownMs:5e3,recheckMs:100},t={shutdown:`shutdown`,recheck:`recheck`,standDown:`stand-down`};function n(e,n){return e.hasPendingMessages?{action:t.standDown}:{action:t.recheck,delayMs:n.cooldownMs}}function r(e,n){return e.hasPendingMessages?{action:t.standDown}:e.isIdle?{action:t.shutdown}:{action:t.recheck,delayMs:n.recheckMs}}export{t as AUTO_STOP_ACTION,e as DEFAULT_AUTO_STOP_DELAYS,r as decideOnRecheck,n as decideOnSettled};
2
+ //# sourceMappingURL=idlePolicy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idlePolicy.mjs","names":[],"sources":["../../src/services/idlePolicy.ts"],"sourcesContent":["/** What the session was doing the moment the policy was asked. */\nexport interface SessionActivity {\n readonly hasPendingMessages: boolean;\n readonly isIdle: boolean;\n}\n\n/** How long the policy waits before each look at the session. */\nexport interface AutoStopDelays {\n /** Grace period after the agent settles, so a follow-up prompt still lands. */\n readonly cooldownMs: number;\n /** Gap between looks while the agent is settled but still streaming. */\n readonly recheckMs: number;\n}\n\nexport const DEFAULT_AUTO_STOP_DELAYS: AutoStopDelays = {\n cooldownMs: 5_000,\n recheckMs: 100,\n};\n\nexport const AUTO_STOP_ACTION = {\n /** The session has nothing left to do and can exit. */\n shutdown: 'shutdown',\n /** Look again after the returned delay. */\n recheck: 'recheck',\n /** The user is back; stop watching until the agent settles again. */\n standDown: 'stand-down',\n} as const;\n\nexport type AutoStopAction = (typeof AUTO_STOP_ACTION)[keyof typeof AUTO_STOP_ACTION];\n\nexport type AutoStopDecision =\n | { readonly action: typeof AUTO_STOP_ACTION.shutdown }\n | { readonly action: typeof AUTO_STOP_ACTION.recheck; readonly delayMs: number }\n | { readonly action: typeof AUTO_STOP_ACTION.standDown };\n\n/**\n * Taken when the agent reports it has settled.\n *\n * Settling is not enough on its own: a queued message means the session is\n * about to run again, and even an empty queue gets the cooldown so a user\n * typing their next prompt is not cut off mid-word.\n */\nexport function decideOnSettled(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision {\n if (activity.hasPendingMessages) return { action: AUTO_STOP_ACTION.standDown };\n return { action: AUTO_STOP_ACTION.recheck, delayMs: delays.cooldownMs };\n}\n\n/**\n * Taken when a scheduled look comes due.\n *\n * The agent can still be streaming after it settled, so a session that is not\n * yet idle is polled rather than stopped.\n */\nexport function decideOnRecheck(activity: SessionActivity, delays: AutoStopDelays): AutoStopDecision {\n if (activity.hasPendingMessages) return { action: AUTO_STOP_ACTION.standDown };\n if (activity.isIdle) return { action: AUTO_STOP_ACTION.shutdown };\n return { action: AUTO_STOP_ACTION.recheck, delayMs: delays.recheckMs };\n}\n"],"mappings":"AAcA,MAAa,EAA2C,CACtD,WAAY,IACZ,UAAW,GACb,EAEa,EAAmB,CAE9B,SAAU,WAEV,QAAS,UAET,UAAW,YACb,EAgBA,SAAgB,EAAgB,EAA2B,EAA0C,CAEnG,OADI,EAAS,mBAA2B,CAAE,OAAQ,EAAiB,SAAU,EACtE,CAAE,OAAQ,EAAiB,QAAS,QAAS,EAAO,UAAW,CACxE,CAQA,SAAgB,EAAgB,EAA2B,EAA0C,CAGnG,OAFI,EAAS,mBAA2B,CAAE,OAAQ,EAAiB,SAAU,EACzE,EAAS,OAAe,CAAE,OAAQ,EAAiB,QAAS,EACzD,CAAE,OAAQ,EAAiB,QAAS,QAAS,EAAO,SAAU,CACvE"}
package/llms.txt ADDED
@@ -0,0 +1,7 @@
1
+ # Doom Pi Autostop
2
+
3
+ Shuts a Doom Pi session down once the agent settles and stays idle.
4
+
5
+ ## Resources
6
+
7
+ - [Package README](./README.md): the idle policy, the cooldown, and when the session is left alone.
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@agimon-ai/doompi-autostop",
3
+ "version": "0.0.1-alpha.2",
4
+ "description": "Shuts a DoomPi session down once the agent settles and stays idle.",
5
+ "keywords": [
6
+ "ai",
7
+ "auto-stop",
8
+ "coding-agent",
9
+ "developer-tools",
10
+ "doompi",
11
+ "idle-timeout",
12
+ "pi-coding-agent",
13
+ "pi-extension",
14
+ "pi-package",
15
+ "session-shutdown"
16
+ ],
17
+ "homepage": "https://agimon.ai",
18
+ "license": "MIT",
19
+ "author": "Vuong Ngo",
20
+ "files": [
21
+ "dist",
22
+ "llms.txt",
23
+ "README.md",
24
+ "LICENSE",
25
+ "package.json"
26
+ ],
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "types": "./dist/index.d.mts",
30
+ "jsnext:main": "./dist/index.mjs",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.mts",
34
+ "import": "./dist/index.mjs",
35
+ "require": "./dist/index.cjs"
36
+ },
37
+ "./extensions/pi": {
38
+ "types": "./dist/extensions/pi.d.mts",
39
+ "import": "./dist/extensions/pi.mjs",
40
+ "require": "./dist/extensions/pi.cjs"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@deepseek-ai/cordis": "4.0.1"
49
+ },
50
+ "devDependencies": {
51
+ "@earendil-works/pi-coding-agent": "0.84.2",
52
+ "@types/node": "26.1.1",
53
+ "@vitest/coverage-v8": "4.1.10",
54
+ "tsdown": "0.22.14",
55
+ "typescript": "6.0.3",
56
+ "vitest": "4.1.10",
57
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.24"
58
+ },
59
+ "peerDependencies": {
60
+ "@earendil-works/pi-coding-agent": "0.84.2"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@earendil-works/pi-coding-agent": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "engines": {
68
+ "node": ">=22.19.0"
69
+ },
70
+ "pi": {
71
+ "extensions": [
72
+ "./dist/extensions/pi.mjs"
73
+ ]
74
+ },
75
+ "scripts": {
76
+ "build": "tsdown",
77
+ "test": "vitest --run",
78
+ "typecheck": "tsc --noEmit",
79
+ "lint": "oxlint . && oxfmt . --check",
80
+ "fixcode": "oxlint . --fix && oxfmt ."
81
+ }
82
+ }