@tekmidian/pai 0.58.0 → 0.60.0

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-DFlzAfDE.mjs} +4 -4
  5. package/dist/{chain-Cltx0NjY.mjs.map → chain-DFlzAfDE.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +12 -12
  7. package/dist/cli/program.mjs +12 -12
  8. package/dist/{config-DCYWFaEi.mjs → config-GceCMUxa.mjs} +1 -1
  9. package/dist/{config-D1omG_UU.mjs → config-msER8xj_.mjs} +16 -4
  10. package/dist/config-msER8xj_.mjs.map +1 -0
  11. package/dist/{context-handover-cache-Dt4HW1Ro.mjs → context-handover-cache-DF0xWVB0.mjs} +4 -4
  12. package/dist/{context-handover-cache-Dt4HW1Ro.mjs.map → context-handover-cache-DF0xWVB0.mjs.map} +1 -1
  13. package/dist/daemon/index.mjs +12 -12
  14. package/dist/daemon-BppwJtrH.mjs +19 -0
  15. package/dist/{daemon-CmqJfeb9.mjs → daemon-DArRKBlk.mjs} +97 -23
  16. package/dist/daemon-DArRKBlk.mjs.map +1 -0
  17. package/dist/daemon-mcp/index.mjs +9 -9
  18. package/dist/daemon-mcp/index.mjs.map +1 -1
  19. package/dist/{factory-DiuntvwG.mjs → factory-ChfAvCzO.mjs} +7 -7
  20. package/dist/{factory-DiuntvwG.mjs.map → factory-ChfAvCzO.mjs.map} +1 -1
  21. package/dist/{factory-C9wGUe30.mjs → factory-D6I2jmjQ.mjs} +1 -1
  22. package/dist/{fallback-B_2ozQHw.mjs → fallback-B7VEMMRn.mjs} +80 -9
  23. package/dist/fallback-B7VEMMRn.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 +4 -3
  39. package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
  40. package/dist/hooks/security-validator.mjs +4 -3
  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 +4 -3
  45. package/dist/hooks/whisper-rules.mjs.map +3 -3
  46. package/dist/hooks/worker-proxy.mjs +4 -3
  47. package/dist/hooks/worker-proxy.mjs.map +3 -3
  48. package/dist/hooks/worker-status-line.mjs +9 -6
  49. package/dist/hooks/worker-status-line.mjs.map +3 -3
  50. package/dist/hooks/worker-supervision.mjs +4 -3
  51. package/dist/hooks/worker-supervision.mjs.map +3 -3
  52. package/dist/{main-resolver-DYNxxNwJ.mjs → main-resolver-BqTjt-GL.mjs} +100 -21
  53. package/dist/main-resolver-BqTjt-GL.mjs.map +1 -0
  54. package/dist/main-resolver-CCojcYr2.mjs +7 -0
  55. package/dist/{planner-DsZZEZCA.mjs → planner-DbpsMZqY.mjs} +83 -30
  56. package/dist/planner-DbpsMZqY.mjs.map +1 -0
  57. package/dist/{program-D-WkmvuQ.mjs → program-QSuci3RH.mjs} +325 -93
  58. package/dist/program-QSuci3RH.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-BFxuNnIJ.mjs} +100 -17
  63. package/dist/run-BFxuNnIJ.mjs.map +1 -0
  64. package/dist/{run-env-DyTWgZC9.mjs → run-env-kbWVwUvU.mjs} +2 -2
  65. package/dist/{run-env-DyTWgZC9.mjs.map → run-env-kbWVwUvU.mjs.map} +1 -1
  66. package/dist/{server-CzqHGbKt.mjs → server-B0vBmfJt.mjs} +2 -2
  67. package/dist/{server-CzqHGbKt.mjs.map → server-B0vBmfJt.mjs.map} +1 -1
  68. package/dist/{session-keepalive-DDsEo81Q.mjs → session-keepalive-CUcFa8ld.mjs} +5 -5
  69. package/dist/{session-keepalive-DDsEo81Q.mjs.map → session-keepalive-CUcFa8ld.mjs.map} +1 -1
  70. package/dist/{tools-Diro2s8J.mjs → tools-CaTZnIau.mjs} +2 -2
  71. package/dist/{tools-Diro2s8J.mjs.map → tools-CaTZnIau.mjs.map} +1 -1
  72. package/dist/{tools-KgL1rkPD.mjs → tools-_7MF8Ebb.mjs} +3 -3
  73. package/dist/{wakeup-D0uvqLsE.mjs → wakeup-CTX3rlz-.mjs} +2 -2
  74. package/dist/wakeup-CTX3rlz-.mjs.map +1 -0
  75. package/dist/{work-queue-worker-DL9lu8sN.mjs → work-queue-worker-B4eZGy5n.mjs} +2 -2
  76. package/dist/{work-queue-worker-DL9lu8sN.mjs.map → work-queue-worker-B4eZGy5n.mjs.map} +1 -1
  77. package/dist/work-queue-worker-DuhZmfDB.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/config-D1omG_UU.mjs.map +0 -1
  118. package/dist/daemon-B6hx1Ysz.mjs +0 -19
  119. package/dist/daemon-CmqJfeb9.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-D-WkmvuQ.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
@@ -3,28 +3,28 @@ import { a as slugify$1, i as parseSessionFilename } from "./migrate-BD7D8EEh.mj
3
3
  import { n as ensurePaiMarker } from "./pai-marker-D1MMswkz.mjs";
4
4
  import { t as STOP_WORDS } from "./stop-words-ClCwO8mZ.mjs";
5
5
  import { t as applyRecencyBoost } from "./search-BaTbyWjK.mjs";
6
- import { a as getStorageBackend, i as getRegistryBackend, r as createStorageBackend, s as humanDuration } from "./factory-DiuntvwG.mjs";
7
- import { a as formatDetectionJson, i as formatDetection, n as migrateIdentityFile, r as detectProject } from "./wakeup-D0uvqLsE.mjs";
8
- import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-Dt4HW1Ro.mjs";
6
+ import { a as getStorageBackend, i as getRegistryBackend, r as createStorageBackend, s as humanDuration } from "./factory-ChfAvCzO.mjs";
7
+ import { a as formatDetectionJson, i as formatDetection, n as migrateIdentityFile, r as detectProject } from "./wakeup-CTX3rlz-.mjs";
8
+ import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-DF0xWVB0.mjs";
9
9
  import { a as paiHomePath, i as paiHomeDir, n as migratePaiDir, o as resolvePaiFile, r as migratePaiFile, t as PaiFileMigrationError } from "./pai-home-CeOIYltV.mjs";
10
- import { b as OWNER_LABEL_PREFIX, c as migrateConfigFile, d as readMainConfigRaw, f as writeMainConfigRaw, h as resolvedMainConfigPath, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE$2, o as expandHome, p as MainConfigError, s as loadConfig, t as CONFIG_DIR, u as paiConfigYamlFilePath, v as readJsonStrict, x as UNROUTED, y as writeJsonAtomic } from "./config-D1omG_UU.mjs";
10
+ import { b as OWNER_LABEL_PREFIX, c as migrateConfigFile, d as readMainConfigRaw, f as writeMainConfigRaw, h as resolvedMainConfigPath, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE$2, o as expandHome, p as MainConfigError, s as loadConfig, t as CONFIG_DIR, u as paiConfigYamlFilePath, v as readJsonStrict, x as UNROUTED, y as writeJsonAtomic } from "./config-msER8xj_.mjs";
11
11
  import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-BRwDeBEg.mjs";
12
- import { A as initWorkersYaml, B as stripModelVariant, F as relocateWorkersYaml, I as workersYamlLegacyNotice, L as workersYamlPath, M as migrateWorkersToYaml, N as needsWorkersYamlRelocation, P as parseWorkersYamlDocument, R as DEFAULT_CONTEXT_WINDOW, S as nativeAnthropicProvider, T as readWorkersSection, _ as expandHome$1, d as PROVIDER_TAGS, j as inlineWorkersYamlKeys, p as WorkersConfigError, r as ANTHROPIC_NATIVE, t as buildRunEnv, u as MODEL_CAPABILITIES, v as getProviderOrNative, x as maskKey, y as isModelCapability, z as contextWindowFromModelId } from "./run-env-DyTWgZC9.mjs";
13
- import { B as saveStatus, C as mergeWorker, D as UNLABELED, F as loadStatus, H as waitForTerminalStatus, I as loadStatuses, K as describeMcp, N as isChatPane, P as isLive, S as discardWorker, T as appendLedger, U as longInlinePromptHint, V as setWorkerLabel, W as parseRunnerArgs, a as projectLaunchConfig, c as handoffFromInside, f as checkPaneForWorker, i as usageContextTokens, m as openPaneForWorker, n as runWorker, o as parseWorkerReport, p as openFollowPane, q as expandMcpNames, r as testProvider, u as sayToWorker, z as ownsPid } from "./run-XaA-UPdW.mjs";
12
+ import { A as initWorkersYaml, B as stripModelVariant, F as relocateWorkersYaml, I as workersYamlLegacyNotice, L as workersYamlPath, M as migrateWorkersToYaml, N as needsWorkersYamlRelocation, P as parseWorkersYamlDocument, R as DEFAULT_CONTEXT_WINDOW, S as nativeAnthropicProvider, T as readWorkersSection, _ as expandHome$1, d as PROVIDER_TAGS, j as inlineWorkersYamlKeys, p as WorkersConfigError, r as ANTHROPIC_NATIVE, t as buildRunEnv, u as MODEL_CAPABILITIES, v as getProviderOrNative, x as maskKey, y as isModelCapability, z as contextWindowFromModelId } from "./run-env-kbWVwUvU.mjs";
13
+ import { A as UNLABELED, E as mergeWorker, G as waitForTerminalStatus, H as ownsPid, I as isChatPane, K as longInlinePromptHint, L as isLive, O as appendLedger, R as loadStatus, U as saveStatus, W as setWorkerLabel, X as expandMcpNames, Y as describeMcp, a as projectLaunchConfig, c as handoffFromInside, f as checkPaneForWorker, g as markControlsHeld, h as clickrControlsArgv, i as usageContextTokens, m as openPaneForWorker, n as runWorker, o as parseWorkerReport, p as openFollowPane, q as parseRunnerArgs, r as testProvider, u as sayToWorker, w as discardWorker, z as loadStatuses } from "./run-BFxuNnIJ.mjs";
14
14
  import { t as MergeError } from "./merge-xoyfZQ4W.mjs";
15
15
  import { n as oldFederationPath, t as federationDbPath } from "./federation-db-w8e-sKjp.mjs";
16
16
  import { r as registryDbPath, t as oldRegistryPath } from "./registry-db-Bkpbp7Gf.mjs";
17
17
  import { i as indexProjectWithBackend } from "./async-CLvuY7b-.mjs";
18
18
  import { t as PaiClient } from "./ipc-client-Dasm515c.mjs";
19
- import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, S as followWorkers, T as statusLineOutput, _ as setWorkersEnabled, a as addProvider, b as updateProvider, c as describeModels, d as removeProvider, f as resolveProviderName, g as setProviderModel, h as setProviderEnabled, i as fallbackStatusText, j as listConfigOp, k as formatConfigGetOutput, l as describeProviders, m as setClass, n as fallbackOn, o as classTargetText, p as setCapabilityPreference, r as fallbackStatus, s as describeCapabilities, t as fallbackOff, u as modelPrefsText, v as unsetCapabilityPreference, w as replayOutput, x as useProvider, y as unsetClass } from "./fallback-B_2ozQHw.mjs";
20
- import { _ as scanCacheFilePath, a as buildDeduped, c as hasConversation, d as restoreTopLevel, f as resumeArgvText, g as resolveSessionByNameOrId, h as migrateSessionScanCache, l as launchInDir, m as fmtAge, o as normalizeName$2, p as printExitDir, s as renderDedupedSessions, t as cmdMain, u as providerResumePlan, v as scanSessions } from "./main-resolver-DYNxxNwJ.mjs";
19
+ import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, S as followWorkers, T as statusLineOutput, _ as setWorkersEnabled, a as addProvider, b as updateProvider, c as describeModels, d as removeProvider, f as resolveProviderName, g as setProviderModel, h as setProviderEnabled, i as fallbackStatusText, j as listConfigOp, k as formatConfigGetOutput, l as describeProviders, m as setClass, n as fallbackOn, o as classTargetText, p as setCapabilityPreference, r as fallbackStatus, s as describeCapabilities, t as fallbackOff, u as modelPrefsText, v as unsetCapabilityPreference, w as replayOutput, x as useProvider, y as unsetClass } from "./fallback-B7VEMMRn.mjs";
20
+ import { _ as resolveSessionByNameOrId, a as buildDeduped, c as hasConversation, d as providerResumePlan, f as restoreTopLevel, g as migrateSessionScanCache, h as fmtAge, l as launchInDir, m as printExitDir, o as normalizeName$2, p as resumeArgvText, s as renderDedupedSessions, t as cmdMain, u as launchPrompt, v as scanCacheFilePath, y as scanSessions } from "./main-resolver-BqTjt-GL.mjs";
21
21
  import { n as queriesDirPath, t as migrateQueriesDir } from "./query-feedback-xz5axTj3.mjs";
22
- import { h as ledgerPath, m as eventsPath, n as ensureProxyRunning, p as ensureNoMcpConfig, r as stopProxy, t as DEFAULT_PROXY_PORT, v as workersLogDir } from "./server-CzqHGbKt.mjs";
22
+ import { h as ledgerPath, m as eventsPath, n as ensureProxyRunning, p as ensureNoMcpConfig, r as stopProxy, t as DEFAULT_PROXY_PORT, v as workersLogDir } from "./server-B0vBmfJt.mjs";
23
23
  import { i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-BK1h--W_.mjs";
24
- import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-DDsEo81Q.mjs";
25
- import { t as runChain } from "./chain-Cltx0NjY.mjs";
24
+ import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-CUcFa8ld.mjs";
25
+ import { t as runChain } from "./chain-DFlzAfDE.mjs";
26
26
  import { appendFileSync, chmodSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
27
- import { homedir, platform, tmpdir } from "node:os";
27
+ import { homedir, platform, tmpdir, userInfo } from "node:os";
28
28
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
29
29
  import chalk from "chalk";
30
30
  import { createHash, randomUUID } from "node:crypto";
@@ -161,7 +161,7 @@ async function cmdPromote(opts) {
161
161
  console.error(dim$1(` Pick a durable location for the new project.`));
162
162
  process.exit(1);
163
163
  }
164
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
164
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
165
165
  const registryBackend = await getRegistryBackend();
166
166
  const encodedDir = encodeDir(targetPath);
167
167
  if (await registryBackend.getProjectBySlug(slug) ?? await registryBackend.getProjectByRootPath(targetPath) ?? await registryBackend.getProjectByEncodedDir(encodedDir)) {
@@ -2035,7 +2035,7 @@ async function cmdUnregister(slug, opts = {}) {
2035
2035
  //#region src/cli/commands/project/projects-index.ts
2036
2036
  function registerProjectsCommands(projectsCmd) {
2037
2037
  projectsCmd.command("list", { isDefault: true }).description("Show the unified deduped listing (same as `pai`).\nUse --all to include cold / zero-session / archived projects.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max rows to show", "20").action(async (opts) => {
2038
- const { cmdMain } = await import("./main-resolver-Df65bPAi.mjs");
2038
+ const { cmdMain } = await import("./main-resolver-CCojcYr2.mjs");
2039
2039
  await cmdMain(void 0, void 0, opts);
2040
2040
  });
2041
2041
  projectsCmd.command("list-raw").description("Raw registry listing with slug, path, status, last_active columns").option("--all", "Include archived projects (default: active only)").option("--status <status>", "Filter by status: active | archived").option("--tag <tag>", "Filter by tag").option("--type <type>", "Filter by type").action(async (opts) => {
@@ -3247,8 +3247,8 @@ function registerStatsCommands(memoryCmd) {
3247
3247
  //#region src/cli/commands/memory/sources-cmd.ts
3248
3248
  function registerSourcesCommand(memoryCmd) {
3249
3249
  memoryCmd.command("sources").description("Show what the indexer has taken in: composition by source, which roots\ncontent enters from, the heaviest single files, and how many chunks are\nrewritten per day (which distinguishes a finite backlog from a treadmill).").option("--limit <n>", "Rows per section (default 8)", "8").action(async (opts) => {
3250
- const { createStorageBackend } = await import("./factory-C9wGUe30.mjs");
3251
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
3250
+ const { createStorageBackend } = await import("./factory-D6I2jmjQ.mjs");
3251
+ const { loadConfig } = await import("./config-GceCMUxa.mjs");
3252
3252
  const { cmdMemorySources } = await import("./sources-BBe3AZ-W.mjs");
3253
3253
  let backend;
3254
3254
  try {
@@ -3949,8 +3949,8 @@ async function escalate(task, headline, detail, state, now, opts) {
3949
3949
  state.alarmedAt[task.id] = now;
3950
3950
  let delivered = false;
3951
3951
  try {
3952
- const { routeNotification } = await import("./router-BXOf_e9e.mjs");
3953
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
3952
+ const { routeNotification } = await import("./router-C5hPk9YP.mjs");
3953
+ const { loadConfig } = await import("./config-GceCMUxa.mjs");
3954
3954
  delivered = (await routeNotification({
3955
3955
  event: "error",
3956
3956
  title: `PAI: ${headline}`,
@@ -3963,6 +3963,35 @@ async function escalate(task, headline, detail, state, now, opts) {
3963
3963
  } catch {}
3964
3964
  }
3965
3965
  /**
3966
+ * One best-effort PAILot alert at the moment a task transitions into parked.
3967
+ *
3968
+ * Parking is silent from the phone's point of view: the tracker note and the
3969
+ * scheduler log both say PARKED, but the only person who reads either is the
3970
+ * one who already knows. On 2026-09-27 both morning sweeps were parked on a
3971
+ * FALSE unreachable (the hub misread a statusline) and nothing surfaced it
3972
+ * until the day's run simply did not happen — the exact failure parking's own
3973
+ * docs promise to prevent.
3974
+ *
3975
+ * Deliberately NOT rate-limited beyond its call sites: it runs exactly once
3976
+ * per parking event (on the transition, not on every poll while parked), and
3977
+ * a re-park after a release is a new event that deserves a new alert. The
3978
+ * escalate() quiet window is untouched — this is the announcement, that is
3979
+ * the daily reminder.
3980
+ *
3981
+ * Never allowed to throw or linger: the hub gets a short deadline, and an
3982
+ * unreachable hub falls back to the poller log (launchd sends both streams to
3983
+ * /tmp/pai-scheduler.log, which already carries every PARKED line).
3984
+ */
3985
+ async function alertParked(task, reason) {
3986
+ const text = `PAI parked "${task.title}" (${task.id}) — the scheduler will not retry it.\nReason: ${reason}\nTo release it: fix the cause, then move the due date — due_string, natural language only (e.g. "tomorrow 9am").`;
3987
+ try {
3988
+ const { callAiBroker } = await import("./aibroker-client-BYG-GtAg.mjs");
3989
+ await callAiBroker("pailot_send", { text }, 8e3);
3990
+ } catch (e) {
3991
+ console.error(`[pai-poller] parked-task alert not delivered to PAILot (${e instanceof Error ? e.message : String(e)}): ${text}`);
3992
+ }
3993
+ }
3994
+ /**
3966
3995
  * Run state is a rebuildable cache, so a damaged file must not block the
3967
3996
  * scheduler forever — starting fresh is the correct recovery here, which is
3968
3997
  * exactly the case json-store's guard is NOT for.
@@ -4242,6 +4271,7 @@ async function handleDispatch(task, overdue, opts, state, now) {
4242
4271
  at: now,
4243
4272
  due: task.due ?? void 0
4244
4273
  };
4274
+ await alertParked(task, reason);
4245
4275
  await escalate(task, "task is not running", `cannot be dispatched: its project directory ${task.owner.rootPath} does not exist (renamed or moved?). It is now PARKED and will not be retried. Repoint the project with \`pai project\` and move the task's due date to release it.`, state, now, opts);
4246
4276
  return {
4247
4277
  note: `PARKED — ${reason}`,
@@ -4280,6 +4310,7 @@ async function handleDispatch(task, overdue, opts, state, now) {
4280
4310
  at: now,
4281
4311
  due: task.due ?? void 0
4282
4312
  };
4313
+ await alertParked(task, detail);
4283
4314
  await escalate(task, "task is not running", `failed to dispatch ${permanent ? "" : `${fails} times `}(${detail}). It is scheduled and nothing is executing it. It is now PARKED and will not be retried until its due date moves — reschedule it once the cause is fixed.`, state, now, opts);
4284
4315
  return {
4285
4316
  note: `PARKED — ${permanent ? "permanent failure" : `${fails} failed dispatches`}: ${detail}`,
@@ -4444,7 +4475,28 @@ async function keepTriggeredSchedule(task, opts, state, now) {
4444
4475
  return `, could NOT keep the schedule: ${e instanceof Error ? e.message : String(e)}`;
4445
4476
  }
4446
4477
  }
4447
- async function handleProbe(task, elapsed, opts, state) {
4478
+ /** Re-dispatch attempts allowed for one claim before NOT-RECEIVED gives up. */
4479
+ const NOT_RECEIVED_MAX_ATTEMPTS = 3;
4480
+ /** Minutes of backoff per attempt: try 1 waits 2m, try 2 waits 4m, try 3 waits 6m. */
4481
+ const NOT_RECEIVED_BACKOFF_BASE_MIN = 2;
4482
+ /**
4483
+ * Does a probe reply say "alive, but the dispatch never reached me"?
4484
+ *
4485
+ * The session answers the liveness probe with NOT-RECEIVED (or the "alive, not
4486
+ * received" spelling) when it is demonstrably live yet has no knowledge of the
4487
+ * work order — the dispatch was reported delivered and was not. That is not
4488
+ * silence (the session is fine) and not death (nothing needs recovering): it is
4489
+ * an UNDELIVERED dispatch, and the honest response is to send it again.
4490
+ *
4491
+ * Anchored to the first line so a reply merely discussing the phrase —
4492
+ * "we discussed not received earlier" — does not trip it.
4493
+ */
4494
+ function isNotReceivedReply(reply) {
4495
+ if (!reply) return false;
4496
+ const first = reply.split("\n", 1)[0].trim().toLowerCase();
4497
+ return first.startsWith("not-received") || /^alive[,:]\s*not[ -]received/.test(first);
4498
+ }
4499
+ async function handleProbe(task, elapsed, opts, state, now) {
4448
4500
  const project = task.owner.project;
4449
4501
  const el = elapsed === null ? "unknown" : `${elapsed}m`;
4450
4502
  if (opts.dryRun) return {
@@ -4458,6 +4510,31 @@ async function handleProbe(task, elapsed, opts, state) {
4458
4510
  const answer = await opts.prober.ask(project, `Are you still working on the task "${task.title}"? Reply in one short line.`);
4459
4511
  if (answer.state === "replied" || answer.state === "busy") {
4460
4512
  state.failedProbes[task.id] = 0;
4513
+ if (isNotReceivedReply(answer.reply)) {
4514
+ const attempts = state.notReceivedAttempts[task.id] ?? 0;
4515
+ const retryAt = state.notReceivedRetryAt[task.id] ?? 0;
4516
+ if (now < retryAt) return {
4517
+ note: `alive but NOT-RECEIVED — re-dispatch backed off ${Math.round((retryAt - now) / 6e4)}m (try ${attempts})`,
4518
+ stuck: false
4519
+ };
4520
+ if (attempts >= NOT_RECEIVED_MAX_ATTEMPTS) return {
4521
+ note: `alive but NOT-RECEIVED x${attempts} — dispatch never landed, claim left for a human`,
4522
+ stuck: true
4523
+ };
4524
+ const next = attempts + 1;
4525
+ state.notReceivedAttempts[task.id] = next;
4526
+ state.notReceivedRetryAt[task.id] = now + next * NOT_RECEIVED_BACKOFF_BASE_MIN * 6e4;
4527
+ const r = await handleDispatch(task, 0, opts, state, now);
4528
+ return {
4529
+ note: `alive but NOT-RECEIVED — re-dispatched (try ${next}): ${r.note}`,
4530
+ stuck: r.alarm === true,
4531
+ dispatched: true
4532
+ };
4533
+ }
4534
+ if (answer.state === "replied") {
4535
+ delete state.notReceivedAttempts[task.id];
4536
+ delete state.notReceivedRetryAt[task.id];
4537
+ }
4461
4538
  const expected = expectedMinutes(state.history[task.id] ?? []);
4462
4539
  if (elapsed !== null && elapsed > expected * GROSS_OVERRUN_FACTOR) return {
4463
4540
  note: `alive but ${el} against an expected ${expected}m — ${GROSS_OVERRUN_FACTOR}x over, worth a look`,
@@ -4649,8 +4726,8 @@ function registerBackupCommands(program) {
4649
4726
  if (existsSync(CONFIG_FILE$1)) {
4650
4727
  const dest = join(backupDir, "config.json");
4651
4728
  try {
4652
- const { copyFileSync } = await import("node:fs");
4653
4729
  copyFileSync(CONFIG_FILE$1, dest);
4730
+ chmodSync(dest, 384);
4654
4731
  results.push({
4655
4732
  label: "Config",
4656
4733
  path: dest,
@@ -6482,6 +6559,40 @@ function registerMcpCommands(mcpCmd) {
6482
6559
  });
6483
6560
  }
6484
6561
 
6562
+ //#endregion
6563
+ //#region src/module-paths.ts
6564
+ /**
6565
+ * module-paths.ts — locate a file relative to the package root from a running module.
6566
+ *
6567
+ * tsdown bundles shared code into chunks emitted directly under dist/ (e.g.
6568
+ * dist/program-*.mjs) rather than always alongside the entry that needs them
6569
+ * (dist/cli/index.mjs, dist/daemon/index.mjs, ...). A fixed "../daemon/index.mjs"
6570
+ * relative to import.meta.url assumes one specific chunk depth and silently
6571
+ * resolves to the wrong file — or a file under the source tree instead of
6572
+ * dist — whenever the bundler's chunking changes. Walking up from the running
6573
+ * module's own directory until `dir/rel` exists is depth-independent, and works
6574
+ * whether `rel` lives under dist/ (built entries) or is a sibling of dist/ at
6575
+ * the package root (files excluded from the bundle, like docker/migrate-sqlite.ts).
6576
+ */
6577
+ /**
6578
+ * Resolve `rel` by walking up from the directory containing `moduleUrl`,
6579
+ * returning the first `dir/rel` that exists on disk.
6580
+ *
6581
+ * Throws rather than returning a guessed path, so a caller writing a service
6582
+ * file or spawning a process fails loudly instead of pointing at nothing.
6583
+ */
6584
+ function resolveFromModule(moduleUrl, rel, maxLevels = 8) {
6585
+ let dir = dirname(fileURLToPath(moduleUrl));
6586
+ for (let i = 0; i < maxLevels; i++) {
6587
+ const candidate = join(dir, rel);
6588
+ if (existsSync(candidate)) return candidate;
6589
+ const parent = dirname(dir);
6590
+ if (parent === dir) break;
6591
+ dir = parent;
6592
+ }
6593
+ throw new Error(`cannot locate "${rel}" above ${dirname(fileURLToPath(moduleUrl))} — is the project built? (bun run build)`);
6594
+ }
6595
+
6485
6596
  //#endregion
6486
6597
  //#region src/cli/commands/daemon-status.ts
6487
6598
  /**
@@ -6548,20 +6659,21 @@ const PLIST_LABEL = "com.pai.pai-daemon";
6548
6659
  const LAUNCH_AGENTS_DIR = join(HOME$1, "Library", "LaunchAgents");
6549
6660
  const PLIST_PATH$1 = join(LAUNCH_AGENTS_DIR, `${PLIST_LABEL}.plist`);
6550
6661
  const DAEMON_LOG = daemonLogPath();
6662
+ const SYSTEMD_UNIT_NAME = "pai-daemon.service";
6663
+ const SYSTEMD_USER_DIR = join(HOME$1, ".config", "systemd", "user");
6664
+ const SYSTEMD_UNIT_PATH = join(SYSTEMD_USER_DIR, SYSTEMD_UNIT_NAME);
6551
6665
  /**
6552
6666
  * Resolve the absolute path to the built daemon entry point.
6553
- * tsdown bundles into dist/daemon/index.mjs (or similar).
6554
- * From dist/cli/index.mjs → dist/ → dist/daemon/index.mjs
6667
+ * tsdown may emit this module's own chunk directly under dist/ rather than
6668
+ * under dist/cli/, so a fixed "../daemon/index.mjs" is not reliable — walk up
6669
+ * from this module until dist/daemon/index.mjs is found instead.
6555
6670
  */
6556
6671
  function getDaemonBinPath() {
6557
- return join(dirname(fileURLToPath(import.meta.url)), "../daemon/index.mjs");
6672
+ return resolveFromModule(import.meta.url, "dist/daemon/index.mjs");
6558
6673
  }
6559
- /**
6560
- * Resolve the absolute path to the built MCP shim entry point.
6561
- * dist/cli/index.mjs → dist/ → dist/daemon-mcp/index.mjs
6562
- */
6674
+ /** Resolve the absolute path to the built MCP shim entry point. Same reasoning as getDaemonBinPath. */
6563
6675
  function getShimBinPath() {
6564
- return join(dirname(fileURLToPath(import.meta.url)), "../daemon-mcp/index.mjs");
6676
+ return resolveFromModule(import.meta.url, "dist/daemon-mcp/index.mjs");
6565
6677
  }
6566
6678
  function generatePlist(daemonBin) {
6567
6679
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -6602,6 +6714,33 @@ function generatePlist(daemonBin) {
6602
6714
  </plist>
6603
6715
  `;
6604
6716
  }
6717
+ function generateSystemdUnit(daemonBin, nodeBin) {
6718
+ return `[Unit]
6719
+ Description=PAI Daemon
6720
+ After=network.target
6721
+
6722
+ [Service]
6723
+ ExecStart=${nodeBin} ${daemonBin} serve
6724
+ Restart=on-failure
6725
+
6726
+ [Install]
6727
+ WantedBy=default.target
6728
+ `;
6729
+ }
6730
+ /** Warns (does not fail) when the user's systemd instance is not kept alive after logout. */
6731
+ function warnIfLingerDisabled() {
6732
+ const username = userInfo().username;
6733
+ const result = spawnSync("loginctl", [
6734
+ "show-user",
6735
+ username,
6736
+ "-p",
6737
+ "Linger"
6738
+ ], { encoding: "utf8" });
6739
+ if (result.status === 0 && result.stdout.trim() !== "Linger=yes") {
6740
+ console.log(warn$1(` Linger is off for ${username} — the daemon stops when you log out.`));
6741
+ console.log(dim$1(` Enable it: loginctl enable-linger ${username}`));
6742
+ }
6743
+ }
6605
6744
  async function cmdStatus$1() {
6606
6745
  const client = new PaiClient(loadConfig().socketPath);
6607
6746
  try {
@@ -6641,6 +6780,24 @@ async function cmdStatus$1() {
6641
6780
  }
6642
6781
  }
6643
6782
  function cmdRestart() {
6783
+ if (process.platform === "linux") {
6784
+ const result = spawnSync("systemctl", [
6785
+ "--user",
6786
+ "restart",
6787
+ SYSTEMD_UNIT_NAME
6788
+ ], { encoding: "utf8" });
6789
+ if (result.status === 0) console.log(ok$1("Restarted via systemd."));
6790
+ else {
6791
+ console.error(err(`systemctl restart failed: ${(result.stderr || "").trim()}`));
6792
+ process.exitCode = 1;
6793
+ }
6794
+ return;
6795
+ }
6796
+ if (process.platform !== "darwin") {
6797
+ console.error(err(`Unsupported platform: ${process.platform}. Daemon restart supports macOS (launchd) and Linux (systemd user) only.`));
6798
+ process.exitCode = 1;
6799
+ return;
6800
+ }
6644
6801
  try {
6645
6802
  const result = spawnSync("pgrep", ["-f", "pai-daemon.*serve"], { encoding: "utf8" });
6646
6803
  if (result.status !== 0 || !result.stdout.trim()) {
@@ -6667,12 +6824,7 @@ function cmdRestart() {
6667
6824
  process.exitCode = 1;
6668
6825
  }
6669
6826
  }
6670
- function cmdInstall() {
6671
- const daemonBin = getDaemonBinPath();
6672
- const shimBin = getShimBinPath();
6673
- console.log();
6674
- console.log(bold$1(" PAI Daemon Install"));
6675
- console.log();
6827
+ function installServiceDarwin(daemonBin) {
6676
6828
  if (!existsSync(LAUNCH_AGENTS_DIR)) mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
6677
6829
  const plistContent = generatePlist(daemonBin);
6678
6830
  try {
@@ -6681,7 +6833,7 @@ function cmdInstall() {
6681
6833
  } catch (e) {
6682
6834
  console.error(err(` Failed to write plist: ${e}`));
6683
6835
  process.exitCode = 1;
6684
- return;
6836
+ return false;
6685
6837
  }
6686
6838
  try {
6687
6839
  spawnSync("launchctl", ["unload", PLIST_PATH$1], { encoding: "utf8" });
@@ -6692,6 +6844,46 @@ function cmdInstall() {
6692
6844
  console.log(warn$1(" Could not run launchctl. Load manually:"));
6693
6845
  console.log(dim$1(` launchctl load ${PLIST_PATH$1}`));
6694
6846
  }
6847
+ return true;
6848
+ }
6849
+ function installServiceLinux(daemonBin) {
6850
+ if (!existsSync(SYSTEMD_USER_DIR)) mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
6851
+ const unitContent = generateSystemdUnit(daemonBin, process.execPath);
6852
+ try {
6853
+ writeFileSync(SYSTEMD_UNIT_PATH, unitContent, "utf8");
6854
+ console.log(ok$1(` Wrote systemd unit: ${SYSTEMD_UNIT_PATH}`));
6855
+ } catch (e) {
6856
+ console.error(err(` Failed to write systemd unit: ${e}`));
6857
+ process.exitCode = 1;
6858
+ return false;
6859
+ }
6860
+ spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
6861
+ const enableResult = spawnSync("systemctl", [
6862
+ "--user",
6863
+ "enable",
6864
+ "--now",
6865
+ SYSTEMD_UNIT_NAME
6866
+ ], { encoding: "utf8" });
6867
+ if (enableResult.status === 0) console.log(ok$1(" Enabled and started via systemd."));
6868
+ else console.log(warn$1(` systemctl enable --now: ${(enableResult.stderr || "").trim()}`));
6869
+ warnIfLingerDisabled();
6870
+ return true;
6871
+ }
6872
+ function cmdInstall() {
6873
+ const daemonBin = getDaemonBinPath();
6874
+ const shimBin = getShimBinPath();
6875
+ console.log();
6876
+ console.log(bold$1(" PAI Daemon Install"));
6877
+ console.log();
6878
+ if (process.platform === "darwin") {
6879
+ if (!installServiceDarwin(daemonBin)) return;
6880
+ } else if (process.platform === "linux") {
6881
+ if (!installServiceLinux(daemonBin)) return;
6882
+ } else {
6883
+ console.error(err(` Unsupported platform: ${process.platform}. Daemon install supports macOS (launchd) and Linux (systemd user) only.`));
6884
+ process.exitCode = 1;
6885
+ return;
6886
+ }
6695
6887
  const config = readClaudeJson();
6696
6888
  if (typeof config.mcpServers !== "object" || config.mcpServers === null) config.mcpServers = {};
6697
6889
  const servers = config.mcpServers;
@@ -6728,10 +6920,7 @@ function cmdInstall() {
6728
6920
  console.log(dim$1(" Restart Claude Code to activate the daemon-backed PAI tools."));
6729
6921
  console.log();
6730
6922
  }
6731
- function cmdUninstall() {
6732
- console.log();
6733
- console.log(bold$1(" PAI Daemon Uninstall"));
6734
- console.log();
6923
+ function uninstallServiceDarwin() {
6735
6924
  if (existsSync(PLIST_PATH$1)) {
6736
6925
  try {
6737
6926
  spawnSync("launchctl", ["unload", PLIST_PATH$1], { encoding: "utf8" });
@@ -6746,6 +6935,36 @@ function cmdUninstall() {
6746
6935
  console.log(warn$1(` Could not remove plist: ${e}`));
6747
6936
  }
6748
6937
  } else console.log(dim$1(" No launchd plist found."));
6938
+ }
6939
+ function uninstallServiceLinux() {
6940
+ if (existsSync(SYSTEMD_UNIT_PATH)) {
6941
+ spawnSync("systemctl", [
6942
+ "--user",
6943
+ "disable",
6944
+ "--now",
6945
+ SYSTEMD_UNIT_NAME
6946
+ ], { encoding: "utf8" });
6947
+ console.log(ok$1(" Disabled and stopped via systemd."));
6948
+ try {
6949
+ unlinkSync(SYSTEMD_UNIT_PATH);
6950
+ console.log(ok$1(` Removed unit: ${SYSTEMD_UNIT_PATH}`));
6951
+ } catch (e) {
6952
+ console.log(warn$1(` Could not remove unit: ${e}`));
6953
+ }
6954
+ spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
6955
+ } else console.log(dim$1(" No systemd unit found."));
6956
+ }
6957
+ function cmdUninstall() {
6958
+ console.log();
6959
+ console.log(bold$1(" PAI Daemon Uninstall"));
6960
+ console.log();
6961
+ if (process.platform === "darwin") uninstallServiceDarwin();
6962
+ else if (process.platform === "linux") uninstallServiceLinux();
6963
+ else {
6964
+ console.error(err(` Unsupported platform: ${process.platform}. Daemon uninstall supports macOS (launchd) and Linux (systemd user) only.`));
6965
+ process.exitCode = 1;
6966
+ return;
6967
+ }
6749
6968
  const config = readClaudeJson();
6750
6969
  const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
6751
6970
  const legacy = servers["pai-legacy"];
@@ -6769,9 +6988,7 @@ async function cmdMigrate(connectionString) {
6769
6988
  console.log();
6770
6989
  console.log(dim$1(" Running SQLite → PostgreSQL migration..."));
6771
6990
  console.log();
6772
- const { fileURLToPath } = await import("node:url");
6773
- const { dirname: pathDirname, join: pathJoin } = await import("node:path");
6774
- const migrationScript = pathJoin(pathDirname(fileURLToPath(import.meta.url)), "../..", "docker", "migrate-sqlite.ts");
6991
+ const migrationScript = resolveFromModule(import.meta.url, "docker/migrate-sqlite.ts");
6775
6992
  const { spawnSync: spawn } = await import("node:child_process");
6776
6993
  const spawnArgs = ["tsx", migrationScript];
6777
6994
  if (connectionString) spawnArgs.push("--connection-string", connectionString);
@@ -6833,8 +7050,8 @@ function cmdLogs(opts) {
6833
7050
  }
6834
7051
  function registerDaemonCommands(daemonCmd) {
6835
7052
  daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
6836
- const { serve } = await import("./daemon-B6hx1Ysz.mjs");
6837
- const { loadConfig: lc, ensureConfigDir } = await import("./config-DCYWFaEi.mjs");
7053
+ const { serve } = await import("./daemon-BppwJtrH.mjs");
7054
+ const { loadConfig: lc, ensureConfigDir } = await import("./config-GceCMUxa.mjs");
6838
7055
  ensureConfigDir();
6839
7056
  await serve(lc());
6840
7057
  });
@@ -6844,13 +7061,13 @@ function registerDaemonCommands(daemonCmd) {
6844
7061
  daemonCmd.command("status").description("Query the running daemon status").action(async () => {
6845
7062
  await cmdStatus$1();
6846
7063
  });
6847
- daemonCmd.command("restart").description("Send SIGTERM to the running daemon (launchd will restart it)").action(() => {
7064
+ daemonCmd.command("restart").description("Restart the running daemon (launchd on macOS, systemd on Linux)").action(() => {
6848
7065
  cmdRestart();
6849
7066
  });
6850
- daemonCmd.command("install").description("Install daemon as a launchd service and update ~/.claude.json to use the shim").action(() => {
7067
+ daemonCmd.command("install").description("Install daemon as a service (launchd/systemd) and update ~/.claude.json to use the shim").action(() => {
6851
7068
  cmdInstall();
6852
7069
  });
6853
- daemonCmd.command("uninstall").description("Remove the launchd service and revert to direct MCP").action(() => {
7070
+ daemonCmd.command("uninstall").description("Remove the daemon service (launchd/systemd) and revert to direct MCP").action(() => {
6854
7071
  cmdUninstall();
6855
7072
  });
6856
7073
  daemonCmd.command("keepalive").description("Show interactive-session cache keepalive config, per-session beat counters, and recent ledger lines").action(() => {
@@ -6993,6 +7210,7 @@ function registerRestoreCommands(program) {
6993
7210
  if (hasConfig) try {
6994
7211
  mkdirSync(dirname(CONFIG_FILE), { recursive: true });
6995
7212
  copyFileSync(join(resolvedDir, "config.json"), CONFIG_FILE);
7213
+ chmodSync(CONFIG_FILE, 384);
6996
7214
  results.push({
6997
7215
  label: "Config",
6998
7216
  status: ok$1("restored")
@@ -14301,6 +14519,12 @@ function generateSchedulePlist(intervalSecs, cli) {
14301
14519
  `;
14302
14520
  }
14303
14521
  function installSchedule(intervalSecs = DEFAULT_INTERVAL_SECS) {
14522
+ if (process.platform !== "darwin") return {
14523
+ plistPath: SCHEDULE_PLIST,
14524
+ intervalSecs,
14525
+ loaded: false,
14526
+ message: "scheduler install supports launchd (macOS) only"
14527
+ };
14304
14528
  if (!existsSync(LAUNCH_AGENTS)) mkdirSync(LAUNCH_AGENTS, { recursive: true });
14305
14529
  writeFileSync(SCHEDULE_PLIST, generateSchedulePlist(intervalSecs, cliPath()), "utf8");
14306
14530
  spawnSync("launchctl", ["unload", SCHEDULE_PLIST], { encoding: "utf8" });
@@ -16389,7 +16613,7 @@ async function getSessionTags(registryBackend, sessionId) {
16389
16613
  * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.
16390
16614
  */
16391
16615
  async function cmdList(projectSlug, opts) {
16392
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16616
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16393
16617
  const registryBackend = await getRegistryBackend();
16394
16618
  const limit = parseInt(opts.limit ?? "20", 10);
16395
16619
  let project;
@@ -16454,7 +16678,7 @@ async function cmdList(projectSlug, opts) {
16454
16678
  console.log(dim$1(` ${rows.length} session(s) shown (limit: ${limit})`));
16455
16679
  }
16456
16680
  async function cmdInfo(projectSlug, sessionNumber) {
16457
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16681
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16458
16682
  const registryBackend = await getRegistryBackend();
16459
16683
  const project = await getProject$1(registryBackend, projectSlug);
16460
16684
  if (!project) {
@@ -16478,7 +16702,7 @@ async function cmdInfo(projectSlug, sessionNumber) {
16478
16702
  console.log();
16479
16703
  }
16480
16704
  async function cmdRename(projectSlug, numberOrLatest, newSlug) {
16481
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16705
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16482
16706
  await cmdRenameWith(await getRegistryBackend(), projectSlug, numberOrLatest, newSlug);
16483
16707
  }
16484
16708
  async function cmdRenameWith(registryBackend, projectSlug, numberOrLatest, newSlug) {
@@ -16541,7 +16765,7 @@ async function cmdRenameWith(registryBackend, projectSlug, numberOrLatest, newSl
16541
16765
  console.log();
16542
16766
  }
16543
16767
  async function cmdSlug(projectSlug, numberOrLatest, opts) {
16544
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16768
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16545
16769
  const registryBackend = await getRegistryBackend();
16546
16770
  const project = await getProject$1(registryBackend, projectSlug);
16547
16771
  if (!project) {
@@ -16571,7 +16795,7 @@ async function cmdSlug(projectSlug, numberOrLatest, opts) {
16571
16795
  }
16572
16796
  }
16573
16797
  async function cmdTag(projectSlug, sessionNumber, rawTags) {
16574
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16798
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16575
16799
  const registryBackend = await getRegistryBackend();
16576
16800
  const project = await getProject$1(registryBackend, projectSlug);
16577
16801
  if (!project) {
@@ -16611,7 +16835,7 @@ async function cmdTag(projectSlug, sessionNumber, rawTags) {
16611
16835
  console.log();
16612
16836
  }
16613
16837
  async function cmdRoute(projectSlug, sessionNumber, targetProjectSlug, opts) {
16614
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16838
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16615
16839
  const registryBackend = await getRegistryBackend();
16616
16840
  const project = await getProject$1(registryBackend, projectSlug);
16617
16841
  if (!project) {
@@ -16653,7 +16877,7 @@ async function cmdRoute(projectSlug, sessionNumber, targetProjectSlug, opts) {
16653
16877
  console.log();
16654
16878
  }
16655
16879
  async function cmdActive(opts) {
16656
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
16880
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16657
16881
  const registryBackend = await getRegistryBackend();
16658
16882
  const minutes = parseInt(opts.minutes ?? "60", 10);
16659
16883
  const cutoff = Date.now() - minutes * 60 * 1e3;
@@ -16741,8 +16965,8 @@ async function cmdActive(opts) {
16741
16965
  }
16742
16966
  async function cmdAutoRoute(opts) {
16743
16967
  const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CF8WNaoy.mjs");
16744
- const { getRegistryBackend, createStorageBackend } = await import("./factory-C9wGUe30.mjs");
16745
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
16968
+ const { getRegistryBackend, createStorageBackend } = await import("./factory-D6I2jmjQ.mjs");
16969
+ const { loadConfig } = await import("./config-GceCMUxa.mjs");
16746
16970
  const config = loadConfig();
16747
16971
  const registryBackend = await getRegistryBackend();
16748
16972
  const federation = await createStorageBackend(config);
@@ -16978,7 +17202,7 @@ async function cmdPause(opts) {
16978
17202
  process.exitCode = 1;
16979
17203
  return;
16980
17204
  }
16981
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
17205
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
16982
17206
  const registryBackend = await getRegistryBackend();
16983
17207
  const cwd = process.cwd();
16984
17208
  const project = await resolveProjectByCwd(registryBackend, cwd);
@@ -17373,7 +17597,7 @@ function buildStopBody(project, sessionId) {
17373
17597
  }
17374
17598
  }
17375
17599
  async function cmdHandover(projectSlug, numberOrLatest, sessionId) {
17376
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
17600
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
17377
17601
  const registryBackend = await getRegistryBackend();
17378
17602
  let project;
17379
17603
  if (projectSlug) {
@@ -17669,7 +17893,7 @@ function touchSentinel(rootPath) {
17669
17893
  async function cmdAutosave(opts) {
17670
17894
  const minGap = parseInt(opts.minGap ?? String(DEFAULT_MIN_GAP), 10);
17671
17895
  const cwd = process.cwd();
17672
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
17896
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
17673
17897
  const registryBackend = await getRegistryBackend();
17674
17898
  const project = await resolveProjectByCwd(registryBackend, cwd);
17675
17899
  if (!project) return;
@@ -17858,23 +18082,24 @@ async function cmdRecent(opts) {
17858
18082
  * (see probeResume in lib/launch.ts — the filesystem answers,
17859
18083
  * and only an unrecognised layout falls back to spawning claude)
17860
18084
  * b. If probe succeeds:
17861
- * exec claude --resume <uuid> --name "<friendlyName>" "/Name <friendlyName>\ngo"
18085
+ * exec claude --resume <uuid> --name "<friendlyName>" "/Name <friendlyName>"
17862
18086
  * — through the provider the transcript ran on, when its model
17863
18087
  * matches one (providerResumePlan in lib/launch.ts)
17864
18088
  * c. Else (probe failed):
17865
18089
  * print clear stderr line: "Resume failed. Starting fresh session in same dir."
17866
- * exec claude --name "<friendlyName>" "/Name <friendlyName>\ngo"
18090
+ * exec claude --name "<friendlyName>" "/Name <friendlyName>"
17867
18091
  * 4. Else (no uuid known):
17868
- * exec claude --name "<friendlyName>" "/Name <friendlyName>\ngo"
18092
+ * exec claude --name "<friendlyName>" "/Name <friendlyName>"
17869
18093
  *
17870
18094
  * Steps 3 and 4 are launchInDir's, shared — this file no longer carries its
17871
18095
  * own copy of the spawn dance.
17872
18096
  *
17873
18097
  * Why both --name AND /Name?
17874
18098
  * --name <friendlyName> → sets Claude Code's internal session label
17875
- * "/Name <friendlyName>\ngo" → runs the /Name slash command via AIBroker, which updates
17876
- * iTerm tab title, statusline, and AIBroker session registry;
17877
- * the \ngo on the next line triggers PAI's ## Continue resume
18099
+ * "/Name <friendlyName>" → runs the /Name slash command via AIBroker, which updates
18100
+ * iTerm tab title, statusline, and AIBroker session registry.
18101
+ * No trailing `go`: the operator types it by hand when the
18102
+ * TODO.md handover is wanted
17878
18103
  *
17879
18104
  * The probe is two `existsSync` calls on the happy path. On the fallback path, total latency
17880
18105
  * is the same as a fresh start.
@@ -17922,7 +18147,7 @@ async function cmdGoto(query, opts) {
17922
18147
  if (resumableUuid) {
17923
18148
  const plan = providerResumePlan(resumableUuid, projectDir);
17924
18149
  const argvResume = resumeArgvText(resumableUuid, name, plan);
17925
- const argvFresh = `claude --name "${name}" "/Name ${name}\\ngo"`;
18150
+ const argvFresh = `claude --name "${name}" "${launchPrompt(name)}"`;
17926
18151
  console.log("\n" + chalk.bold("Dry run — would probe then exec (RESUME path):") + "\n");
17927
18152
  console.log(` cwd: ${chalk.cyan(projectDir)}`);
17928
18153
  console.log(` probe: claude --resume ${resumableUuid} --print --output-format=json "_"`);
@@ -17936,7 +18161,7 @@ async function cmdGoto(query, opts) {
17936
18161
  console.log(` sys: ${resumableSession.topLevelSystemLines} system lines`);
17937
18162
  }
17938
18163
  } else {
17939
- const argvFresh = `claude --name "${name}" "/Name ${name}\\ngo"`;
18164
+ const argvFresh = `claude --name "${name}" "${launchPrompt(name)}"`;
17940
18165
  console.log("\n" + chalk.bold("Dry run — would exec (FRESH path, no resumable UUID):") + "\n");
17941
18166
  console.log(` cwd: ${chalk.cyan(projectDir)}`);
17942
18167
  console.log(` argv: ${chalk.white(argvFresh)}`);
@@ -18204,7 +18429,7 @@ function registerSessionCommands(sessionCmd) {
18204
18429
  process.stderr.write("Deprecated: `pai session recent` — use `pai session list` or `pai sessions` instead.\n");
18205
18430
  await cmdRecent(opts);
18206
18431
  });
18207
- sessionCmd.command("goto <name-or-id>").description("Go to a session: resume if a resumable snapshot exists, start fresh otherwise.\nRecommended short form: pai resume <name>\nResolves by clc/registry name (case-insensitive) or UUID prefix.\nSends '/Name <name>\\ngo' as the initial prompt so the PAI ## Continue hook fires.").option("--skip-name", "Do not prepend /Name to set the session name in chrome").option("--skip-go", "Do not append \\ngo to trigger PAI auto-resume (## Continue)").option("--dry-run", "Print the exact argv and cwd, then exit without launching").action(async (nameOrId, opts) => {
18432
+ sessionCmd.command("goto <name-or-id>").description("Go to a session: resume if a resumable snapshot exists, start fresh otherwise.\nRecommended short form: pai resume <name>\nResolves by clc/registry name (case-insensitive) or UUID prefix.\nSends '/Name <name>' as the initial prompt to label the session.").option("--skip-name", "Do not prepend /Name to set the session name in chrome").option("--skip-go", "Deprecated no-op: 'go' is no longer appended — type it by hand when wanted").option("--dry-run", "Print the exact argv and cwd, then exit without launching").action(async (nameOrId, opts) => {
18208
18433
  process.stderr.write("Note: `pai session goto` works but `pai resume` is the new shorter form.\n");
18209
18434
  await cmdGoto(nameOrId, {
18210
18435
  noName: opts.skipName,
@@ -18489,10 +18714,10 @@ function registerWorkerProviderCommands(providersCmd) {
18489
18714
  fail$4(e);
18490
18715
  }
18491
18716
  });
18492
- providersCmd.command("use <name>").description("Make this provider the active one for runs without --provider/--class").action((name) => {
18717
+ providersCmd.command("use <name>").description("Make this provider the active one, re-pointing every class pinned to a provider").action((name) => {
18493
18718
  try {
18494
- useProvider(name);
18495
- console.log(ok$1(`active provider: ${name}`));
18719
+ const { repointed } = useProvider(name);
18720
+ console.log(ok$1(`active provider: ${name}${repointed ? `; ${repointed} class(es) re-pointed` : ""}`));
18496
18721
  } catch (e) {
18497
18722
  fail$4(e);
18498
18723
  }
@@ -19107,27 +19332,32 @@ function registerWorkerCommands(workerCmd) {
19107
19332
  fail(e);
19108
19333
  }
19109
19334
  });
19110
- workerCmd.command("controls <id> <who>").description("Hand the desktop controls (clickr) to a worker or take them back.\n<who> is `you` (the worker may actuate) or `me` (the operator keeps them);\ninside a worker's pane, typing \"your controls\" does the same.").action((id, who) => {
19335
+ workerCmd.command("controls <id> <who>").description("Hand the desktop controls (clickr) to a worker or take them back.\n<who> is `you` (<id> becomes the exclusive controls holder; needs clickr with\n--agent support) or `me` (the operator takes them back);\ninside a worker's pane, typing \"your controls\" does the same.").action((id, who) => {
19111
19336
  try {
19112
19337
  if (who !== "you" && who !== "me") {
19113
19338
  fail(/* @__PURE__ */ new Error(`<who> must be "you" or "me"`));
19114
19339
  return;
19115
19340
  }
19116
- if (!loadStatus(currentLogDir(), id)) {
19341
+ const logDir = currentLogDir();
19342
+ const status = loadStatus(logDir, id);
19343
+ if (!status) {
19117
19344
  fail(/* @__PURE__ */ new Error(`no worker named "${id}"`));
19118
19345
  return;
19119
19346
  }
19120
- const proc = spawn("clickr", ["controls", who], { stdio: "inherit" });
19347
+ const proc = spawn("clickr", clickrControlsArgv(id, who, status.pid), { stdio: "inherit" });
19121
19348
  proc.on("error", (e) => fail(/* @__PURE__ */ new Error(`cannot run clickr controls: ${e.message} (is clickr installed?)`)));
19122
19349
  proc.on("close", (code) => {
19123
- if (code === 0) console.log(`controls → ${who === "you" ? id : "operator"}`);
19350
+ if (code === 0) {
19351
+ if (who === "you") markControlsHeld(logDir, id);
19352
+ console.log(`controls → ${who === "you" ? `${id} (exclusive; needs clickr with --agent support)` : "operator"}`);
19353
+ }
19124
19354
  process.exitCode = code ?? 1;
19125
19355
  });
19126
19356
  } catch (e) {
19127
19357
  fail(e);
19128
19358
  }
19129
19359
  });
19130
- workerCmd.command("resume <id> <text>").description("Continue a finished worker on the same provider: claude --resume <session>").option("--print-id", "Print the new worker id on its own line (pane follow handoff)").option("--no-pane", "Do not open a follow pane for the resumed worker").action(async (id, text, opts) => {
19360
+ workerCmd.command("resume <id> <text>").description("Continue a finished worker on the same provider: claude --resume <session>").option("--print-id", "Print the new worker id on its own line (pane follow handoff)").option("--no-pane", "Do not open a follow pane for the resumed worker").option("--output-format <fmt>", "text (default), json or stream-json — same as `run`").option("--label <text>", "Short task label (default: \"↩ <old label>\")").action(async (id, text, opts) => {
19131
19361
  try {
19132
19362
  const old = loadStatus(currentLogDir(), id);
19133
19363
  if (!old) {
@@ -19138,18 +19368,20 @@ function registerWorkerCommands(workerCmd) {
19138
19368
  fail(/* @__PURE__ */ new Error(`worker ${id} recorded no Claude session id — it predates resume support or ran through an engine that does not expose one`));
19139
19369
  return;
19140
19370
  }
19371
+ const claudeArgs = [
19372
+ "--resume",
19373
+ old.claudeSession,
19374
+ "-p",
19375
+ text
19376
+ ];
19377
+ if (opts.outputFormat !== void 0) claudeArgs.push("--output-format", opts.outputFormat);
19141
19378
  let newId = "";
19142
19379
  const rc = await runWorker({
19143
19380
  providerFlag: old.provider,
19144
19381
  modelFlag: old.model,
19145
- label: `↩ ${old.label}`,
19382
+ label: opts.label || `↩ ${old.label}`,
19146
19383
  noPane: opts.pane === false,
19147
- claudeArgs: [
19148
- "--resume",
19149
- old.claudeSession,
19150
- "-p",
19151
- text
19152
- ],
19384
+ claudeArgs,
19153
19385
  onWorkerStart: (wid) => {
19154
19386
  newId = wid;
19155
19387
  }
@@ -19937,8 +20169,8 @@ async function analyzeProject(registryBackend, project) {
19937
20169
  async function countVectorDbPaths(oldPaths) {
19938
20170
  if (oldPaths.length === 0) return 0;
19939
20171
  try {
19940
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
19941
- const { createStorageBackend } = await import("./factory-C9wGUe30.mjs");
20172
+ const { loadConfig } = await import("./config-GceCMUxa.mjs");
20173
+ const { createStorageBackend } = await import("./factory-D6I2jmjQ.mjs");
19942
20174
  const config = loadConfig();
19943
20175
  if (config.storageBackend !== "postgres") return 0;
19944
20176
  const backend = await createStorageBackend(config);
@@ -19955,8 +20187,8 @@ async function countVectorDbPaths(oldPaths) {
19955
20187
  async function updateVectorDbPaths(moves) {
19956
20188
  if (moves.length === 0) return 0;
19957
20189
  try {
19958
- const { loadConfig } = await import("./config-DCYWFaEi.mjs");
19959
- const { createStorageBackend } = await import("./factory-C9wGUe30.mjs");
20190
+ const { loadConfig } = await import("./config-GceCMUxa.mjs");
20191
+ const { createStorageBackend } = await import("./factory-D6I2jmjQ.mjs");
19960
20192
  const config = loadConfig();
19961
20193
  if (config.storageBackend !== "postgres") return 0;
19962
20194
  const backend = await createStorageBackend(config);
@@ -20043,7 +20275,7 @@ async function displayDryRun(plans) {
20043
20275
  console.log();
20044
20276
  }
20045
20277
  async function executeCleanup(plans, skipReindex) {
20046
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
20278
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
20047
20279
  const registryBackend = await getRegistryBackend();
20048
20280
  let deleted = 0;
20049
20281
  let renamed = 0;
@@ -20240,7 +20472,7 @@ async function executeCleanup(plans, skipReindex) {
20240
20472
  //#region src/cli/commands/session-cleanup/index.ts
20241
20473
  function registerSessionCleanupCommand(sessionCmd) {
20242
20474
  sessionCmd.command("cleanup [project-slug]").description("Clean up session notes: delete empties, auto-name unnamed, move into YYYY/MM/ hierarchy, renumber").option("--execute", "Actually perform the cleanup (default is dry-run)").option("--no-renumber", "Skip renumbering sessions after deletions").option("--no-reindex", "Skip triggering memory re-index after moves").action(async (projectSlug, opts) => {
20243
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
20475
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
20244
20476
  const registryBackend = await getRegistryBackend();
20245
20477
  const dryRun = !opts.execute;
20246
20478
  const skipReindex = opts.reindex === false;
@@ -20310,7 +20542,7 @@ function finalizeNote(notePath) {
20310
20542
  }
20311
20543
  async function cmdEnd(opts) {
20312
20544
  await cmdPause(opts);
20313
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
20545
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
20314
20546
  const registryBackend = await getRegistryBackend();
20315
20547
  const cwd = process.cwd();
20316
20548
  const project = await registryBackend.findProjectByCwdPrefix(cwd);
@@ -20758,7 +20990,7 @@ async function cmdFind(query, opts) {
20758
20990
  */
20759
20991
  async function getProjects(includeArchived) {
20760
20992
  try {
20761
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
20993
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
20762
20994
  const rows = await (await getRegistryBackend()).listProjectsWithSessionStats();
20763
20995
  return includeArchived ? rows : rows.filter((p) => p.status === "active");
20764
20996
  } catch {
@@ -21232,7 +21464,7 @@ async function cmdPick(opts = {}) {
21232
21464
  const onRemove = async (rec) => {
21233
21465
  if (!rec.slug) return false;
21234
21466
  try {
21235
- const { getRegistryBackend } = await import("./factory-C9wGUe30.mjs");
21467
+ const { getRegistryBackend } = await import("./factory-D6I2jmjQ.mjs");
21236
21468
  const registryBackend = await getRegistryBackend();
21237
21469
  const project = await registryBackend.getProjectBySlug(rec.slug);
21238
21470
  if (!project || project.status !== "active") return false;
@@ -21517,4 +21749,4 @@ claude() {
21517
21749
 
21518
21750
  //#endregion
21519
21751
  export { drainStdio as n, buildProgram as t };
21520
- //# sourceMappingURL=program-D-WkmvuQ.mjs.map
21752
+ //# sourceMappingURL=program-QSuci3RH.mjs.map