@tekmidian/pai 0.60.1 → 0.62.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 (93) hide show
  1. package/dist/{async-CLvuY7b-.mjs → async-DNVWtQl2.mjs} +2 -2
  2. package/dist/{async-CLvuY7b-.mjs.map → async-DNVWtQl2.mjs.map} +1 -1
  3. package/dist/{chain-CGyAIHo4.mjs → chain-CuaDawK9.mjs} +4 -4
  4. package/dist/{chain-CGyAIHo4.mjs.map → chain-CuaDawK9.mjs.map} +1 -1
  5. package/dist/cli/index.mjs +13 -13
  6. package/dist/cli/program.mjs +13 -13
  7. package/dist/{context-handover-cache-BzM7Qudn.mjs → context-handover-cache-DFNnlWv2.mjs} +4 -4
  8. package/dist/{context-handover-cache-BzM7Qudn.mjs.map → context-handover-cache-DFNnlWv2.mjs.map} +1 -1
  9. package/dist/daemon/index.mjs +10 -10
  10. package/dist/{daemon-B_TodsIS.mjs → daemon-Bf7Eock4.mjs} +45 -24
  11. package/dist/{daemon-B_TodsIS.mjs.map → daemon-Bf7Eock4.mjs.map} +1 -1
  12. package/dist/daemon-bJi1gDkG.mjs +19 -0
  13. package/dist/daemon-mcp/index.mjs +8 -10
  14. package/dist/daemon-mcp/index.mjs.map +1 -1
  15. package/dist/{factory-EUUhSSoq.mjs → factory-BozZa81q.mjs} +3 -3
  16. package/dist/{factory-EUUhSSoq.mjs.map → factory-BozZa81q.mjs.map} +1 -1
  17. package/dist/{factory-DW7BCJJT.mjs → factory-UO9_DFMN.mjs} +1 -1
  18. package/dist/{fallback-CjdakQVu.mjs → fallback-F0TWGVM2.mjs} +11 -13
  19. package/dist/{fallback-CjdakQVu.mjs.map → fallback-F0TWGVM2.mjs.map} +1 -1
  20. package/dist/{helpers-D9WkIf9R.mjs → helpers-Dj-B314s.mjs} +3 -2
  21. package/dist/{helpers-D9WkIf9R.mjs.map → helpers-Dj-B314s.mjs.map} +1 -1
  22. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  23. package/dist/hooks/context-compression-hook.mjs.map +1 -1
  24. package/dist/hooks/load-project-context.mjs +8513 -133
  25. package/dist/hooks/load-project-context.mjs.map +4 -4
  26. package/dist/hooks/post-compact-inject.mjs.map +1 -1
  27. package/dist/hooks/route-agents-to-worker.mjs +3 -3
  28. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  29. package/dist/hooks/security-validator.mjs +58 -58
  30. package/dist/hooks/security-validator.mjs.map +3 -3
  31. package/dist/hooks/stop-hook.mjs +15 -15
  32. package/dist/hooks/stop-hook.mjs.map +3 -3
  33. package/dist/hooks/update-tab-on-action.mjs +14 -12
  34. package/dist/hooks/update-tab-on-action.mjs.map +3 -3
  35. package/dist/hooks/update-tab-titles.mjs +13 -9
  36. package/dist/hooks/update-tab-titles.mjs.map +3 -3
  37. package/dist/hooks/worker-guard.mjs +7859 -0
  38. package/dist/hooks/worker-guard.mjs.map +7 -0
  39. package/dist/hooks/worker-proxy.mjs +3 -3
  40. package/dist/hooks/worker-proxy.mjs.map +2 -2
  41. package/dist/hooks/worker-status-line.mjs +104 -11
  42. package/dist/hooks/worker-status-line.mjs.map +4 -4
  43. package/dist/hooks/worker-supervision.mjs +3 -3
  44. package/dist/hooks/worker-supervision.mjs.map +2 -2
  45. package/dist/index.mjs +1 -1
  46. package/dist/{indexer-backend-DcDtin1O.mjs → indexer-backend-BN9rpp9O.mjs} +2 -2
  47. package/dist/{main-resolver-BFQfU5xo.mjs → main-resolver-BIft_FVF.mjs} +4 -4
  48. package/dist/{main-resolver-BFQfU5xo.mjs.map → main-resolver-BIft_FVF.mjs.map} +1 -1
  49. package/dist/{main-resolver-VMBENifT.mjs → main-resolver-C9-poMdi.mjs} +2 -2
  50. package/dist/{planner-DN967Lvm.mjs → planner-BG9ivPj0.mjs} +6 -6
  51. package/dist/{planner-DN967Lvm.mjs.map → planner-BG9ivPj0.mjs.map} +1 -1
  52. package/dist/{postgres-nvU5pShF.mjs → postgres-CxD6_Dca.mjs} +3 -3
  53. package/dist/{postgres-nvU5pShF.mjs.map → postgres-CxD6_Dca.mjs.map} +1 -1
  54. package/dist/{program-BnsjB57Q.mjs → program-BRvCzhjx.mjs} +249 -82
  55. package/dist/program-BRvCzhjx.mjs.map +1 -0
  56. package/dist/{run-FeM1_UVa.mjs → run-BVQZI79D.mjs} +610 -253
  57. package/dist/run-BVQZI79D.mjs.map +1 -0
  58. package/dist/{run-env-CEHTeIUM.mjs → run-env-BE5xyCwy.mjs} +25 -6
  59. package/dist/run-env-BE5xyCwy.mjs.map +1 -0
  60. package/dist/{server-CekGOJcp.mjs → server-BY6AedmC.mjs} +2 -2
  61. package/dist/{server-CekGOJcp.mjs.map → server-BY6AedmC.mjs.map} +1 -1
  62. package/dist/{session-keepalive-Bc9aT678.mjs → session-keepalive-DJA97K2X.mjs} +4 -4
  63. package/dist/{session-keepalive-Bc9aT678.mjs.map → session-keepalive-DJA97K2X.mjs.map} +1 -1
  64. package/dist/{sqlite-D9p5a4qe.mjs → sqlite-CfKfuggn.mjs} +2 -2
  65. package/dist/{sqlite-D9p5a4qe.mjs.map → sqlite-CfKfuggn.mjs.map} +1 -1
  66. package/dist/{tools-r-7ZG8B5.mjs → tools-B52IvaU6.mjs} +3 -3
  67. package/dist/{tools-qXyHFwNj.mjs → tools-DFdPbX-X.mjs} +2 -2
  68. package/dist/{tools-qXyHFwNj.mjs.map → tools-DFdPbX-X.mjs.map} +1 -1
  69. package/dist/{vault-indexer-DHYBpRZ5.mjs → vault-indexer-mCFus_AM.mjs} +2 -2
  70. package/dist/{vault-indexer-DHYBpRZ5.mjs.map → vault-indexer-mCFus_AM.mjs.map} +1 -1
  71. package/dist/{wakeup-C48hcN6O.mjs → wakeup-DO1O4udw.mjs} +2 -2
  72. package/dist/{wakeup-C48hcN6O.mjs.map → wakeup-DO1O4udw.mjs.map} +1 -1
  73. package/dist/{work-queue-worker-BL4dq5P7.mjs → work-queue-worker-BbrC3I_R.mjs} +2 -2
  74. package/dist/{work-queue-worker-BL4dq5P7.mjs.map → work-queue-worker-BbrC3I_R.mjs.map} +1 -1
  75. package/dist/work-queue-worker-z-AATZ2t.mjs +11 -0
  76. package/docs/commands/README.md +2 -0
  77. package/docs/commands/worker.md +28 -0
  78. package/package.json +2 -2
  79. package/scripts/build-hooks.mjs +6 -5
  80. package/scripts/build-skill-stubs.mjs +6 -5
  81. package/src/hooks/ts/lib/tab-title.ts +18 -0
  82. package/src/hooks/ts/lib/worker-guard.test.ts +188 -0
  83. package/src/hooks/ts/lib/worker-guard.ts +315 -0
  84. package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +3 -20
  85. package/src/hooks/ts/pre-tool-use/worker-guard.ts +75 -0
  86. package/src/hooks/ts/session-start/load-project-context.ts +14 -0
  87. package/src/hooks/ts/stop/stop-hook.ts +4 -15
  88. package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -10
  89. package/dist/daemon-jviL3VVJ.mjs +0 -19
  90. package/dist/program-BnsjB57Q.mjs.map +0 -1
  91. package/dist/run-FeM1_UVa.mjs.map +0 -1
  92. package/dist/run-env-CEHTeIUM.mjs.map +0 -1
  93. package/dist/work-queue-worker-DVOVcEnO.mjs +0 -11
@@ -3,27 +3,27 @@ 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-EUUhSSoq.mjs";
7
- import { a as formatDetectionJson, i as formatDetection, n as migrateIdentityFile, r as detectProject } from "./wakeup-C48hcN6O.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-BzM7Qudn.mjs";
6
+ import { a as getStorageBackend, i as getRegistryBackend, r as createStorageBackend, s as humanDuration } from "./factory-BozZa81q.mjs";
7
+ import { a as formatDetectionJson, i as formatDetection, n as migrateIdentityFile, r as detectProject } from "./wakeup-DO1O4udw.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-DFNnlWv2.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
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-CQCmH_gV.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-CEHTeIUM.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-FeM1_UVa.mjs";
12
+ import { B as contextWindowFromModelId, C as nativeAnthropicProvider, E as readWorkersSection, F as parseWorkersYamlDocument, I as relocateWorkersYaml, L as workersYamlLegacyNotice, M as inlineWorkersYamlKeys, N as migrateWorkersToYaml, P as needsWorkersYamlRelocation, R as workersYamlPath, S as maskKey, V as stripModelVariant, b as isModelCapability, f as PROVIDER_TAGS, j as initWorkersYaml, m as WorkersConfigError, r as ANTHROPIC_NATIVE, t as buildRunEnv, u as MODEL_CAPABILITIES, v as expandHome$1, y as getProviderOrNative, z as DEFAULT_CONTEXT_WINDOW } from "./run-env-BE5xyCwy.mjs";
13
+ import { A as appendLedger, B as loadStatus, D as mergeWorker, E as git, G as saveStatus, J as longInlinePromptHint, K as setWorkerLabel, M as UNLABELED, O as uncommittedPaths, Q as expandMcpNames, R as isChatPane, T as gcWorktrees, V as loadStatuses, W as ownsPid, Y as parseRunnerArgs, Z 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 waitForTerminalStatus, r as testProvider, u as sayToWorker, w as discardWorker, z as isLive } from "./run-BVQZI79D.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
- import { i as indexProjectWithBackend } from "./async-CLvuY7b-.mjs";
17
+ import { i as indexProjectWithBackend } from "./async-DNVWtQl2.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-CjdakQVu.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-BFQfU5xo.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-F0TWGVM2.mjs";
20
+ import { _ as resolveSessionByNameOrId, a as buildDeduped, c as hasConversation, d as providerResumePlan, f as restoreTopLevel, g as migrateSessionScanCache, h as fmtAge$1, 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-BIft_FVF.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-CekGOJcp.mjs";
22
+ import { _ as statusPath, 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-BY6AedmC.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-Bc9aT678.mjs";
25
- import { t as runChain } from "./chain-CGyAIHo4.mjs";
26
- import { appendFileSync, chmodSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
24
+ import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-DJA97K2X.mjs";
25
+ import { t as runChain } from "./chain-CuaDawK9.mjs";
26
+ import { appendFileSync, chmodSync, closeSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
27
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";
@@ -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-DW7BCJJT.mjs");
164
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.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-VMBENifT.mjs");
2038
+ const { cmdMain } = await import("./main-resolver-C9-poMdi.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) => {
@@ -2630,13 +2630,13 @@ function migrateFederationDbOrphan(dryRun) {
2630
2630
  if (existsSync(oldPath) && !dryRun) {
2631
2631
  let heldOpen = false;
2632
2632
  try {
2633
- execSync(`lsof "${oldPath}"`, { stdio: "pipe" });
2633
+ execFileSync("lsof", [oldPath], { stdio: "pipe" });
2634
2634
  heldOpen = true;
2635
2635
  } catch {}
2636
2636
  if (heldOpen) {
2637
2637
  console.log(" federation database (orphan): held open by the daemon — restarting it first");
2638
- execSync("pai daemon restart", { stdio: "inherit" });
2639
- execSync("sleep 2");
2638
+ execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
2639
+ execFileSync("sleep", ["2"]);
2640
2640
  }
2641
2641
  }
2642
2642
  return migrateOrphanFederationDb({ dryRun });
@@ -2644,12 +2644,12 @@ function migrateFederationDbOrphan(dryRun) {
2644
2644
  function checkpointAndVerify(oldPath, newPath, label) {
2645
2645
  let hasSqlite3 = true;
2646
2646
  try {
2647
- execSync("command -v sqlite3", { stdio: "pipe" });
2647
+ execFileSync("which", ["sqlite3"], { stdio: "pipe" });
2648
2648
  } catch {
2649
2649
  hasSqlite3 = false;
2650
2650
  }
2651
2651
  if (hasSqlite3) try {
2652
- execSync(`sqlite3 "${oldPath}" "PRAGMA wal_checkpoint(TRUNCATE);"`, { stdio: "pipe" });
2652
+ execFileSync("sqlite3", [oldPath, "PRAGMA wal_checkpoint(TRUNCATE);"], { stdio: "pipe" });
2653
2653
  } catch {}
2654
2654
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
2655
2655
  for (const suffix of ["-wal", "-shm"]) {
@@ -2659,7 +2659,7 @@ function checkpointAndVerify(oldPath, newPath, label) {
2659
2659
  } catch {}
2660
2660
  }
2661
2661
  if (hasSqlite3) try {
2662
- const check = execSync(`sqlite3 "${newPath}" "PRAGMA integrity_check;"`, { stdio: "pipe" }).toString().trim();
2662
+ const check = execFileSync("sqlite3", [newPath, "PRAGMA integrity_check;"], { stdio: "pipe" }).toString().trim();
2663
2663
  if (check !== "ok") throw new PaiFileMigrationError(`${newPath}: PRAGMA integrity_check reported "${check}" — investigate before trusting this copy`);
2664
2664
  } catch (e) {
2665
2665
  if (e instanceof PaiFileMigrationError) throw e;
@@ -2682,13 +2682,13 @@ function migrateRegistryDbFile(dryRun) {
2682
2682
  if (!existsSync(oldPath) || dryRun) return migratePaiFile(newPath, [oldPath], { dryRun });
2683
2683
  let heldOpen = false;
2684
2684
  try {
2685
- execSync(`lsof "${oldPath}"`, { stdio: "pipe" });
2685
+ execFileSync("lsof", [oldPath], { stdio: "pipe" });
2686
2686
  heldOpen = true;
2687
2687
  } catch {}
2688
2688
  if (heldOpen) {
2689
2689
  console.log(" registry database: held open by the daemon — restarting it first");
2690
- execSync("pai daemon restart", { stdio: "inherit" });
2691
- execSync("sleep 2");
2690
+ execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
2691
+ execFileSync("sleep", ["2"]);
2692
2692
  }
2693
2693
  const result = migratePaiFile(newPath, [oldPath], { dryRun: false });
2694
2694
  if (result.fromPath) checkpointAndVerify(oldPath, newPath, "registry database");
@@ -2711,13 +2711,13 @@ function migrateFederationDbFile(dryRun) {
2711
2711
  if (!existsSync(oldPath) || dryRun) return migratePaiFile(newPath, [oldPath], { dryRun });
2712
2712
  let heldOpen = false;
2713
2713
  try {
2714
- execSync(`lsof "${oldPath}"`, { stdio: "pipe" });
2714
+ execFileSync("lsof", [oldPath], { stdio: "pipe" });
2715
2715
  heldOpen = true;
2716
2716
  } catch {}
2717
2717
  if (heldOpen) {
2718
2718
  console.log(" federation database (live): held open by the daemon — restarting it first");
2719
- execSync("pai daemon restart", { stdio: "inherit" });
2720
- execSync("sleep 2");
2719
+ execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
2720
+ execFileSync("sleep", ["2"]);
2721
2721
  }
2722
2722
  const result = migratePaiFile(newPath, [oldPath], { dryRun: false });
2723
2723
  if (result.fromPath) checkpointAndVerify(oldPath, newPath, "federation database");
@@ -3247,7 +3247,7 @@ 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-DW7BCJJT.mjs");
3250
+ const { createStorageBackend } = await import("./factory-UO9_DFMN.mjs");
3251
3251
  const { loadConfig } = await import("./config-DjXq_W42.mjs");
3252
3252
  const { cmdMemorySources } = await import("./sources-BBe3AZ-W.mjs");
3253
3253
  let backend;
@@ -4752,15 +4752,29 @@ function registerBackupCommands(program) {
4752
4752
  const sqlDest = join(backupDir, "postgres-pai.sql");
4753
4753
  console.log(dim$1(` Running pg_dump on ${DOCKER_CONTAINER$1} (this may take a moment)...`));
4754
4754
  try {
4755
- execSync(`docker inspect ${DOCKER_CONTAINER$1} --format='{{.State.Status}}'`, { stdio: "pipe" });
4756
- execSync(`docker exec ${DOCKER_CONTAINER$1} pg_dump -U ${PG_USER$1} ${PG_DATABASE$1} > "${sqlDest}"`, {
4757
- stdio: [
4758
- "pipe",
4755
+ execFileSync("docker", [
4756
+ "inspect",
4757
+ DOCKER_CONTAINER$1,
4758
+ "--format={{.State.Status}}"
4759
+ ], { stdio: "pipe" });
4760
+ const sqlFd = openSync(sqlDest, "w");
4761
+ try {
4762
+ const dump = spawnSync("docker", [
4763
+ "exec",
4764
+ DOCKER_CONTAINER$1,
4765
+ "pg_dump",
4766
+ "-U",
4767
+ PG_USER$1,
4768
+ PG_DATABASE$1
4769
+ ], { stdio: [
4759
4770
  "pipe",
4771
+ sqlFd,
4760
4772
  "pipe"
4761
- ],
4762
- shell: true
4763
- });
4773
+ ] });
4774
+ if (dump.status !== 0) throw new Error(dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`);
4775
+ } finally {
4776
+ closeSync(sqlFd);
4777
+ }
4764
4778
  results.push({
4765
4779
  label: "Postgres DB",
4766
4780
  path: sqlDest,
@@ -5650,7 +5664,7 @@ function cmdOpen(opts) {
5650
5664
  const url = `obsidian://open?vault=${encodeURIComponent(vaultName)}`;
5651
5665
  console.log(dim$1(` Opening: ${url}`));
5652
5666
  try {
5653
- execSync(`open "${url}"`, { stdio: "ignore" });
5667
+ execFileSync("open", [url], { stdio: "ignore" });
5654
5668
  console.log(ok$1(` Opened vault "${vaultName}" in Obsidian.`));
5655
5669
  } catch (e) {
5656
5670
  console.error(err(` Failed to open Obsidian: ${e}`));
@@ -7038,10 +7052,19 @@ function cmdLogs(opts) {
7038
7052
  return;
7039
7053
  }
7040
7054
  if (opts.follow) try {
7041
- execSync(`tail -f -n ${lines} "${DAEMON_LOG}"`, { stdio: "inherit" });
7055
+ execFileSync("tail", [
7056
+ "-f",
7057
+ "-n",
7058
+ lines,
7059
+ DAEMON_LOG
7060
+ ], { stdio: "inherit" });
7042
7061
  } catch {}
7043
7062
  else try {
7044
- execSync(`tail -n ${lines} "${DAEMON_LOG}"`, { stdio: "inherit" });
7063
+ execFileSync("tail", [
7064
+ "-n",
7065
+ lines,
7066
+ DAEMON_LOG
7067
+ ], { stdio: "inherit" });
7045
7068
  } catch (e) {
7046
7069
  const msg = e instanceof Error ? e.message : String(e);
7047
7070
  console.error(err(`Could not read log: ${msg}`));
@@ -7050,7 +7073,7 @@ function cmdLogs(opts) {
7050
7073
  }
7051
7074
  function registerDaemonCommands(daemonCmd) {
7052
7075
  daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
7053
- const { serve } = await import("./daemon-jviL3VVJ.mjs");
7076
+ const { serve } = await import("./daemon-bJi1gDkG.mjs");
7054
7077
  const { loadConfig: lc, ensureConfigDir } = await import("./config-DjXq_W42.mjs");
7055
7078
  ensureConfigDir();
7056
7079
  await serve(lc());
@@ -7228,11 +7251,20 @@ function registerRestoreCommands(program) {
7228
7251
  if (hasSql && opts.postgres) {
7229
7252
  console.log(dim$1(" Restoring Postgres database (this may take a while)..."));
7230
7253
  try {
7231
- execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, { stdio: "pipe" });
7232
- execSync(`docker exec ${DOCKER_CONTAINER} psql -U ${PG_USER} -c "DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};"`, {
7233
- stdio: "pipe",
7234
- shell: true
7235
- });
7254
+ execFileSync("docker", [
7255
+ "inspect",
7256
+ DOCKER_CONTAINER,
7257
+ "--format={{.State.Status}}"
7258
+ ], { stdio: "pipe" });
7259
+ execFileSync("docker", [
7260
+ "exec",
7261
+ DOCKER_CONTAINER,
7262
+ "psql",
7263
+ "-U",
7264
+ PG_USER,
7265
+ "-c",
7266
+ `DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};`
7267
+ ], { stdio: "pipe" });
7236
7268
  const sqlContent = readFileSync(join(resolvedDir, "postgres-pai.sql"), "utf8");
7237
7269
  const psqlResult = spawnSync("docker", [
7238
7270
  "exec",
@@ -8376,6 +8408,11 @@ async function stepSettings(rl, daName) {
8376
8408
  matcher: "Bash",
8377
8409
  command: "${ADAPTER_DIR}/Hooks/security-validator.mjs"
8378
8410
  },
8411
+ {
8412
+ hookType: "PreToolUse",
8413
+ matcher: "Bash|Edit|Write|MultiEdit|NotebookEdit",
8414
+ command: "${ADAPTER_DIR}/Hooks/worker-guard.mjs"
8415
+ },
8379
8416
  {
8380
8417
  hookType: "PreToolUse",
8381
8418
  matcher: "*",
@@ -9304,7 +9341,7 @@ async function stepTaskBus(rl) {
9304
9341
  if (await promptYesNo(rl, "Found TODOIST_API_KEY in the environment. Use it?", true)) apiKey = envToken;
9305
9342
  }
9306
9343
  while (!apiKey) {
9307
- apiKey = (await prompt$1(rl, " Paste your Todoist API token: ")).trim();
9344
+ apiKey = (await prompt$1(rl, " Paste your Todoist API token here: ")).trim();
9308
9345
  if (!apiKey) console.log(c.warn(" A token is required, or answer no to skip the task bus."));
9309
9346
  }
9310
9347
  line$1();
@@ -15574,20 +15611,32 @@ function pgDump(container, pgConfig, destPath) {
15574
15611
  const database = resolvePgDatabaseName(pgConfig);
15575
15612
  const user = pgConfig.user ?? "pai";
15576
15613
  mkdirSync(dirname(destPath), { recursive: true });
15614
+ const fd = openSync(destPath, "w");
15577
15615
  try {
15578
- execSync(`docker exec ${container} pg_dump -U ${user} -Fc ${database} > "${destPath}"`, {
15579
- stdio: [
15580
- "pipe",
15581
- "pipe",
15582
- "pipe"
15583
- ],
15584
- shell: true
15585
- });
15616
+ const dump = spawnSync("docker", [
15617
+ "exec",
15618
+ container,
15619
+ "pg_dump",
15620
+ "-U",
15621
+ user,
15622
+ "-Fc",
15623
+ database
15624
+ ], { stdio: [
15625
+ "pipe",
15626
+ fd,
15627
+ "pipe"
15628
+ ] });
15629
+ if (dump.status !== 0) return {
15630
+ ok: false,
15631
+ reason: `pg_dump failed: ${dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`}`
15632
+ };
15586
15633
  } catch (e) {
15587
15634
  return {
15588
15635
  ok: false,
15589
15636
  reason: `pg_dump failed: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`
15590
15637
  };
15638
+ } finally {
15639
+ closeSync(fd);
15591
15640
  }
15592
15641
  if (!existsSync(destPath) || statSync(destPath).size === 0) return {
15593
15642
  ok: false,
@@ -16042,7 +16091,7 @@ async function migrateToPostgres(opts = {}) {
16042
16091
  const dump = pgDump(container, pgConfig, dumpPath);
16043
16092
  if (!dump.ok) return refused(dump.reason, dryRun);
16044
16093
  }
16045
- const { PostgresBackend } = await import("./postgres-nvU5pShF.mjs");
16094
+ const { PostgresBackend } = await import("./postgres-CxD6_Dca.mjs");
16046
16095
  await PostgresBackend.ensureDatabase(pgConfig);
16047
16096
  const sqlite = new BetterSqlite3(sqliteFederationPath, { readonly: true });
16048
16097
  const registrySqlite = sqliteRegistryPath === sqliteFederationPath ? sqlite : new BetterSqlite3(sqliteRegistryPath, { readonly: true });
@@ -16613,7 +16662,7 @@ async function getSessionTags(registryBackend, sessionId) {
16613
16662
  * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.
16614
16663
  */
16615
16664
  async function cmdList(projectSlug, opts) {
16616
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16665
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16617
16666
  const registryBackend = await getRegistryBackend();
16618
16667
  const limit = parseInt(opts.limit ?? "20", 10);
16619
16668
  let project;
@@ -16678,7 +16727,7 @@ async function cmdList(projectSlug, opts) {
16678
16727
  console.log(dim$1(` ${rows.length} session(s) shown (limit: ${limit})`));
16679
16728
  }
16680
16729
  async function cmdInfo(projectSlug, sessionNumber) {
16681
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16730
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16682
16731
  const registryBackend = await getRegistryBackend();
16683
16732
  const project = await getProject$1(registryBackend, projectSlug);
16684
16733
  if (!project) {
@@ -16702,7 +16751,7 @@ async function cmdInfo(projectSlug, sessionNumber) {
16702
16751
  console.log();
16703
16752
  }
16704
16753
  async function cmdRename(projectSlug, numberOrLatest, newSlug) {
16705
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16754
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16706
16755
  await cmdRenameWith(await getRegistryBackend(), projectSlug, numberOrLatest, newSlug);
16707
16756
  }
16708
16757
  async function cmdRenameWith(registryBackend, projectSlug, numberOrLatest, newSlug) {
@@ -16765,7 +16814,7 @@ async function cmdRenameWith(registryBackend, projectSlug, numberOrLatest, newSl
16765
16814
  console.log();
16766
16815
  }
16767
16816
  async function cmdSlug(projectSlug, numberOrLatest, opts) {
16768
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16817
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16769
16818
  const registryBackend = await getRegistryBackend();
16770
16819
  const project = await getProject$1(registryBackend, projectSlug);
16771
16820
  if (!project) {
@@ -16795,7 +16844,7 @@ async function cmdSlug(projectSlug, numberOrLatest, opts) {
16795
16844
  }
16796
16845
  }
16797
16846
  async function cmdTag(projectSlug, sessionNumber, rawTags) {
16798
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16847
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16799
16848
  const registryBackend = await getRegistryBackend();
16800
16849
  const project = await getProject$1(registryBackend, projectSlug);
16801
16850
  if (!project) {
@@ -16835,7 +16884,7 @@ async function cmdTag(projectSlug, sessionNumber, rawTags) {
16835
16884
  console.log();
16836
16885
  }
16837
16886
  async function cmdRoute(projectSlug, sessionNumber, targetProjectSlug, opts) {
16838
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16887
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16839
16888
  const registryBackend = await getRegistryBackend();
16840
16889
  const project = await getProject$1(registryBackend, projectSlug);
16841
16890
  if (!project) {
@@ -16877,7 +16926,7 @@ async function cmdRoute(projectSlug, sessionNumber, targetProjectSlug, opts) {
16877
16926
  console.log();
16878
16927
  }
16879
16928
  async function cmdActive(opts) {
16880
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
16929
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
16881
16930
  const registryBackend = await getRegistryBackend();
16882
16931
  const minutes = parseInt(opts.minutes ?? "60", 10);
16883
16932
  const cutoff = Date.now() - minutes * 60 * 1e3;
@@ -16965,7 +17014,7 @@ async function cmdActive(opts) {
16965
17014
  }
16966
17015
  async function cmdAutoRoute(opts) {
16967
17016
  const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CF8WNaoy.mjs");
16968
- const { getRegistryBackend, createStorageBackend } = await import("./factory-DW7BCJJT.mjs");
17017
+ const { getRegistryBackend, createStorageBackend } = await import("./factory-UO9_DFMN.mjs");
16969
17018
  const { loadConfig } = await import("./config-DjXq_W42.mjs");
16970
17019
  const config = loadConfig();
16971
17020
  const registryBackend = await getRegistryBackend();
@@ -17202,7 +17251,7 @@ async function cmdPause(opts) {
17202
17251
  process.exitCode = 1;
17203
17252
  return;
17204
17253
  }
17205
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
17254
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
17206
17255
  const registryBackend = await getRegistryBackend();
17207
17256
  const cwd = process.cwd();
17208
17257
  const project = await resolveProjectByCwd(registryBackend, cwd);
@@ -17597,7 +17646,7 @@ function buildStopBody(project, sessionId) {
17597
17646
  }
17598
17647
  }
17599
17648
  async function cmdHandover(projectSlug, numberOrLatest, sessionId) {
17600
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
17649
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
17601
17650
  const registryBackend = await getRegistryBackend();
17602
17651
  let project;
17603
17652
  if (projectSlug) {
@@ -17893,7 +17942,7 @@ function touchSentinel(rootPath) {
17893
17942
  async function cmdAutosave(opts) {
17894
17943
  const minGap = parseInt(opts.minGap ?? String(DEFAULT_MIN_GAP), 10);
17895
17944
  const cwd = process.cwd();
17896
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
17945
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
17897
17946
  const registryBackend = await getRegistryBackend();
17898
17947
  const project = await resolveProjectByCwd(registryBackend, cwd);
17899
17948
  if (!project) return;
@@ -18018,7 +18067,7 @@ async function cmdRecent(opts) {
18018
18067
  shortId: s.shortId,
18019
18068
  resumable: s.resumable,
18020
18069
  sessionStatus: s.sessionStatus,
18021
- age: fmtAge(s.mtime),
18070
+ age: fmtAge$1(s.mtime),
18022
18071
  mtime: s.mtime,
18023
18072
  name: s.friendlyName,
18024
18073
  lastUserPrompt: s.lastUserPrompt,
@@ -18056,7 +18105,7 @@ async function cmdRecent(opts) {
18056
18105
  return [
18057
18106
  chalk.dim(String(idx + 1)),
18058
18107
  chalk.cyan(s.shortId),
18059
- dim$1(fmtAge(s.mtime)),
18108
+ dim$1(fmtAge$1(s.mtime)),
18060
18109
  name.length > 26 ? name.slice(0, 25) + "…" : name,
18061
18110
  prompt,
18062
18111
  s.userLines > 0 ? String(s.userLines) : dim$1("—"),
@@ -18156,7 +18205,7 @@ async function cmdGoto(query, opts) {
18156
18205
  console.log(` fallback: ${chalk.yellow(argvFresh)}`);
18157
18206
  if (resumableSession) {
18158
18207
  console.log(`\n uuid: ${resumableSession.uuid}`);
18159
- console.log(` age: ${fmtAge(resumableSession.mtime)}`);
18208
+ console.log(` age: ${fmtAge$1(resumableSession.mtime)}`);
18160
18209
  console.log(` status: ${resumableSession.sessionStatus}`);
18161
18210
  console.log(` sys: ${resumableSession.topLevelSystemLines} system lines`);
18162
18211
  }
@@ -18802,9 +18851,15 @@ function fail$3(e) {
18802
18851
  process.exitCode = 1;
18803
18852
  }
18804
18853
  function registerWorkerModelCommand(workerCmd) {
18805
- workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\n`pai worker capability` picks which provider serves one across the whole config.\n--provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").action((what, model, opts) => {
18854
+ workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\n`pai worker capability` picks which provider serves one across the whole config.\n--provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").option("--capability <name>", "Capability to set/show, with a model id as the only argument (same as the MCP tool)").action((what, model, opts) => {
18806
18855
  try {
18807
18856
  const { workers } = readWorkersSection();
18857
+ if (opts.capability !== void 0) {
18858
+ if (!isModelCapability(opts.capability)) throw new WorkersConfigError(`"${opts.capability}" is not a valid capability name`);
18859
+ if (model !== void 0) throw new WorkersConfigError("with --capability pass only a model id");
18860
+ model = what;
18861
+ what = opts.capability;
18862
+ }
18808
18863
  if (what === void 0) {
18809
18864
  for (const line of describeModels(workers)) console.log(` ${line}`);
18810
18865
  return;
@@ -18815,14 +18870,14 @@ function registerWorkerModelCommand(workerCmd) {
18815
18870
  capability = what;
18816
18871
  if (model === void 0) {
18817
18872
  const name = resolveProviderName(workers, opts.provider);
18818
- const p = workers.providers[name];
18873
+ const p = getProviderOrNative(workers, name);
18819
18874
  if (p) console.log(` ${name} ${capability} ${p.models[capability] ?? "(none)"}`);
18820
18875
  return;
18821
18876
  }
18822
18877
  id = model;
18823
18878
  } else if (model !== void 0) throw new WorkersConfigError(`unexpected second argument "${model}" — usage: model [<capability>] <model-id>`);
18824
18879
  const name = resolveProviderName(workers, opts.provider);
18825
- const p = setProviderModel(name, capability, id).providers[name];
18880
+ const p = getProviderOrNative(setProviderModel(name, capability, id), name);
18826
18881
  if (p) {
18827
18882
  console.log(ok$1(`${name} ${capability} model: ${p.models[capability]}`));
18828
18883
  console.log(dim$1(` ${modelPrefsText(p)}`));
@@ -18960,6 +19015,89 @@ function registerWorkerConfigCommands(workerCmd) {
18960
19015
  });
18961
19016
  }
18962
19017
 
19018
+ //#endregion
19019
+ //#region src/workers/pending.ts
19020
+ /**
19021
+ * Pending worker results: finished workers whose worktree branch still holds
19022
+ * work that never reached the repo (commits not in HEAD, or uncommitted
19023
+ * files). Surfaced by `pai worker ps/pending`, the SessionStart hook, the
19024
+ * status line and the prepublish gate so an unmerged fix cannot be forgotten.
19025
+ */
19026
+ /** Stale after this long: `pai worker gc` (archive) instead of merge. */
19027
+ const STALE_HOURS = 24;
19028
+ function toplevel(dir) {
19029
+ try {
19030
+ return git(dir, ["rev-parse", "--show-toplevel"]);
19031
+ } catch {
19032
+ return null;
19033
+ }
19034
+ }
19035
+ function fmtAge(ms) {
19036
+ const h = Math.floor(ms / 36e5);
19037
+ return h >= 48 ? `${Math.floor(h / 24)}d` : h >= 1 ? `${h}h` : `${Math.floor(ms / 6e4)}m`;
19038
+ }
19039
+ /** Finished workers with unmerged work; `repoRoot` limits to one repo, else every repo. */
19040
+ function pendingResults(logDir, repoRoot, now = Date.now()) {
19041
+ const want = repoRoot ? toplevel(repoRoot) : null;
19042
+ if (repoRoot && !want) return [];
19043
+ const out = [];
19044
+ for (const st of loadStatuses(logDir)) {
19045
+ if (st.archived || st.merged || isLive(st)) continue;
19046
+ if (!st.worktreeDir || !existsSync(st.worktreeDir)) continue;
19047
+ const repo = toplevel(st.cwd);
19048
+ if (!repo || want && repo !== want) continue;
19049
+ let commits = 0;
19050
+ let dirty = 0;
19051
+ try {
19052
+ if (st.branch) commits = git(repo, [
19053
+ "cherry",
19054
+ "HEAD",
19055
+ `refs/heads/${st.branch}`
19056
+ ]).split("\n").filter((l) => l.startsWith("+")).length;
19057
+ dirty = uncommittedPaths(st.worktreeDir).filter((p) => !/(^|\/)node_modules(\/|$)/.test(p)).length;
19058
+ } catch {
19059
+ continue;
19060
+ }
19061
+ if (!commits && !dirty) continue;
19062
+ let ageMs = 0;
19063
+ try {
19064
+ ageMs = Math.max(0, now - statSync(statusPath(logDir, st.id)).mtimeMs);
19065
+ } catch {}
19066
+ out.push({
19067
+ id: st.id,
19068
+ label: st.label,
19069
+ repo,
19070
+ ageMs,
19071
+ age: fmtAge(ageMs),
19072
+ commits,
19073
+ dirty
19074
+ });
19075
+ }
19076
+ return out;
19077
+ }
19078
+ /** One human line per result, with the exact next command. */
19079
+ function formatPending(results, showRepo = false) {
19080
+ return results.map((r) => {
19081
+ const next = r.ageMs > STALE_HOURS * 36e5 ? "pai worker gc" : `pai worker merge ${r.id}`;
19082
+ const repo = showRepo ? ` ${r.repo}` : "";
19083
+ return `${r.id} ${r.age} ${r.commits} commit(s), ${r.dirty} dirty ${r.label}${repo} -> ${next}`;
19084
+ }).join("\n");
19085
+ }
19086
+ /**
19087
+ * Release gate: exit code and message for `pai worker pending --gate`.
19088
+ * PAI_ALLOW_PENDING=1 overrides.
19089
+ */
19090
+ function pendingGate(results, env = process.env) {
19091
+ if (!results.length || env.PAI_ALLOW_PENDING === "1") return {
19092
+ code: 0,
19093
+ message: ""
19094
+ };
19095
+ return {
19096
+ code: 1,
19097
+ message: `${results.length} unmerged worker result(s) in this repo — merge or discard before publishing:\n${formatPending(results)}\nOverride deliberately with PAI_ALLOW_PENDING=1.`
19098
+ };
19099
+ }
19100
+
18963
19101
  //#endregion
18964
19102
  //#region src/workers/wait.ts
18965
19103
  /**
@@ -19141,7 +19279,24 @@ function registerWorkerCommands(workerCmd) {
19141
19279
  });
19142
19280
  workerCmd.command("ps").description("List workers of this session (RUNNING + FINISHED); --all for every worker").option("--all", "Show workers of all sessions, not just this terminal's").action((opts) => {
19143
19281
  try {
19144
- console.log(psOutput(currentLogDir(), opts.all === true));
19282
+ const logDir = currentLogDir();
19283
+ const pending = pendingResults(logDir, process.cwd()).length;
19284
+ if (pending) console.log(`${pending} unmerged result(s) — pai worker pending`);
19285
+ console.log(psOutput(logDir, opts.all === true));
19286
+ } catch (e) {
19287
+ fail(e);
19288
+ }
19289
+ });
19290
+ workerCmd.command("pending").description("Finished workers whose branch or worktree holds work not yet merged into this repo").option("--all", "Every repo in the log dir, not just the current one").option("--json", "Machine-readable output").option("--gate", "Release gate: exit 1 with the list unless PAI_ALLOW_PENDING=1").action((opts) => {
19291
+ try {
19292
+ const res = pendingResults(currentLogDir(), opts.all ? void 0 : process.cwd());
19293
+ if (opts.gate) {
19294
+ const g = pendingGate(res);
19295
+ if (g.message) console.error(g.message);
19296
+ process.exit(g.code);
19297
+ }
19298
+ if (opts.json) console.log(JSON.stringify(res, null, 2));
19299
+ else console.log(res.length ? formatPending(res, opts.all === true) : "no pending worker results");
19145
19300
  } catch (e) {
19146
19301
  fail(e);
19147
19302
  }
@@ -19179,7 +19334,7 @@ function registerWorkerCommands(workerCmd) {
19179
19334
  const logDir = currentLogDir();
19180
19335
  const term = process.env.ITERM_SESSION_ID ?? "";
19181
19336
  if (id) {
19182
- const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : await openPaneForWorker(logDir, workers, id, term);
19337
+ const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : (await openPaneForWorker(logDir, workers, id, term)).message;
19183
19338
  console.log(msg);
19184
19339
  } else console.log(await openFollowPane(logDir, workers, term, opts.check === true));
19185
19340
  } catch (e) {
@@ -19290,6 +19445,18 @@ function registerWorkerCommands(workerCmd) {
19290
19445
  fail(e);
19291
19446
  }
19292
19447
  });
19448
+ workerCmd.command("gc").description("Archive leftover worker worktrees (state kept under refs/pai-archive/<id>; gitignored files are not kept)").option("--older-than <hours>", "minimum age of a finished worker", "24").option("--dry-run", "print what would be archived, change nothing").action((opts) => {
19449
+ try {
19450
+ const hours = Number(opts.olderThan);
19451
+ if (!Number.isFinite(hours) || hours < 0) throw new Error(`bad --older-than "${opts.olderThan}"`);
19452
+ const res = gcWorktrees(currentLogDir(), hours, opts.dryRun === true);
19453
+ const verb = opts.dryRun ? "would archive" : "archived";
19454
+ for (const r of res) console.log(r.skipped ? `skipped ${r.id}: ${r.skipped}` : `${verb} ${r.id} -> ${r.archive}`);
19455
+ console.log(`${res.filter((r) => r.archive).length} ${opts.dryRun ? "would be archived" : "archived"}`);
19456
+ } catch (e) {
19457
+ fail(e);
19458
+ }
19459
+ });
19293
19460
  workerCmd.command("kill <id>").description("Send SIGTERM to a running worker process").action(async (id) => {
19294
19461
  try {
19295
19462
  const logDir = currentLogDir();
@@ -20170,7 +20337,7 @@ async function countVectorDbPaths(oldPaths) {
20170
20337
  if (oldPaths.length === 0) return 0;
20171
20338
  try {
20172
20339
  const { loadConfig } = await import("./config-DjXq_W42.mjs");
20173
- const { createStorageBackend } = await import("./factory-DW7BCJJT.mjs");
20340
+ const { createStorageBackend } = await import("./factory-UO9_DFMN.mjs");
20174
20341
  const config = loadConfig();
20175
20342
  if (config.storageBackend !== "postgres") return 0;
20176
20343
  const backend = await createStorageBackend(config);
@@ -20188,7 +20355,7 @@ async function updateVectorDbPaths(moves) {
20188
20355
  if (moves.length === 0) return 0;
20189
20356
  try {
20190
20357
  const { loadConfig } = await import("./config-DjXq_W42.mjs");
20191
- const { createStorageBackend } = await import("./factory-DW7BCJJT.mjs");
20358
+ const { createStorageBackend } = await import("./factory-UO9_DFMN.mjs");
20192
20359
  const config = loadConfig();
20193
20360
  if (config.storageBackend !== "postgres") return 0;
20194
20361
  const backend = await createStorageBackend(config);
@@ -20275,7 +20442,7 @@ async function displayDryRun(plans) {
20275
20442
  console.log();
20276
20443
  }
20277
20444
  async function executeCleanup(plans, skipReindex) {
20278
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
20445
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
20279
20446
  const registryBackend = await getRegistryBackend();
20280
20447
  let deleted = 0;
20281
20448
  let renamed = 0;
@@ -20472,7 +20639,7 @@ async function executeCleanup(plans, skipReindex) {
20472
20639
  //#region src/cli/commands/session-cleanup/index.ts
20473
20640
  function registerSessionCleanupCommand(sessionCmd) {
20474
20641
  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) => {
20475
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
20642
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
20476
20643
  const registryBackend = await getRegistryBackend();
20477
20644
  const dryRun = !opts.execute;
20478
20645
  const skipReindex = opts.reindex === false;
@@ -20542,7 +20709,7 @@ function finalizeNote(notePath) {
20542
20709
  }
20543
20710
  async function cmdEnd(opts) {
20544
20711
  await cmdPause(opts);
20545
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
20712
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
20546
20713
  const registryBackend = await getRegistryBackend();
20547
20714
  const cwd = process.cwd();
20548
20715
  const project = await registryBackend.findProjectByCwdPrefix(cwd);
@@ -20990,7 +21157,7 @@ async function cmdFind(query, opts) {
20990
21157
  */
20991
21158
  async function getProjects(includeArchived) {
20992
21159
  try {
20993
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
21160
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
20994
21161
  const rows = await (await getRegistryBackend()).listProjectsWithSessionStats();
20995
21162
  return includeArchived ? rows : rows.filter((p) => p.status === "active");
20996
21163
  } catch {
@@ -21131,7 +21298,7 @@ function buildFeedFrom(allSessions, projects, liveSessions, allProjects) {
21131
21298
  const tag = tagFor(e.status);
21132
21299
  const name = chalk.white(pad(e.name, 22));
21133
21300
  const where = dir ? dim$1(shorten(dir, 40)) : chalk.green("(running in another tab)");
21134
- const age = e.status === "live" ? chalk.green("now") : e.lastActivity > 0 ? dim$1(fmtAge(e.lastActivity)) : dim$1("—");
21301
+ const age = e.status === "live" ? chalk.green("now") : e.lastActivity > 0 ? dim$1(fmtAge$1(e.lastActivity)) : dim$1("—");
21135
21302
  const kwDisp = kw ? dim$1(" " + kw) : "";
21136
21303
  const display = `${tag} ${name} ${pad(age, 5)} ${where}${kwDisp}`;
21137
21304
  const search = `${e.name} ${dir ?? ""} ${kw}`.toLowerCase();
@@ -21197,7 +21364,7 @@ function contextFor(rec) {
21197
21364
  const notes = recentMarkdown(rec.dir, 4);
21198
21365
  if (notes.length) {
21199
21366
  lines.push(dim$1("recent notes:"));
21200
- for (const n of notes) lines.push(" " + dim$1(n.rel.slice(0, 60)) + " " + dim$1(fmtAge(n.mtime)));
21367
+ for (const n of notes) lines.push(" " + dim$1(n.rel.slice(0, 60)) + " " + dim$1(fmtAge$1(n.mtime)));
21201
21368
  } else lines.push(dim$1("(no notes here yet)"));
21202
21369
  ctxCache.set(rec.dir, lines);
21203
21370
  return lines;
@@ -21464,7 +21631,7 @@ async function cmdPick(opts = {}) {
21464
21631
  const onRemove = async (rec) => {
21465
21632
  if (!rec.slug) return false;
21466
21633
  try {
21467
- const { getRegistryBackend } = await import("./factory-DW7BCJJT.mjs");
21634
+ const { getRegistryBackend } = await import("./factory-UO9_DFMN.mjs");
21468
21635
  const registryBackend = await getRegistryBackend();
21469
21636
  const project = await registryBackend.getProjectBySlug(rec.slug);
21470
21637
  if (!project || project.status !== "active") return false;
@@ -21749,4 +21916,4 @@ claude() {
21749
21916
 
21750
21917
  //#endregion
21751
21918
  export { drainStdio as n, buildProgram as t };
21752
- //# sourceMappingURL=program-BnsjB57Q.mjs.map
21919
+ //# sourceMappingURL=program-BRvCzhjx.mjs.map