@tekmidian/pai 0.59.0 → 0.60.1

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 (129) hide show
  1. package/.claude-plugin/plugin.json +8 -33
  2. package/PLUGIN-ARCHITECTURE.md +4 -6
  3. package/dist/{aibroker-client-B8Wtasez.mjs → aibroker-client-BYG-GtAg.mjs} +1 -1
  4. package/dist/{chain-Cltx0NjY.mjs → chain-CGyAIHo4.mjs} +4 -4
  5. package/dist/{chain-Cltx0NjY.mjs.map → chain-CGyAIHo4.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +12 -12
  7. package/dist/cli/program.mjs +12 -12
  8. package/dist/{config-D1omG_UU.mjs → config-CQCmH_gV.mjs} +17 -4
  9. package/dist/{config-D1omG_UU.mjs.map → config-CQCmH_gV.mjs.map} +1 -1
  10. package/dist/{config-DCYWFaEi.mjs → config-DjXq_W42.mjs} +1 -1
  11. package/dist/{context-handover-cache-Dt4HW1Ro.mjs → context-handover-cache-BzM7Qudn.mjs} +4 -4
  12. package/dist/{context-handover-cache-Dt4HW1Ro.mjs.map → context-handover-cache-BzM7Qudn.mjs.map} +1 -1
  13. package/dist/daemon/index.mjs +12 -12
  14. package/dist/{daemon-CmqJfeb9.mjs → daemon-B_TodsIS.mjs} +97 -23
  15. package/dist/daemon-B_TodsIS.mjs.map +1 -0
  16. package/dist/daemon-jviL3VVJ.mjs +19 -0
  17. package/dist/daemon-mcp/index.mjs +9 -9
  18. package/dist/daemon-mcp/index.mjs.map +1 -1
  19. package/dist/{factory-C9wGUe30.mjs → factory-DW7BCJJT.mjs} +1 -1
  20. package/dist/{factory-DiuntvwG.mjs → factory-EUUhSSoq.mjs} +7 -8
  21. package/dist/factory-EUUhSSoq.mjs.map +1 -0
  22. package/dist/{fallback-B_2ozQHw.mjs → fallback-CjdakQVu.mjs} +80 -9
  23. package/dist/fallback-CjdakQVu.mjs.map +1 -0
  24. package/dist/hooks/block-sleep-poll.mjs +7590 -0
  25. package/dist/hooks/block-sleep-poll.mjs.map +7 -0
  26. package/dist/hooks/capture-all-events.mjs +8 -4
  27. package/dist/hooks/capture-all-events.mjs.map +2 -2
  28. package/dist/hooks/capture-session-summary.mjs.map +1 -1
  29. package/dist/hooks/capture-tool-output.mjs.map +1 -1
  30. package/dist/hooks/cleanup-session-files.mjs.map +1 -1
  31. package/dist/hooks/context-compression-hook.mjs +6 -6
  32. package/dist/hooks/context-compression-hook.mjs.map +3 -3
  33. package/dist/hooks/initialize-session.mjs.map +1 -1
  34. package/dist/hooks/load-core-context.mjs.map +1 -1
  35. package/dist/hooks/load-project-context.mjs.map +2 -2
  36. package/dist/hooks/post-compact-inject.mjs +1 -1
  37. package/dist/hooks/post-compact-inject.mjs.map +2 -2
  38. package/dist/hooks/route-agents-to-worker.mjs +5 -4
  39. package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
  40. package/dist/hooks/security-validator.mjs +5 -4
  41. package/dist/hooks/security-validator.mjs.map +3 -3
  42. package/dist/hooks/stop-hook.mjs.map +1 -1
  43. package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
  44. package/dist/hooks/whisper-rules.mjs +5 -4
  45. package/dist/hooks/whisper-rules.mjs.map +3 -3
  46. package/dist/hooks/worker-proxy.mjs +5 -4
  47. package/dist/hooks/worker-proxy.mjs.map +3 -3
  48. package/dist/hooks/worker-status-line.mjs +10 -7
  49. package/dist/hooks/worker-status-line.mjs.map +3 -3
  50. package/dist/hooks/worker-supervision.mjs +5 -4
  51. package/dist/hooks/worker-supervision.mjs.map +3 -3
  52. package/dist/{main-resolver-DYNxxNwJ.mjs → main-resolver-BFQfU5xo.mjs} +100 -21
  53. package/dist/main-resolver-BFQfU5xo.mjs.map +1 -0
  54. package/dist/main-resolver-VMBENifT.mjs +7 -0
  55. package/dist/{planner-DsZZEZCA.mjs → planner-DN967Lvm.mjs} +83 -30
  56. package/dist/planner-DN967Lvm.mjs.map +1 -0
  57. package/dist/{program-B5miXqQa.mjs → program-BnsjB57Q.mjs} +278 -92
  58. package/dist/program-BnsjB57Q.mjs.map +1 -0
  59. package/dist/router-C5hPk9YP.mjs +3 -0
  60. package/dist/{router-BdxdsAvs.mjs → router-DjFZIoDl.mjs} +2 -1
  61. package/dist/router-DjFZIoDl.mjs.map +1 -0
  62. package/dist/{run-XaA-UPdW.mjs → run-FeM1_UVa.mjs} +102 -18
  63. package/dist/run-FeM1_UVa.mjs.map +1 -0
  64. package/dist/{run-env-DyTWgZC9.mjs → run-env-CEHTeIUM.mjs} +2 -2
  65. package/dist/{run-env-DyTWgZC9.mjs.map → run-env-CEHTeIUM.mjs.map} +1 -1
  66. package/dist/{server-CzqHGbKt.mjs → server-CekGOJcp.mjs} +2 -2
  67. package/dist/{server-CzqHGbKt.mjs.map → server-CekGOJcp.mjs.map} +1 -1
  68. package/dist/{session-keepalive-DDsEo81Q.mjs → session-keepalive-Bc9aT678.mjs} +5 -5
  69. package/dist/{session-keepalive-DDsEo81Q.mjs.map → session-keepalive-Bc9aT678.mjs.map} +1 -1
  70. package/dist/{tools-Diro2s8J.mjs → tools-qXyHFwNj.mjs} +2 -2
  71. package/dist/{tools-Diro2s8J.mjs.map → tools-qXyHFwNj.mjs.map} +1 -1
  72. package/dist/{tools-KgL1rkPD.mjs → tools-r-7ZG8B5.mjs} +3 -3
  73. package/dist/{wakeup-D0uvqLsE.mjs → wakeup-C48hcN6O.mjs} +2 -2
  74. package/dist/wakeup-C48hcN6O.mjs.map +1 -0
  75. package/dist/{work-queue-worker-DL9lu8sN.mjs → work-queue-worker-BL4dq5P7.mjs} +2 -2
  76. package/dist/{work-queue-worker-DL9lu8sN.mjs.map → work-queue-worker-BL4dq5P7.mjs.map} +1 -1
  77. package/dist/work-queue-worker-DVOVcEnO.mjs +11 -0
  78. package/docker/init.sql +1 -1
  79. package/docs/commands/README.md +4 -4
  80. package/docs/commands/daemon.md +6 -6
  81. package/docs/commands/session.md +2 -2
  82. package/docs/commands/worker.md +5 -2
  83. package/docs/workers-config.md +5 -1
  84. package/package.json +1 -1
  85. package/plugins/context-preservation/hooks/hooks.json +38 -31
  86. package/plugins/context-preservation/plugin.json +1 -4
  87. package/plugins/core/hooks/hooks.json +51 -46
  88. package/plugins/core/plugin.json +1 -4
  89. package/plugins/creative/plugin.json +1 -5
  90. package/plugins/observability/hooks/hooks.json +87 -72
  91. package/plugins/observability/plugin.json +1 -5
  92. package/plugins/productivity/hooks/hooks.json +22 -19
  93. package/plugins/productivity/plugin.json +2 -5
  94. package/plugins/semantic-search/plugin.json +1 -16
  95. package/plugins/ui/hooks/hooks.json +17 -14
  96. package/plugins/ui/plugin.json +1 -5
  97. package/plugins/zettelkasten/plugin.json +1 -14
  98. package/src/hooks/session-autosave.sh +4 -2
  99. package/src/hooks/session-stop.sh +2 -2
  100. package/src/hooks/ts/capture-all-events.ts +3 -3
  101. package/src/hooks/ts/lib/metadata-extraction.ts +3 -2
  102. package/src/hooks/ts/lib/pai-paths.test.ts +1 -1
  103. package/src/hooks/ts/lib/sleep-poll-gate.test.ts +62 -0
  104. package/src/hooks/ts/lib/sleep-poll-gate.ts +77 -0
  105. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +1 -1
  106. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +1 -1
  107. package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
  108. package/src/hooks/ts/pre-tool-use/block-sleep-poll.test.ts +44 -0
  109. package/src/hooks/ts/pre-tool-use/block-sleep-poll.ts +50 -0
  110. package/src/hooks/ts/session-end/capture-session-summary.ts +1 -1
  111. package/src/hooks/ts/session-start/initialize-session.ts +2 -2
  112. package/src/hooks/ts/session-start/load-core-context.ts +2 -2
  113. package/src/hooks/ts/session-start/load-project-context.ts +1 -1
  114. package/src/hooks/ts/stop/stop-hook.ts +1 -1
  115. package/src/hooks/ts/user-prompt/cleanup-session-files.ts +1 -1
  116. package/statusline-command.sh +26 -11
  117. package/dist/daemon-B6hx1Ysz.mjs +0 -19
  118. package/dist/daemon-CmqJfeb9.mjs.map +0 -1
  119. package/dist/factory-DiuntvwG.mjs.map +0 -1
  120. package/dist/fallback-B_2ozQHw.mjs.map +0 -1
  121. package/dist/main-resolver-DYNxxNwJ.mjs.map +0 -1
  122. package/dist/main-resolver-Df65bPAi.mjs +0 -7
  123. package/dist/planner-DsZZEZCA.mjs.map +0 -1
  124. package/dist/program-B5miXqQa.mjs.map +0 -1
  125. package/dist/router-BXOf_e9e.mjs +0 -3
  126. package/dist/router-BdxdsAvs.mjs.map +0 -1
  127. package/dist/run-XaA-UPdW.mjs.map +0 -1
  128. package/dist/wakeup-D0uvqLsE.mjs.map +0 -1
  129. package/dist/work-queue-worker-BY7xn4pw.mjs +0 -11
@@ -1,3 +1,3 @@
1
- import { a as getStorageBackend, i as getRegistryBackend, n as createRegistryBackend, r as createStorageBackend, t as closeStorage } from "./factory-DiuntvwG.mjs";
1
+ import { a as getStorageBackend, i as getRegistryBackend, n as createRegistryBackend, r as createStorageBackend, t as closeStorage } from "./factory-EUUhSSoq.mjs";
2
2
 
3
3
  export { createStorageBackend, getRegistryBackend };
@@ -85,8 +85,8 @@ const ESCALATE_AFTER_ATTEMPTS = 5;
85
85
  /** Tell the user the backend is down, through whatever channels are configured. */
86
86
  async function notifyBackendDown(attempts, lastError) {
87
87
  try {
88
- const { routeNotification } = await import("./router-BXOf_e9e.mjs");
89
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
88
+ const { routeNotification } = await import("./router-C5hPk9YP.mjs");
89
+ const { loadConfig } = await import("./config-DjXq_W42.mjs");
90
90
  await routeNotification({
91
91
  event: "error",
92
92
  title: "PAI: storage backend unreachable",
@@ -97,8 +97,8 @@ async function notifyBackendDown(attempts, lastError) {
97
97
  /** And say when it comes back, so the alert is not left hanging. */
98
98
  async function notifyBackendRecovered(attempts, since) {
99
99
  try {
100
- const { routeNotification } = await import("./router-BXOf_e9e.mjs");
101
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
100
+ const { routeNotification } = await import("./router-C5hPk9YP.mjs");
101
+ const { loadConfig } = await import("./config-DjXq_W42.mjs");
102
102
  const mins = since ? Math.max(1, Math.round((Date.now() - since) / 6e4)) : null;
103
103
  await routeNotification({
104
104
  event: "completion",
@@ -121,7 +121,6 @@ async function retryConnect(attempt, waitForever, label) {
121
121
  const result = await attempt();
122
122
  if ("backend" in result) {
123
123
  if (attemptN > 1) process.stderr.write(`[pai-daemon] Connected to ${label} (after ${attemptN} attempts).\n`);
124
- else process.stderr.write(`[pai-daemon] Connected to ${label}.\n`);
125
124
  clearBackendOutage();
126
125
  if (escalated) notifyBackendRecovered(attemptN, outageSince);
127
126
  return result.backend;
@@ -210,7 +209,7 @@ function registerBeforeExit() {
210
209
  */
211
210
  async function getStorageBackend() {
212
211
  if (!storagePromise) storagePromise = (async () => {
213
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
212
+ const { loadConfig } = await import("./config-DjXq_W42.mjs");
214
213
  registerBeforeExit();
215
214
  return await createStorageBackend(loadConfig());
216
215
  })();
@@ -219,7 +218,7 @@ async function getStorageBackend() {
219
218
  /** Returns the process-wide RegistryBackend, creating it on first call. Same synchronous-caching requirement as getStorageBackend(). */
220
219
  async function getRegistryBackend() {
221
220
  if (!registryPromise) registryPromise = (async () => {
222
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
221
+ const { loadConfig } = await import("./config-DjXq_W42.mjs");
223
222
  registerBeforeExit();
224
223
  return await createRegistryBackend(loadConfig());
225
224
  })();
@@ -247,4 +246,4 @@ async function closeStorage() {
247
246
 
248
247
  //#endregion
249
248
  export { getStorageBackend as a, getRegistryBackend as i, createRegistryBackend as n, getBackendOutage as o, createStorageBackend as r, humanDuration as s, closeStorage as t };
250
- //# sourceMappingURL=factory-DiuntvwG.mjs.map
249
+ //# sourceMappingURL=factory-EUUhSSoq.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory-EUUhSSoq.mjs","names":[],"sources":["../src/storage/outage.ts","../src/storage/factory.ts"],"sourcesContent":["/**\n * outage.ts — is the storage backend actually reachable right now?\n *\n * The daemon retries a dead backend forever, which is correct: a Postgres\n * container that is down will usually come back, and giving up would lose the\n * work queue. What was wrong is that it did so in complete silence.\n *\n * Observed 2026-07-26: the container was down for roughly two days. The daemon\n * logged \"Postgres unavailable\" 144 times over 36 minutes, the work queue backed\n * up, session notes for the whole period were never written — and\n * `pai daemon status` reported \"Index: idle\" throughout. The one command anyone\n * would run to check said everything was fine.\n *\n * So the outage is recorded where the status command can see it, and escalated\n * once through the notification channels that were already configured and\n * already unused for this.\n */\n\nexport interface BackendOutage {\n backend: string;\n /** When the current run of failures began. */\n since: number;\n /** Consecutive failed attempts so far. */\n attempts: number;\n lastError: string;\n}\n\nlet current: BackendOutage | null = null;\n\n/** Record that the backend is currently unreachable. */\nexport function setBackendOutage(outage: BackendOutage): void {\n current = outage;\n}\n\n/**\n * Record that the backend answered.\n *\n * Called on every successful connection, including the first — so a daemon that\n * never had a problem reports none, and one that recovered stops reporting an\n * outage that has ended.\n */\nexport function clearBackendOutage(): void {\n current = null;\n}\n\n/** The current outage, or null when the backend is answering. */\nexport function getBackendOutage(): BackendOutage | null {\n return current;\n}\n\n/**\n * Human-readable elapsed time, for a status line rather than a log.\n *\n * Exported because the status command needs the duration on its own, to colour\n * it separately from the rest of the sentence. Without this it kept a second\n * copy of the same rounding, which is how two renderings of one outage drift.\n */\nexport function humanDuration(ms: number): string {\n const mins = Math.max(1, Math.round(ms / 60_000));\n return mins < 60 ? `${mins} min` : `${(mins / 60).toFixed(1)} h`;\n}\n\n/** Human-readable duration, for a status line rather than a log. */\nexport function describeOutage(o: BackendOutage, now = Date.now()): string {\n return `${o.backend} unreachable for ${humanDuration(now - o.since)}, ${o.attempts} attempts — ${o.lastError}`;\n}\n","/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n *\n * When Postgres is the configured backend we NEVER silently fall back to\n * SQLite — doing so would split the corpus across two databases. Instead:\n * - Daemon (waitForPostgres: true) retries Postgres forever with capped\n * backoff until it comes up (handles the boot race where launchd starts\n * the daemon before Docker Desktop / Postgres is ready).\n * - CLI / one-shot callers (default) retry a few times, then throw a clear\n * error rather than returning a wrong/empty SQLite database.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\nimport type { RegistryBackend } from \"./registry-interface.js\";\nimport { setBackendOutage, clearBackendOutage } from \"./outage.js\";\n\nexport interface StorageBackendOptions {\n /**\n * When true, retry Postgres indefinitely instead of giving up. Used by the\n * long-lived daemon so a not-yet-ready Postgres at boot is tolerated.\n * Defaults to false (one-shot CLI behaviour: bounded retries, then throw).\n */\n waitForPostgres?: boolean;\n}\n\n/** Backoff schedule (ms) for the bounded CLI retry path. */\nconst CLI_RETRY_DELAYS_MS = [500, 1_000, 2_000];\n\n/** Backoff cap (ms) for the daemon's infinite retry path. */\nconst DAEMON_RETRY_CAP_MS = 15_000;\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend (retried; never falls back)\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig,\n opts: StorageBackendOptions = {}\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await getSharedPostgresBackend(config, opts.waitForPostgres ?? false);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\n/**\n * Attempt a single Postgres connection (ensure DB + test). Returns the live\n * backend on success, or an error string describing why it failed.\n */\nasync function attemptPostgres(\n config: PaiDaemonConfig\n): Promise<{ backend: StorageBackend } | { error: string }> {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n\n let backend: InstanceType<typeof PostgresBackend> | null = null;\n try {\n // Ensure the per-user database exists and has the schema applied.\n await PostgresBackend.ensureDatabase(pgConfig);\n\n backend = new PostgresBackend(pgConfig);\n const err = await backend.testConnection();\n if (err) {\n await backend.close().catch(() => {});\n return { error: err };\n }\n return { backend };\n } catch (e) {\n if (backend) await backend.close().catch(() => {});\n return { error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/**\n * Consecutive failures before the outage is escalated to the user.\n *\n * Not the first failure: a container restarting, or the daemon starting before\n * Docker is up, recovers within a few seconds and is not worth a notification.\n * By the fifth attempt the backoff has already spent tens of seconds, which is\n * long enough that something is actually wrong.\n */\nconst ESCALATE_AFTER_ATTEMPTS = 5;\n\n/** Tell the user the backend is down, through whatever channels are configured. */\nasync function notifyBackendDown(attempts: number, lastError: string): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n await routeNotification(\n {\n event: \"error\",\n title: \"PAI: storage backend unreachable\",\n message:\n `Postgres has not answered in ${attempts} attempts (${lastError}). ` +\n `Indexing, session notes and the work queue are stalled until it returns. ` +\n `Check the container, then \\`pai daemon status\\`.`,\n },\n loadConfig().notifications\n );\n } catch {\n // A notification that cannot be sent must never take the daemon down with\n // it — the daemon retrying is still the useful behaviour here.\n }\n}\n\n/** And say when it comes back, so the alert is not left hanging. */\nasync function notifyBackendRecovered(\n attempts: number,\n since: number | null\n): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n const mins = since ? Math.max(1, Math.round((Date.now() - since) / 60_000)) : null;\n await routeNotification(\n {\n event: \"completion\",\n title: \"PAI: storage backend back\",\n message:\n `Postgres answered after ${attempts} attempts` +\n (mins ? `, ${mins} min down` : \"\") +\n `. The queue will drain on its own.`,\n },\n loadConfig().notifications\n );\n } catch {\n /* same reasoning as above */\n }\n}\n\n/**\n * Shared retry loop: attempt a connection, back off, escalate, repeat — used\n * by both the federation and registry Postgres connectors so the retry/outage\n * behaviour (§6, \"never falls back to SQLite silently\") stays in one place.\n */\nasync function retryConnect<T>(\n attempt: () => Promise<{ backend: T } | { error: string }>,\n waitForever: boolean,\n label: string\n): Promise<T> {\n let attemptN = 0;\n let outageSince: number | null = null;\n let escalated = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n attemptN++;\n const result = await attempt();\n if (\"backend\" in result) {\n if (attemptN > 1) process.stderr.write(`[pai-daemon] Connected to ${label} (after ${attemptN} attempts).\\n`);\n // An outage that ended must stop being reported, or the status command\n // trades one wrong answer for another.\n clearBackendOutage();\n if (escalated) void notifyBackendRecovered(attemptN, outageSince);\n return result.backend;\n }\n\n const lastError = result.error;\n\n if (!waitForever && attemptN > CLI_RETRY_DELAYS_MS.length) {\n // Bounded CLI path exhausted — fail loudly, never silently use SQLite.\n throw new Error(\n `${label} unreachable after ${attemptN} attempts: ${lastError}. ` +\n `Is Docker Desktop / Postgres running? Refusing to fall back to SQLite ` +\n `(would split the corpus). Start Postgres and retry.`\n );\n }\n\n const delayMs = waitForever\n ? Math.min(DAEMON_RETRY_CAP_MS, 1_000 * 2 ** Math.min(attemptN - 1, 4))\n : CLI_RETRY_DELAYS_MS[attemptN - 1];\n\n process.stderr.write(\n `[pai-daemon] ${label} unavailable (${lastError}). ` +\n `Retry ${attemptN}${waitForever ? \"\" : `/${CLI_RETRY_DELAYS_MS.length + 1}`} ` +\n `in ${delayMs}ms...\\n`\n );\n\n // Publish the outage so `pai daemon status` can report it. Without this the\n // daemon retries silently forever and status still reads \"idle\" — which is\n // what happened for two days in July: 144 retries over 36 minutes, session\n // notes never written, and the one command anyone would run to check\n // reporting that everything was fine.\n setBackendOutage({\n backend: label,\n since: outageSince ?? (outageSince = Date.now()),\n attempts: attemptN,\n lastError: String(lastError),\n });\n\n // And escalate once, out loud, rather than only into a log nobody tails.\n // Once — not per retry — because a notification that repeats every few\n // seconds is filtered within a minute and stops being a signal at all.\n if (waitForever && attemptN === ESCALATE_AFTER_ATTEMPTS && !escalated) {\n escalated = true;\n void notifyBackendDown(attemptN, String(lastError));\n }\n\n await new Promise((r) => setTimeout(r, delayMs));\n }\n}\n\nasync function connectPostgres(\n config: PaiDaemonConfig,\n waitForever: boolean\n): Promise<StorageBackend> {\n return retryConnect(() => attemptPostgres(config), waitForever, \"PostgreSQL backend\");\n}\n\n/**\n * The process's one Postgres connection: storage and registry share a\n * single pg Pool (one connection budget) instead of each opening their own.\n * Whichever of createStorageBackend()/createRegistryBackend() runs first\n * creates it via connectPostgres()'s retry/outage handling; the other\n * reuses its pool through PostgresBackend.getPool(). Reset by closeStorage().\n */\nlet pgBackendPromise: Promise<StorageBackend> | null = null;\n\nfunction getSharedPostgresBackend(\n config: PaiDaemonConfig,\n waitForever: boolean\n): Promise<StorageBackend> {\n if (!pgBackendPromise) {\n pgBackendPromise = connectPostgres(config, waitForever);\n }\n return pgBackendPromise;\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"./sqlite/federation-db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n\n/**\n * Create and return the configured RegistryBackend (projects, sessions,\n * tags, aliases, links, compaction_log). Same auto-behaviour and same\n * never-fall-back-to-SQLite rule as createStorageBackend().\n */\nexport async function createRegistryBackend(\n config: PaiDaemonConfig,\n opts: StorageBackendOptions = {}\n): Promise<RegistryBackend> {\n if (config.storageBackend === \"postgres\") {\n // Reuses the storage backend's pool (getSharedPostgresBackend) rather\n // than opening a second one against the same database.\n const storage = await getSharedPostgresBackend(config, opts.waitForPostgres ?? false);\n const { PostgresRegistryBackend } = await import(\"./registry-postgres.js\");\n const pool = (storage as unknown as { getPool: () => import(\"pg\").Pool }).getPool();\n return new PostgresRegistryBackend(pool);\n }\n return createSQLiteRegistryBackend();\n}\n\nasync function createSQLiteRegistryBackend(): Promise<RegistryBackend> {\n const { openRegistry } = await import(\"./sqlite/registry-db.js\");\n const { SQLiteRegistryBackend } = await import(\"./registry-sqlite.js\");\n const db = openRegistry();\n return new SQLiteRegistryBackend(db);\n}\n\n/**\n * Process-wide backend accessors.\n *\n * Callers across the codebase must get their StorageBackend/RegistryBackend\n * from here rather than constructing SQLite or Postgres directly — this is\n * the one place that decides which backend a process uses, and the one place\n * that closes it on exit.\n */\n\nlet storagePromise: Promise<StorageBackend> | null = null;\nlet registryPromise: Promise<RegistryBackend> | null = null;\nlet beforeExitRegistered = false;\n\nfunction registerBeforeExit(): void {\n if (beforeExitRegistered) return;\n beforeExitRegistered = true;\n process.once(\"beforeExit\", () => {\n void closeStorage();\n });\n}\n\n/**\n * Returns the process-wide StorageBackend, creating it on first call.\n *\n * The whole body up to and including the `storagePromise = (async () =>\n * ...)()` assignment must run synchronously (no `await` before it) — two\n * calls issued in the same tick (e.g. `Promise.all([get(), get()])`) must\n * both observe the same cached promise rather than each racing to create\n * their own backend.\n */\nexport async function getStorageBackend(): Promise<StorageBackend> {\n if (!storagePromise) {\n storagePromise = (async () => {\n const { loadConfig } = await import(\"../daemon/config.js\");\n registerBeforeExit();\n return await createStorageBackend(loadConfig());\n })();\n }\n return storagePromise;\n}\n\n/** Returns the process-wide RegistryBackend, creating it on first call. Same synchronous-caching requirement as getStorageBackend(). */\nexport async function getRegistryBackend(): Promise<RegistryBackend> {\n if (!registryPromise) {\n registryPromise = (async () => {\n const { loadConfig } = await import(\"../daemon/config.js\");\n registerBeforeExit();\n return await createRegistryBackend(loadConfig());\n })();\n }\n return registryPromise;\n}\n\n/** Closes any backends created via getStorageBackend()/getRegistryBackend(). Idempotent. */\nexport async function closeStorage(): Promise<void> {\n const pending = [storagePromise, registryPromise];\n const pg = pgBackendPromise;\n storagePromise = null;\n registryPromise = null;\n pgBackendPromise = null;\n for (const p of pending) {\n if (!p) continue;\n try {\n const backend = await p;\n // Postgres backends share one pool owned by pgBackendPromise (closed\n // below) — closing them here too would end() it twice.\n if (backend.backendType === \"postgres\") continue;\n await backend.close();\n } catch {\n // A backend that never connected has nothing to close.\n }\n }\n if (pg) {\n try {\n await (await pg).close();\n } catch {\n // Never connected — nothing to close.\n }\n }\n}\n\n/** Test-only: clear the process-wide cache without closing (tests own their own lifecycle). */\nexport function __resetStorageForTests(): void {\n storagePromise = null;\n registryPromise = null;\n pgBackendPromise = null;\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { _ as writeYamlFileAtomic, d as readMainConfigRaw, f as writeMainConfigRaw, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE, r as DEFAULTS, s as loadConfig, u as paiConfigYamlFilePath, v as readJsonStrict, y as writeJsonAtomic } from "./config-D1omG_UU.mjs";
2
- import { C as providerCostTier, D as resolveModelCapability, E as resolveCapability, L as workersYamlPath, O as resolveProviderKey, S as nativeAnthropicProvider, T as readWorkersSection, _ as expandHome, a as DEFAULT_CACHE_KEEPALIVE_SECS, b as keysDir, c as DEFAULT_ROUTING, d as PROVIDER_TAGS, k as writeWorkersSection, l as DEFAULT_TREE, o as DEFAULT_LOG_DIR, p as WorkersConfigError, r as ANTHROPIC_NATIVE, s as DEFAULT_PANE, u as MODEL_CAPABILITIES, x as maskKey, y as isModelCapability } from "./run-env-DyTWgZC9.mjs";
3
- import { A as contextLabel, D as UNLABELED, G as shortText, I as loadStatuses, M as elapsedOf, N as isChatPane, O as ageOf, P as isLive, T as appendLedger, b as sessionTag, d as workerDepth, h as currentTabKey, j as contextPercent, k as alive, l as readInbox, o as parseWorkerReport, s as renderReport, u as sayToWorker, v as resolveSession, x as workerInScope } from "./run-XaA-UPdW.mjs";
4
- import { a as clearCooldown, c as probeQuota, d as resolveTarget, h as ledgerPath, l as quotaSkipThreshold, m as eventsPath, v as workersLogDir } from "./server-CzqHGbKt.mjs";
1
+ import { _ as writeYamlFileAtomic, d as readMainConfigRaw, f as writeMainConfigRaw, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE, r as DEFAULTS, s as loadConfig, u as paiConfigYamlFilePath, v as readJsonStrict, y as writeJsonAtomic } from "./config-CQCmH_gV.mjs";
2
+ import { C as providerCostTier, D as resolveModelCapability, E as resolveCapability, L as workersYamlPath, O as resolveProviderKey, S as nativeAnthropicProvider, T as readWorkersSection, _ as expandHome, a as DEFAULT_CACHE_KEEPALIVE_SECS, b as keysDir, c as DEFAULT_ROUTING, d as PROVIDER_TAGS, k as writeWorkersSection, l as DEFAULT_TREE, o as DEFAULT_LOG_DIR, p as WorkersConfigError, r as ANTHROPIC_NATIVE, s as DEFAULT_PANE, u as MODEL_CAPABILITIES, x as maskKey, y as isModelCapability } from "./run-env-CEHTeIUM.mjs";
3
+ import { A as UNLABELED, C as workerInScope, F as elapsedOf, I as isChatPane, J as shortText, L as isLive, M as alive, N as contextLabel, O as appendLedger, P as contextPercent, S as sessionTag, _ as currentTabKey, b as resolveSession, d as workerDepth, g as markControlsHeld, h as clickrControlsArgv, j as ageOf, l as readInbox, o as parseWorkerReport, s as renderReport, u as sayToWorker, z as loadStatuses } from "./run-FeM1_UVa.mjs";
4
+ import { a as clearCooldown, c as probeQuota, d as resolveTarget, h as ledgerPath, l as quotaSkipThreshold, m as eventsPath, v as workersLogDir } from "./server-CekGOJcp.mjs";
5
5
  import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { basename, join, relative, resolve } from "node:path";
@@ -251,6 +251,7 @@ const CHAT_HELP = [
251
251
  "/quit close this pane",
252
252
  "/resume <text> continue the finished worker with <text>",
253
253
  "/status one-line worker status",
254
+ "your controls exclusive clickr control to this worker (my controls takes it back)",
254
255
  "anything else is sent to the worker — said while it runs, resumed after"
255
256
  ];
256
257
  /** Index just past the escape starting at `i` (CSI, OSC or a two-char one). */
@@ -454,11 +455,14 @@ function chatInsertLine(line, fill, regionRows) {
454
455
  fill: Math.min(fill + 1, regionRows)
455
456
  };
456
457
  }
458
+ const CONTROLS_LINE = /^(your|my) controls\.?$/i;
457
459
  /**
458
460
  * One submitted prompt line → what to do with it. `/help`, `/quit`,
459
461
  * `/status` and `/resume <text>` are commands (a bare `/resume` comes back
460
- * with empty text so the caller can print its usage); anything else,
461
- * including any other `/word`, is a message for the worker.
462
+ * with empty text so the caller can print its usage); "your controls" /
463
+ * "my controls" is the same `pai worker controls <id> you|me` action, never
464
+ * forwarded to the worker as a normal chat line; anything else, including
465
+ * any other `/word`, is a message for the worker.
462
466
  */
463
467
  function parseChatLine(raw) {
464
468
  const text = raw.trim();
@@ -469,6 +473,11 @@ function parseChatLine(raw) {
469
473
  kind: "resume",
470
474
  text: text.slice(7).trim()
471
475
  };
476
+ const controls = text.match(CONTROLS_LINE);
477
+ if (controls) return {
478
+ kind: "controls",
479
+ who: controls[1].toLowerCase() === "your" ? "you" : "me"
480
+ };
472
481
  return {
473
482
  kind: "message",
474
483
  text
@@ -1239,6 +1248,25 @@ async function followWorkers(logDir, target, showAll, autoExit, env = process.en
1239
1248
  } else noteLine(c("red", `» resume failed (rc=${rc})`));
1240
1249
  });
1241
1250
  };
1251
+ const spawnControls = io?.spawnControls ?? ((id, who, pid) => spawn("clickr", clickrControlsArgv(id, who, pid), { stdio: [
1252
+ "ignore",
1253
+ "pipe",
1254
+ "pipe"
1255
+ ] }));
1256
+ const controlsTarget = (id, who) => {
1257
+ const pid = loadStatuses(logDir).find((s) => s.id === id)?.pid;
1258
+ const child = spawnControls(id, who, pid);
1259
+ let errText = "";
1260
+ child.stderr?.on("data", (chunk) => {
1261
+ errText += chunk.toString("utf8");
1262
+ });
1263
+ child.on("close", (rc) => {
1264
+ if (rc === 0) {
1265
+ if (who === "you") markControlsHeld(logDir, id);
1266
+ noteLine(c("dim", `» controls → ${who === "you" ? `${id} (exclusive; needs clickr with --agent support)` : "operator"}`));
1267
+ } else noteLine(c("red", `» controls failed (rc=${rc})${errText.trim() ? `: ${errText.trim()}` : ""}`));
1268
+ });
1269
+ };
1242
1270
  const handleOperatorLine = makeOperatorInput({
1243
1271
  target: () => target,
1244
1272
  say: (id, text) => sayToWorker(logDir, id, text),
@@ -1313,6 +1341,10 @@ async function followWorkers(logDir, target, showAll, autoExit, env = process.en
1313
1341
  redrew = true;
1314
1342
  echoOperator(act.text);
1315
1343
  break;
1344
+ case "controls":
1345
+ if (target !== null) controlsTarget(target, act.who);
1346
+ else out(c("dim", "no target worker for controls"));
1347
+ break;
1316
1348
  case "help":
1317
1349
  for (const ln of CHAT_HELP) out(c("dim", ln));
1318
1350
  break;
@@ -1669,12 +1701,51 @@ function removeProvider(name, configPath) {
1669
1701
  writeWorkersSection(raw, workers, configPath);
1670
1702
  return workers;
1671
1703
  }
1704
+ /**
1705
+ * Point a class target at `name`, keeping every other field. Only touches
1706
+ * targets that already pin a provider — a constraint-only object (no
1707
+ * `provider`) only narrows auto-routing and is left alone. A string target's
1708
+ * `/alias` is dropped (falling back to `name`'s default model) when `name`
1709
+ * has no such model configured — the class must move to `name` either way,
1710
+ * since leaving it on the old provider is exactly the bug this fixes.
1711
+ */
1712
+ function repointClassTarget(target, name, providers) {
1713
+ if (typeof target === "string") {
1714
+ const [provider, alias] = target.split("/");
1715
+ if (provider === name) return null;
1716
+ if (alias && name !== ANTHROPIC_NATIVE && alias !== "default" && !providers[name]?.models[alias]) return name;
1717
+ return alias ? `${name}/${alias}` : name;
1718
+ }
1719
+ if (!target.provider || target.provider === name) return null;
1720
+ return {
1721
+ ...target,
1722
+ provider: name
1723
+ };
1724
+ }
1725
+ /**
1726
+ * Make `name` the active provider. Unless `name` is "auto", also re-points
1727
+ * every class whose target pins a provider (string or `{provider: …}`) at
1728
+ * it — a class pinned to a provider otherwise wins over `active` at run time
1729
+ * (routing.ts resolveTarget), so leaving classes untouched would make `use`
1730
+ * silently do nothing for any class already pinned elsewhere.
1731
+ */
1672
1732
  function useProvider(name, configPath) {
1673
1733
  const { raw, workers } = readWorkersSection(configPath);
1674
- if (name !== ANTHROPIC_NATIVE && !workers.providers[name]) throw new WorkersConfigError(`no provider named "${name}". Configured: ${Object.keys(workers.providers).join(", ") || "(none)"}`);
1734
+ if (name !== ANTHROPIC_NATIVE && name !== "auto" && !workers.providers[name]) throw new WorkersConfigError(`no provider named "${name}". Configured: ${Object.keys(workers.providers).join(", ") || "(none)"}`);
1675
1735
  workers.active = name;
1736
+ let repointed = 0;
1737
+ if (name !== "auto") for (const [cls, target] of Object.entries(workers.classes)) {
1738
+ const next = repointClassTarget(target, name, workers.providers);
1739
+ if (next !== null) {
1740
+ workers.classes[cls] = next;
1741
+ repointed++;
1742
+ }
1743
+ }
1676
1744
  writeWorkersSection(raw, workers, configPath);
1677
- return workers;
1745
+ return {
1746
+ workers,
1747
+ repointed
1748
+ };
1678
1749
  }
1679
1750
  /**
1680
1751
  * The provider a model command targets: the named one, else the active one.
@@ -2135,4 +2206,4 @@ function fallbackStatusText(s) {
2135
2206
 
2136
2207
  //#endregion
2137
2208
  export { getConfigValueOp as A, psOutput as C, resolveSpecPath as D, readSpecPrompt as E, setConfigValueOp as M, unsetConfigValueOp as N, MainConfigOpsError as O, followWorkers as S, statusLineOutput as T, setWorkersEnabled as _, addProvider as a, updateProvider as b, describeModels as c, removeProvider as d, resolveProviderName as f, setProviderModel as g, setProviderEnabled as h, fallbackStatusText as i, listConfigOp as j, formatConfigGetOutput as k, describeProviders as l, setClass as m, fallbackOn as n, classTargetText as o, setCapabilityPreference as p, fallbackStatus as r, describeCapabilities as s, fallbackOff as t, modelPrefsText as u, unsetCapabilityPreference as v, replayOutput as w, useProvider as x, unsetClass as y };
2138
- //# sourceMappingURL=fallback-B_2ozQHw.mjs.map
2209
+ //# sourceMappingURL=fallback-CjdakQVu.mjs.map