@tekmidian/pai 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/{auto-route-B7Yu6Uam.mjs → auto-route-21GdHL70.mjs} +1 -1
  3. package/dist/{auto-route-CjjQqU1o.mjs → auto-route-C3S2W3Bc.mjs} +1 -1
  4. package/dist/{budget-Dod6rnA9.mjs → budget-BrloTOZW.mjs} +1 -1
  5. package/dist/{chain-7n9Gx56r.mjs → chain-D7PFur8Y.mjs} +2 -2
  6. package/dist/cli/index.mjs +70 -180
  7. package/dist/{cockpit-actions-DR9X514g.mjs → cockpit-actions-BfRMr2sm.mjs} +6 -6
  8. package/dist/{cockpit-actions-BBHeyEZI.mjs → cockpit-actions-OodH_vx_.mjs} +20 -18
  9. package/dist/{cockpit-memory--dnShXBA.mjs → cockpit-memory-teG9M8bG.mjs} +6 -6
  10. package/dist/config/defaults/config.yaml +6 -0
  11. package/dist/{controls-CPoeudou.mjs → controls-JLm6g0yP.mjs} +7 -5
  12. package/dist/daemon/index.mjs +18 -18
  13. package/dist/{daemon-DKdBNKfa.mjs → daemon-DeYImF1F.mjs} +18 -18
  14. package/dist/daemon-mcp/index.mjs +18 -18
  15. package/dist/{daemon-DPAo6WTa.mjs → daemon-nXanenDM.mjs} +21 -17
  16. package/dist/{detect-9CZNO0yf.mjs → detect-BLKsUOgG.mjs} +2 -2
  17. package/dist/{detect-D1ziFFfA.mjs → detect-Rrimh0xc.mjs} +3 -3
  18. package/dist/{embed-pass-DIWjA68P.mjs → embed-pass-e64AwDCa.mjs} +1 -1
  19. package/dist/{endreason-C577xIQz.mjs → endreason-BC-v05H_.mjs} +17 -3
  20. package/dist/{factory-DjAAsqr_.mjs → factory-B7nv75rC.mjs} +2 -2
  21. package/dist/{factory-Bsib-qDG.mjs → factory-Bs9imVMq.mjs} +2 -2
  22. package/dist/{fallback-DFlItw1Z.mjs → fallback-CBWpp2F2.mjs} +227 -5
  23. package/dist/{health-Dnym-qcT.mjs → health-C4FQmkJn.mjs} +2 -2
  24. package/dist/{helpers-BD6V-p9w.mjs → helpers-DSNK_1KP.mjs} +2 -2
  25. package/dist/{helpers-CMGjy_0N.mjs → helpers-Dz7mjTSw.mjs} +3 -3
  26. package/dist/hooks/capture-all-events.mjs +21 -2
  27. package/dist/hooks/capture-tool-output.mjs +21 -2
  28. package/dist/hooks/worker-pool-host.mjs +52 -9
  29. package/dist/hooks/worker-status-line.mjs +34 -6
  30. package/dist/{indexer-backend-Cll7le4P.mjs → indexer-backend-BdjeL4P8.mjs} +31 -228
  31. package/dist/{ipc-client-CaiGJnSM.mjs → ipc-client-CnYEF31R.mjs} +1 -1
  32. package/dist/{ipc-client-ZRmDKd5Z.mjs → ipc-client-DN7EvoVo.mjs} +1 -1
  33. package/dist/{kill-tree-EWYaEk5v.mjs → kill-tree-DleMNZX3.mjs} +2 -2
  34. package/dist/{kill-tree-BG7dRVtp.mjs → kill-tree-SV6hrIa3.mjs} +2 -2
  35. package/dist/{launch-Jh5NVeNe.mjs → launch-Di7N-J5X.mjs} +2 -2
  36. package/dist/{launch-CXVlZjHt.mjs → launch-DlZTKE3u.mjs} +2 -2
  37. package/dist/{main-config-ops-BEMpuhDz.mjs → main-config-ops-kTXR8Pyv.mjs} +1 -1
  38. package/dist/{main-resolver-3Ns1HPmS.mjs → main-resolver-yp_puSSb.mjs} +5 -5
  39. package/dist/{model-inventory-BFVSNFT3.mjs → model-inventory-h6Z9fN_o.mjs} +1 -1
  40. package/dist/{models-add-C1QIc1DA.mjs → models-add-TVni3A7V.mjs} +19 -19
  41. package/dist/{pane-audit-DKQt71s4.mjs → pane-audit-Lqy9N_Ki.mjs} +23 -5
  42. package/dist/{planner-BmFACdLa.mjs → planner-j77ndkBf.mjs} +11 -11
  43. package/dist/{postgres-BdBNkL_l.mjs → postgres-BK4FP9Zg.mjs} +1 -1
  44. package/dist/{postgres-C6ti4haA.mjs → postgres-DNIOQijl.mjs} +17 -7
  45. package/dist/{project-setup-jvfONJTQ.mjs → project-setup-CBRtFFh0.mjs} +15 -235
  46. package/dist/{providers-CblTFrhz.mjs → providers-C9QC0TtO.mjs} +2 -2
  47. package/dist/{providers-BeDiVEb-.mjs → providers-FvCLzFge.mjs} +2 -2
  48. package/dist/{query-feedback-BRfPtqki.mjs → query-feedback-DN52N9C5.mjs} +1 -1
  49. package/dist/{render-CoMvwMPs.mjs → render-B35bQTE-.mjs} +2 -2
  50. package/dist/{render-BYmQkuLd.mjs → render-C84BqQRC.mjs} +2 -2
  51. package/dist/{root-Xglu0OPD.mjs → root-C4oXlXS_.mjs} +7 -7
  52. package/dist/{root-FtJsxYnm.mjs → root-DD1xx_Vu.mjs} +12 -12
  53. package/dist/route-table-FEfDuhIV.mjs +15 -0
  54. package/dist/{routing-BAAST9vT.mjs → routing-DmkheCRH.mjs} +126 -8
  55. package/dist/{routing-CENt4HH1.mjs → routing-f2zulGwD.mjs} +1 -1
  56. package/dist/{run-CSC7mgPf.mjs → run-BwZKBJZZ.mjs} +25 -11
  57. package/dist/{scan-C_Y5W9py.mjs → scan-BBGvU5EB.mjs} +2 -2
  58. package/dist/{session-BeqAbNPd.mjs → session-BAhtXdxj.mjs} +11 -11
  59. package/dist/{session-8iqQDkqb.mjs → session-Dn5OJXlg.mjs} +20 -20
  60. package/dist/{session-scan-BJI_ZO8M.mjs → session-scan-B57dNuA4.mjs} +1 -1
  61. package/dist/{session-scan-W-gbwJHM.mjs → session-scan-DUn10MBd.mjs} +2 -2
  62. package/dist/session-usage-CPvXvSkw.mjs +222 -0
  63. package/dist/{sources-ESUkZaWv.mjs → sources-CiE3rZek.mjs} +1 -1
  64. package/dist/{viewer-CqozWW39.mjs → viewer-DEySE4c_.mjs} +6 -6
  65. package/dist/{viewer-mDXUNHNy.mjs → viewer-wfKnvnrI.mjs} +5 -5
  66. package/dist/{worker-config-DESkIjMF.mjs → worker-config-CO5FXLAT.mjs} +18 -18
  67. package/dist/{worker-config-CJhEMk19.mjs → worker-config-eY5Qy4vl.mjs} +318 -21
  68. package/dist/{zettelkasten-BzMm8REH.mjs → zettelkasten-_h_tUm2T.mjs} +1 -1
  69. package/dist/{zettelkasten-B_rRRRvD.mjs → zettelkasten-tpCRBYvH.mjs} +2 -2
  70. package/docs/cockpit.md +1 -0
  71. package/gemini-extension.json +1 -1
  72. package/package.json +1 -1
  73. package/pai-plugin.json +1 -1
  74. package/plugins/context-preservation/plugin.json +1 -1
  75. package/plugins/core/plugin.json +1 -1
  76. package/plugins/creative/plugin.json +1 -1
  77. package/plugins/observability/plugin.json +1 -1
  78. package/plugins/productivity/plugin.json +1 -1
  79. package/plugins/semantic-search/plugin.json +1 -1
  80. package/plugins/ui/plugin.json +1 -1
  81. package/plugins/zettelkasten/plugin.json +1 -1
  82. package/src/hooks/ts/capture-all-events.ts +15 -3
  83. package/src/hooks/ts/lib/raw-capture.ts +7 -0
  84. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +13 -3
  85. package/dist/route-table-B7zpoypi.mjs +0 -15
@@ -7,7 +7,7 @@ import { E as worktreesStatsCached, l as formatWorktreesStats } from "./worktree
7
7
  import { T as ownsPidCached, b as loadStatusesCached, g as isLive, v as loadStatus } from "./status-ggJ7lgVg.mjs";
8
8
  import { C as describeMcp, _ as readPoolMetrics, f as poolMetricsPath, h as poolSettings, i as bundleBuildStamp, l as poolHostProbe, o as poolCarry, u as poolHostStale, v as readPoolState, x as warmOrCold } from "./pool-Dh-q3iJi.mjs";
9
9
  import { a as ownerSessionName } from "./scope-D-h6F6DN.mjs";
10
- import { $t as findLocal, Bt as routeRows, E as budgetCache, Ft as flowText, Gn as isTimeOrdered, Gt as setRoute, Ht as servingChip, I as buildSpeed, Kn as markTimeOrdered, Kt as setSessionRoute, L as percentile, Mt as clearSessionRoute, Ot as isTailnetOrLocalName, Rt as resolveSessionArg, Tt as duplicateModelIds, Vt as routeText, W as currentInventory, Xn as usageLogFile, Xt as clearPolicy, Yn as splitBySessionKind, Yt as POLICY_MODES, _n as pickLocal, at as readInventoryInput, cn as getProfile, en as onBattery, gn as localTierOrder, in as writePolicy, it as readInventory, jt as applyPolicyToRoutes, kt as loopbackRefusal, mn as currentLocalStatus, nn as readPolicy, rn as routeRun, rt as processTreeRssMb, tn as parseDuration, un as readProfiles, vn as probeLoopback, zt as routeChoices } from "./routing-BAAST9vT.mjs";
10
+ import { $n as splitBySessionKind, $t as POLICY_MODES, E as budgetCache, Ft as applyPolicyToRoutes, Gt as routeText, Ht as resolveSessionArg, I as buildSpeed, It as clearSessionRoute, Kt as servingChip, L as percentile, Mt as isTailnetOrLocalName, Nt as loopbackRefusal, Sn as probeLoopback, Tt as duplicateModelIds, Ut as routeChoices, W as currentInventory, Wt as routeRows, Xn as markTimeOrdered, Xt as setSessionRoute, Yn as isTimeOrdered, Yt as setRoute, an as parseDuration, at as readInventoryInput, bn as localTierOrder, cn as writePolicy, en as clearPolicy, er as usageLogFile, fn as getProfile, in as onBattery, it as readInventory, mn as readProfiles, on as readPolicy, rn as findLocal, rt as processTreeRssMb, sn as routeRun, vn as currentLocalStatus, xn as pickLocal, zt as flowText } from "./routing-DmkheCRH.mjs";
11
11
  import { C as stopLines, T as tierButtons, _ as readLocalConfig, a as LocalTierError, c as fetchServedModels, f as listensOnPort, g as planStart, h as parseServers, l as fmtRss, m as parseLocalConfig, u as healthUrl, v as resolveModelPath, x as stopCommandFor } from "./local-tiers-BE4jGaMa.mjs";
12
12
  import { _ as buildSlots, a as buildsDir, g as waitReason, l as listBuildsSync, m as ticketKind, n as breakStaleMutex, x as testSlots } from "./caller-identity-yLFb7CKq.mjs";
13
13
  import { f as sharedScheduler, o as readSchedFile, u as schedPlain } from "./scheduler-CtUyWJ3O.mjs";
@@ -16,25 +16,28 @@ import { a as instancesFromProcesses, d as reconcileInstances, i as instanceNumb
16
16
  import { i as localStatusLine, s as readLocalStatus } from "./local-candidate-DLdwQt0s.mjs";
17
17
  import { t as LOCAL_PROBE_TIMEOUT_MS } from "./timeouts-1MLooEBB.mjs";
18
18
  import { n as OPT, t as CliError } from "./define-CjJsmq-6.mjs";
19
- import { c as resolveFromModule } from "./postgres-C6ti4haA.mjs";
20
- import { c as ensureProxyRunning, l as proxyPort, u as stopProxy } from "./launch-Jh5NVeNe.mjs";
21
- import { a as colorEnabled, f as renderTable, n as readBudget, o as makeStyle } from "./health-Dnym-qcT.mjs";
22
- import { H as testProvider, o as sessionWorkersLine } from "./run-CSC7mgPf.mjs";
23
- import { U as planRows, V as loadPlanStatuses, i as localIdleLine, n as markControlsHeld, s as readLocalIdle, t as clickrControlsArgv, yt as psTable } from "./controls-CPoeudou.mjs";
19
+ import { c as resolveFromModule } from "./postgres-DNIOQijl.mjs";
20
+ import { c as ensureProxyRunning, l as proxyPort, u as stopProxy } from "./launch-Di7N-J5X.mjs";
21
+ import { a as colorEnabled, f as renderTable, n as readBudget, o as makeStyle } from "./health-C4FQmkJn.mjs";
22
+ import { H as testProvider, o as sessionWorkersLine } from "./run-BwZKBJZZ.mjs";
23
+ import { U as planRows, V as loadPlanStatuses, i as localIdleLine, n as markControlsHeld, s as readLocalIdle, t as clickrControlsArgv, yt as psTable } from "./controls-JLm6g0yP.mjs";
24
+ import { w as parseDenialText } from "./render-C84BqQRC.mjs";
24
25
  import { n as diskBundleMtime, r as ensurePoolHost } from "./pool-cli-BggU8GRp.mjs";
25
- import { a as readAudit, d as loadPaneRunStatuses, f as paneRunRows } from "./pane-audit-DKQt71s4.mjs";
26
- import { b as useProvider, f as setClass, g as setWorkersEnabled, l as removeProvider, m as setProviderEnabled, p as setProviderCap, r as classTargetText, s as describeProviders, t as addProvider, v as unsetClass, y as updateProvider } from "./providers-CblTFrhz.mjs";
27
- import { A as cockpitMemoryPayload, B as cockpitLayout, N as liveMemoryDeps, O as embeddingRuntimeName, P as memoryJobProgress, S as runtimeJobProgress, V as cockpitProbeSettings, c as defaultDeps, g as readLabels, h as readCockpitJob, k as readRuntimeCommands, t as COCKPIT_ACTION_EVENT, v as runCockpitAction, x as runningRuntimeJobs } from "./cockpit-actions-BBHeyEZI.mjs";
28
- import { i as fallbackStatusText, n as fallbackOn, r as fallbackStatus, t as fallbackOff } from "./fallback-DFlItw1Z.mjs";
29
- import { appendFileSync, chmodSync, closeSync, copyFileSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
26
+ import { a as readAudit, d as loadPaneRunStatuses, f as paneRunRows } from "./pane-audit-Lqy9N_Ki.mjs";
27
+ import { i as fallbackStatusText, n as fallbackOn, r as fallbackStatus, t as fallbackOff } from "./fallback-CBWpp2F2.mjs";
28
+ import { b as useProvider, f as setClass, g as setWorkersEnabled, l as removeProvider, m as setProviderEnabled, p as setProviderCap, r as classTargetText, s as describeProviders, t as addProvider, v as unsetClass, y as updateProvider } from "./providers-C9QC0TtO.mjs";
29
+ import { A as cockpitMemoryPayload, B as cockpitLayout, N as liveMemoryDeps, O as embeddingRuntimeName, P as memoryJobProgress, S as runtimeJobProgress, V as cockpitProbeSettings, c as defaultDeps, g as readLabels, h as readCockpitJob, k as readRuntimeCommands, t as COCKPIT_ACTION_EVENT, v as runCockpitAction, x as runningRuntimeJobs } from "./cockpit-actions-OodH_vx_.mjs";
30
+ import { n as isRealUserPrompt, t as extractUserPromptText } from "./session-usage-CPvXvSkw.mjs";
31
+ import { appendFileSync, chmodSync, closeSync, copyFileSync, createReadStream, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
30
32
  import { createHash } from "node:crypto";
31
- import { basename, dirname, join, resolve } from "node:path";
33
+ import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
32
34
  import chalk from "chalk";
33
35
  import { homedir, loadavg } from "node:os";
34
36
  import { execFile, execFileSync, spawn } from "node:child_process";
35
37
  import { fileURLToPath } from "node:url";
36
38
  import { LineCounter, isMap, isScalar, parse, parseDocument } from "yaml";
37
39
  import { connect, createServer } from "node:net";
40
+ import { createInterface } from "node:readline";
38
41
 
39
42
  //#region src/cli/commands/providers.ts
40
43
  /**
@@ -1469,6 +1472,7 @@ main.win .cbody>.tw{padding:6px 10px}.wt-auto>.sp{padding:6px 10px}
1469
1472
  .wc-svg{overflow:hidden}.wc-svg svg{display:block}.wc-leg{overflow:hidden;padding:0 10px}main.win .cbody:has(.wt-svg,.wc-svg){overflow:hidden}
1470
1473
  .wr{padding:6px 10px;min-width:0}.wr-top{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center}.wr-sel{flex:none}.wr .rtcls{font-size:11px}.wr-sess{margin-top:4px}.wr-sess .rtsess{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:center;margin-left:12px}
1471
1474
  .wmd{padding:6px 10px;min-width:0}.wmd-top{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.wmd-g{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:6px 10px;margin-top:6px}.wmdt{font-size:11px;color:var(--dim)}.wmdv{font-size:18px}.wmds{font-size:11px}.wmd-n{margin-top:4px}.wmd-i .linst{margin-top:3px}.wmd-in{font:inherit;min-width:120px}.wmd-e .eprow{padding:2px 0}
1475
+ .wr-row[data-sess]{cursor:pointer}.sd{margin-top:8px;border-top:1px solid var(--bd,rgba(128,128,128,.3));padding-top:6px}.sd-h{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center}.sd-h a{text-decoration:none}.sd-h a.on{font-weight:600;text-decoration:underline}.sd-x{margin-left:auto}
1472
1476
  ${winSelectCss()}
1473
1477
  ${winGridCss()}
1474
1478
  `;
@@ -5147,12 +5151,16 @@ WCHX.speed={
5147
5151
  resize:function(){}
5148
5152
  };
5149
5153
  function wtFit(ctx){fitTables(ctx.body)}
5150
- /** Row-level disclosures: a click on a fold or "+N more" row stores its state and re-feeds the page's data. */
5154
+ /** Row-level disclosures: a click on a fold or "+N more" row stores its state and re-feeds the page's data; a click on a session row opens its detail panel (SESS_OPEN), a tab or close button changes it. */
5151
5155
  function wtWire(ctx){
5152
5156
  ctx.body.addEventListener("click",function(e){
5153
- var t=e.target&&e.target.closest?e.target.closest("[data-wrfold],[data-sessmore]"):null;
5157
+ var t=e.target&&e.target.closest?e.target.closest("[data-wrfold],[data-sessmore],[data-sess],[data-sesstab],[data-sessclose]"):null;
5154
5158
  if(!t||!ctx.body.contains(t))return;
5155
5159
  e.preventDefault();
5160
+ var redraw=function(){Win.update("data",data)};
5161
+ if(t.hasAttribute("data-sess"))return sessDetailOpen(t.getAttribute("data-sess"),redraw);
5162
+ if(t.hasAttribute("data-sesstab")){SESS_TAB=t.getAttribute("data-sesstab");return redraw()}
5163
+ if(t.hasAttribute("data-sessclose")){SESS_OPEN=null;return redraw()}
5156
5164
  if(t.hasAttribute("data-wrfold")){var f=wrFolded(),k=t.getAttribute("data-wrfold");if(f[k])delete f[k];else f[k]=1;try{localStorage.setItem(WR_FOLD_KEY,JSON.stringify(f))}catch(x){}}
5157
5165
  else{try{localStorage.setItem(SESS_MORE_KEY,t.getAttribute("data-sessmore"))}catch(x){}}
5158
5166
  Win.update("data",data);
@@ -5493,7 +5501,7 @@ function runHead(timeLab){
5493
5501
  }
5494
5502
  function runRow(o){
5495
5503
  var pc=o.w>0?Math.max(2,Math.round(o.w/o.max*100)):0;
5496
- return'<div class="wr-row" title="'+esc(o.full)+'"><i class="wr-dot wr-'+o.st+'" title="'+esc(o.dotTitle)+'"></i><span class="wr-lab">'+esc(o.lab)+'</span><span class="badge" title="'+esc((o.models||[]).join(", ")||"no model")+'">'+esc(o.model)+'</span><span class="wr-bar" title="tokens: '+fmt(o.w)+'"><i style="width:'+pc+'%"></i></span><span class="wr-n">'+(o.w?fmt(o.w):"–")+'</span><span class="wr-du">'+o.du+'</span><span class="wr-hit '+hitCls(o.hit)+'" title="cache hit">'+(o.hit==null?"–":pct(o.hit))+'</span><span class="wr-fl">'+(o.extra||"")+'</span></div>'
5504
+ return'<div class="wr-row"'+(o.sid?' data-sess="'+esc(o.sid)+'"':'')+' title="'+esc(o.full)+'"><i class="wr-dot wr-'+o.st+'" title="'+esc(o.dotTitle)+'"></i><span class="wr-lab">'+esc(o.lab)+'</span><span class="badge" title="'+esc((o.models||[]).join(", ")||"no model")+'">'+esc(o.model)+'</span><span class="wr-bar" title="tokens: '+fmt(o.w)+'"><i style="width:'+pc+'%"></i></span><span class="wr-n">'+(o.w?fmt(o.w):"–")+'</span><span class="wr-du">'+o.du+'</span><span class="wr-hit '+hitCls(o.hit)+'" title="cache hit">'+(o.hit==null?"–":pct(o.hit))+'</span><span class="wr-fl">'+(o.extra||"")+'</span></div>'
5497
5505
  }
5498
5506
  /** Sessions by tokens: one runRow per session (dot: error red, worker verdict colour, active in the last 5 min blue, else dim), rows past the top N folded into one expandable row (state in localStorage like folds). */
5499
5507
  var SESS_MORE_KEY="pai.cockpit.sessmore";
@@ -5506,14 +5514,38 @@ function sessionsHtml(d){
5506
5514
  var lab=s.name||s.session.slice(0,8);
5507
5515
  var full=lab+" · "+s.session+(s.worker?" · worker "+s.worker+" · "+(s.verdict||"worker"):"")+"\n"+s.requests+" req · fresh in "+fmt(s.input+s.cacheWrite)+" · out "+fmt(s.output)+" · hit "+pct(s.hitRatio)+" · busts "+(s.busts||0)+" · errors "+(s.errors||0)+" · last "+ago(s.lastTs);
5508
5516
  var ex=(s.busts?'<span class="chip warn" title="cache busts">'+s.busts+'</span>':'')+(s.errors?'<span class="chip bad" title="errors">'+s.errors+'</span>':'');
5509
- return runRow({st:st,lab:lab,full:full,dotTitle:s.verdict||(recent?"active":"idle"),model:ms[0]||"–",models:ms,w:t,max:max,du:ago(s.lastTs),hit:s.requests?s.hitRatio:null,extra:ex});
5517
+ return runRow({sid:s.session,st:st,lab:lab,full:full,dotTitle:s.verdict||(recent?"active":"idle"),model:ms[0]||"–",models:ms,w:t,max:max,du:ago(s.lastTs),hit:s.requests?s.hitRatio:null,extra:ex});
5510
5518
  }
5511
5519
  var shown=open?all:all.slice(0,top),rest=all.slice(top),h=shown.map(row).join("");
5512
5520
  if(rest.length){
5513
5521
  var rt=rest.reduce(function(a,s){return a+tot(s)},0);
5514
5522
  h+='<div class="wr-row wr-span"><a href="#" class="dim" data-sessmore="'+(open?"0":"1")+'">'+(open?"show fewer sessions":"+"+rest.length+" more sessions ("+fmt(rt)+" tokens)")+'</a></div>';
5515
5523
  }
5516
- return'<div class="wr-cq"><div class="wr-tbl">'+runHead("age")+'<div class="wr-group">'+h+'</div></div></div>';
5524
+ return'<div class="wr-cq"><div class="wr-tbl">'+runHead("age")+'<div class="wr-group">'+h+'</div></div></div>'+(typeof sessDetailHtml==="function"?sessDetailHtml():"");
5525
+ }
5526
+ /** Session drill-down state: the opened id, its tab and the fetched detail (survives the poll redraw; the panel is part of the sessions html). */
5527
+ var SESS_OPEN=null,SESS_TAB="timeline",SESS_DET=null;
5528
+ /** One tab of the opened session's detail, drawn with table(). */
5529
+ function sessDetailBody(d,tab){
5530
+ if(tab==="files")return table(["file","edits .n","~lines .n"],d.filesChanged.map(function(f){return[esc(f.path),f.edits,f.approxLinesChanged]}));
5531
+ if(tab==="skills")return table(["skill","time"],d.skills.map(function(k){return[esc(k.name),esc(ago(k.ts))]}));
5532
+ if(tab==="mcp")return table(["server","calls .n","failures .n"],d.mcp.map(function(m){return[esc(m.server),m.calls,m.failures]}));
5533
+ if(tab==="denials")return table(["time","tool","reason"],d.denials.map(function(x){return[esc(ago(x.ts)),esc(x.tool),esc(x.reason)]}));
5534
+ return table(["# .n","time","prompt","tools","edits","cmds .n"],d.turns.map(function(t){return[t.idx+1,esc(ago(t.ts)),esc(t.prompt),esc(t.tools.map(function(x){return x.name+"×"+x.count}).join(", ")),esc(t.edits.join(", ")),t.commands]}));
5535
+ }
5536
+ /** The detail panel under the sessions table: tabs Timeline / Files / Skills / MCP / Denials and a close button; empty when no session is open. */
5537
+ function sessDetailHtml(){
5538
+ if(!SESS_OPEN)return"";
5539
+ var d=SESS_DET&&SESS_DET.id===SESS_OPEN?SESS_DET:null,tabs=[["timeline","Timeline"],["files","Files"],["skills","Skills"],["mcp","MCP"],["denials","Denials"]];
5540
+ var head='<div class="sd-h"><b title="'+esc(SESS_OPEN)+'">'+esc(SESS_OPEN.slice(0,8))+'</b>'+tabs.map(function(t){return'<a href="#" data-sesstab="'+t[0]+'"'+(t[0]===SESS_TAB?' class="on"':'')+'>'+t[1]+(d&&t[0]!=="timeline"?" ("+(t[0]==="files"?d.filesChanged:t[0]==="skills"?d.skills:t[0]==="mcp"?d.mcp:d.denials).length+")":"")+'</a>'}).join("")+'<a href="#" class="dim sd-x" data-sessclose="1" title="close">close ✕</a></div>';
5541
+ var meta=d?'<div class="dim">'+esc(d.cwd)+(d.models.length?" · "+esc(d.models.join(", ")):"")+(d.turnCount>d.turns.length?" · last "+d.turns.length+" of "+d.turnCount+" turns":"")+'</div>':"";
5542
+ return'<div class="sd" data-sd="'+esc(SESS_OPEN)+'">'+head+meta+(d?sessDetailBody(d,SESS_TAB):'<span class="dim">'+(SESS_DET&&SESS_DET.error?esc(SESS_DET.error):"loading…")+'</span>')+'</div>';
5543
+ }
5544
+ /** Opens (or closes, on a second click) a session's detail and fetches it; the panel redraws with the sessions card. */
5545
+ function sessDetailOpen(id,redraw){
5546
+ if(SESS_OPEN===id){SESS_OPEN=null;redraw();return}
5547
+ SESS_OPEN=id;SESS_DET=null;redraw();
5548
+ fetch("/api/session/"+encodeURIComponent(id)+"/detail",{cache:"no-store"}).then(function(r){return r.json().then(function(j){if(!r.ok)throw new Error(j.error||"HTTP "+r.status);return j})}).then(function(j){if(SESS_OPEN===id){SESS_DET=j;redraw()}},function(e){if(SESS_OPEN===id){SESS_DET={id:id,error:String(e.message||e)};redraw()}});
5517
5549
  }
5518
5550
  /** Worker runs grouped under the spawning session: newest run first, groups by their newest run; weighted tokens per run on one scale shared by all rows. */
5519
5551
  function workerGroups(ws,W){
@@ -6491,6 +6523,264 @@ function selectorChoices(workers, endpoints) {
6491
6523
  ];
6492
6524
  }
6493
6525
 
6526
+ //#endregion
6527
+ //#region src/hooks/ts/lib/build-gate.ts
6528
+ /** Tiny reader for the two keys of builds.yaml: flow `[a, b]` or block `- a` lists. */
6529
+ function parseBuildsYaml(text) {
6530
+ const res = {
6531
+ doors: [],
6532
+ patterns: []
6533
+ };
6534
+ let key = null;
6535
+ const unq = (s) => s.trim().replace(/^(['"])(.*)\1$/, "$2");
6536
+ for (const raw of text.split("\n")) {
6537
+ const line = raw.replace(/(^|\s)#.*$/, "");
6538
+ if (!line.trim()) continue;
6539
+ const m = /^(doors|patterns)\s*:\s*(.*)$/.exec(line);
6540
+ if (m) {
6541
+ key = m[1];
6542
+ const rest = m[2].trim();
6543
+ if (rest.startsWith("[")) {
6544
+ res[key].push(...rest.replace(/^\[|\]$/g, "").split(",").map(unq).filter(Boolean));
6545
+ key = null;
6546
+ }
6547
+ continue;
6548
+ }
6549
+ const li = /^\s*-\s+(.*)$/.exec(line);
6550
+ if (li && key) res[key].push(unq(li[1]));
6551
+ else if (!/^\s/.test(line)) key = null;
6552
+ }
6553
+ return res;
6554
+ }
6555
+ /** The nearest `.pai/builds.yaml` at or above `cwd`. */
6556
+ function loadBuildsConfig(cwd) {
6557
+ let dir = resolve(cwd);
6558
+ for (;;) {
6559
+ const f = join(dir, ".pai", "builds.yaml");
6560
+ if (existsSync(f)) try {
6561
+ return {
6562
+ root: dir,
6563
+ ...parseBuildsYaml(readFileSync(f, "utf8"))
6564
+ };
6565
+ } catch {
6566
+ return null;
6567
+ }
6568
+ const up = dirname(dir);
6569
+ if (up === dir) return null;
6570
+ dir = up;
6571
+ }
6572
+ }
6573
+
6574
+ //#endregion
6575
+ //#region src/hooks/ts/lib/loop-guard.ts
6576
+ const LOOP_MAX_REPEATS = 4;
6577
+ /** Consecutive same-template read-only Bash calls without a worktree change after which the next one is denied (env PAI_LOOP_READ_STREAK overrides). */
6578
+ const LOOP_READ_STREAK = Number(process.env.PAI_LOOP_READ_STREAK) > 0 ? Number(process.env.PAI_LOOP_READ_STREAK) : 15;
6579
+ const SELF = "If your part is done, commit and write your R report. If you are stuck, report the blocker in R x= (or use pai worker help if available).";
6580
+ const LOOP_DENY_REASON = `loop guard: you ran this command ${LOOP_MAX_REPEATS} times with the same result and nothing changed. Stop repeating it. ${SELF}`;
6581
+ /** Total polling time per command key after which a "still running" helper is treated as an ordinary repeat again. */
6582
+ const WAIT_CAP_MS = 45 * 6e4;
6583
+ /** Untracked files up to this size are hashed by content; larger ones by size + mtime. */
6584
+ const FP_FULL_READ_BYTES = 256 * 1024;
6585
+ /** Content bytes read per fingerprint across all untracked files; past it files are hashed by size + mtime. */
6586
+ const FP_TOTAL_BYTES = 4 * 1024 * 1024;
6587
+ const SECRET_LINE = /pass(?:word|wd)|secret|token|api[_-]?key|credential|authorization|bearer|private key|^\s*(?:export\s+)?[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASS\w*|CRED\w*|AUTH\w*)\s*=|\b(?:sk|ghp|gho|xox[bap])[-_][A-Za-z0-9_-]{16,}|\b[A-Za-z0-9+]{40,}={0,2}(?![\w/])|\b[a-z][a-z0-9+.-]*:\/\/[^\s\/:@]+:[^\s\/@]+@|\b(?:AKIA|ASIA|AGPA|AIDA|AROA)[A-Z0-9]{16}\b|\bgh[pousr]_[A-Za-z0-9]{20,}/i;
6588
+ const MASKED_LINE = "[line masked: looks like a secret]";
6589
+ /** Result text with lines that look like secrets replaced by a marker. */
6590
+ function maskSecrets(text) {
6591
+ return text.split("\n").map((l) => SECRET_LINE.test(l) ? MASKED_LINE : l).join("\n");
6592
+ }
6593
+
6594
+ //#endregion
6595
+ //#region src/router/session-detail.ts
6596
+ /**
6597
+ * session-detail.ts — one session's drill-down for the cockpit: turns, files
6598
+ * changed, skills, MCP calls and hook denials, read from the Claude Code
6599
+ * transcript (<projects dir>/<encoded cwd>/<session id>.jsonl) by streaming it
6600
+ * line by line. Prompt text is secret-masked.
6601
+ */
6602
+ /** Session ids the endpoint accepts: no path separators, no dots. */
6603
+ const SESSION_ID_RE = /^[A-Za-z0-9-]{8,64}$/;
6604
+ const PROMPT_CHARS = 300;
6605
+ /** The transcript file of a session id, or null. */
6606
+ function findTranscript(id, projectsDir = claudeProjectsDir()) {
6607
+ if (!SESSION_ID_RE.test(id) || !existsSync(projectsDir)) return null;
6608
+ let dirs = [];
6609
+ try {
6610
+ dirs = readdirSync(projectsDir);
6611
+ } catch {
6612
+ return null;
6613
+ }
6614
+ for (const d of dirs) {
6615
+ const f = join(projectsDir, d, `${id}.jsonl`);
6616
+ if (existsSync(f)) return f;
6617
+ }
6618
+ return null;
6619
+ }
6620
+ const lineCount = (s) => typeof s === "string" && s ? s.split("\n").length : 0;
6621
+ /** Text of a tool_result's content: a string or the text blocks joined. */
6622
+ function resultText(c) {
6623
+ if (typeof c === "string") return c;
6624
+ if (Array.isArray(c)) return c.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n");
6625
+ return "";
6626
+ }
6627
+ /** Lines written by one Edit / Write / MultiEdit input. */
6628
+ function changedLines(name, input) {
6629
+ if (name === "Write") return lineCount(input.content);
6630
+ if (name === "Edit") return lineCount(input.new_string);
6631
+ if (name === "MultiEdit" && Array.isArray(input.edits)) return input.edits.reduce((a, e) => a + lineCount(e?.new_string), 0);
6632
+ return 0;
6633
+ }
6634
+ /** Skill name a user line announces ("/name" command tag or "Launching skill: name"), or null. */
6635
+ function loadedSkill(text) {
6636
+ const cmd = text.match(/<command-name>\/?([^<\s]+)<\/command-name>/);
6637
+ if (cmd) return cmd[1];
6638
+ const l = text.match(/^Launching skill: (\S+)/);
6639
+ return l ? l[1] : null;
6640
+ }
6641
+ /** Streams the session's transcript into a SessionDetail; null when the id has no transcript. */
6642
+ async function sessionDetail(id, opts = {}) {
6643
+ const file = findTranscript(id, opts.projectsDir);
6644
+ if (!file) return null;
6645
+ const maxTurns = Math.max(1, opts.maxTurns ?? cockpitLayout().sessionDetailTurns);
6646
+ const out = {
6647
+ id,
6648
+ cwd: "",
6649
+ models: [],
6650
+ turns: [],
6651
+ turnCount: 0,
6652
+ filesChanged: [],
6653
+ skills: [],
6654
+ mcp: [],
6655
+ denials: []
6656
+ };
6657
+ const models = /* @__PURE__ */ new Set();
6658
+ const files = /* @__PURE__ */ new Map();
6659
+ const mcp = /* @__PURE__ */ new Map();
6660
+ const toolOf = /* @__PURE__ */ new Map();
6661
+ const mcpOf = /* @__PURE__ */ new Map();
6662
+ let cur = null;
6663
+ const close = () => {
6664
+ if (!cur) return;
6665
+ cur.turn.tools = [...cur.tools].map(([name, count]) => ({
6666
+ name,
6667
+ count
6668
+ }));
6669
+ cur.turn.edits = [...cur.edits];
6670
+ out.turns.push(cur.turn);
6671
+ if (out.turns.length > maxTurns) out.turns.shift();
6672
+ cur = null;
6673
+ };
6674
+ const rl = createInterface({
6675
+ input: createReadStream(file, { encoding: "utf8" }),
6676
+ crlfDelay: Infinity
6677
+ });
6678
+ for await (const raw of rl) {
6679
+ if (!raw) continue;
6680
+ let obj;
6681
+ try {
6682
+ obj = JSON.parse(raw);
6683
+ } catch {
6684
+ continue;
6685
+ }
6686
+ const ts = obj.timestamp ?? "";
6687
+ if (!out.cwd && typeof obj.cwd === "string") out.cwd = obj.cwd;
6688
+ const content = obj.message?.content;
6689
+ if (obj.type === "user") {
6690
+ if (isRealUserPrompt(obj)) {
6691
+ close();
6692
+ const text = extractUserPromptText(obj);
6693
+ const skill = loadedSkill(text);
6694
+ if (skill) out.skills.push({
6695
+ name: skill,
6696
+ ts
6697
+ });
6698
+ cur = {
6699
+ turn: {
6700
+ idx: out.turnCount++,
6701
+ ts,
6702
+ prompt: maskSecrets(text).slice(0, PROMPT_CHARS),
6703
+ tools: [],
6704
+ edits: [],
6705
+ commands: 0
6706
+ },
6707
+ tools: /* @__PURE__ */ new Map(),
6708
+ edits: /* @__PURE__ */ new Set()
6709
+ };
6710
+ continue;
6711
+ }
6712
+ const blocks = Array.isArray(content) ? content : [];
6713
+ for (const b of blocks) {
6714
+ if (b.type === "text" && typeof b.text === "string" && obj.isMeta) {
6715
+ const skill = loadedSkill(b.text);
6716
+ if (skill) out.skills.push({
6717
+ name: skill,
6718
+ ts
6719
+ });
6720
+ }
6721
+ if (b.type !== "tool_result") continue;
6722
+ const text = resultText(b.content);
6723
+ const server = b.tool_use_id ? mcpOf.get(b.tool_use_id) : void 0;
6724
+ if (server && b.is_error) mcp.get(server).failures++;
6725
+ const d = parseDenialText(text, b.tool_use_id && toolOf.get(b.tool_use_id) || "?");
6726
+ if (d) out.denials.push({
6727
+ ts,
6728
+ tool: d.tool,
6729
+ reason: maskSecrets(d.reason).slice(0, 200)
6730
+ });
6731
+ }
6732
+ continue;
6733
+ }
6734
+ if (obj.type !== "assistant") continue;
6735
+ if (obj.message?.model) models.add(obj.message.model);
6736
+ if (!Array.isArray(content)) continue;
6737
+ for (const b of content) {
6738
+ if (b.type !== "tool_use" || !b.name) continue;
6739
+ const input = b.input ?? {};
6740
+ if (b.id) toolOf.set(b.id, b.name);
6741
+ if (cur) cur.tools.set(b.name, (cur.tools.get(b.name) ?? 0) + 1);
6742
+ if (b.name === "Bash" && cur) cur.turn.commands++;
6743
+ if (b.name === "Skill" && typeof input.skill === "string") out.skills.push({
6744
+ name: input.skill,
6745
+ ts
6746
+ });
6747
+ if (b.name.startsWith("mcp__")) {
6748
+ const server = b.name.split("__")[1];
6749
+ if (server) {
6750
+ const m = mcp.get(server) ?? {
6751
+ calls: 0,
6752
+ failures: 0
6753
+ };
6754
+ m.calls++;
6755
+ mcp.set(server, m);
6756
+ if (b.id) mcpOf.set(b.id, server);
6757
+ }
6758
+ }
6759
+ if ((b.name === "Edit" || b.name === "Write" || b.name === "MultiEdit") && typeof input.file_path === "string") {
6760
+ const f = files.get(input.file_path) ?? {
6761
+ edits: 0,
6762
+ approxLinesChanged: 0
6763
+ };
6764
+ f.edits++;
6765
+ f.approxLinesChanged += changedLines(b.name, input);
6766
+ files.set(input.file_path, f);
6767
+ cur?.edits.add(input.file_path);
6768
+ }
6769
+ }
6770
+ }
6771
+ close();
6772
+ out.models = [...models];
6773
+ out.filesChanged = [...files].map(([path, f]) => ({
6774
+ path,
6775
+ ...f
6776
+ })).sort((a, b) => b.approxLinesChanged - a.approxLinesChanged);
6777
+ out.mcp = [...mcp].map(([server, m]) => ({
6778
+ server,
6779
+ ...m
6780
+ })).sort((a, b) => b.calls - a.calls);
6781
+ return out;
6782
+ }
6783
+
6494
6784
  //#endregion
6495
6785
  //#region src/router/cockpit-layout-default.ts
6496
6786
  /**
@@ -7855,7 +8145,8 @@ function cockpitBuildId() {
7855
8145
  */
7856
8146
  function handleCockpit(req, res, limits, readRows = cachedRows(), sessionMap = loadSessionMap, system, workerWindows = loadWorkerWindows, readPool = cachedPoolCarry(), readPoolPanel = sharedPoolPanel, queueLogDir = workersLogDir) {
7857
8147
  const url = new URL(req.url ?? "/", "http://localhost");
7858
- if (url.pathname !== "/cockpit" && !url.pathname.startsWith("/cockpit/")) return false;
8148
+ const detail = url.pathname.match(/^\/api\/session\/([^/]*)\/detail$/);
8149
+ if (!detail && url.pathname !== "/cockpit" && !url.pathname.startsWith("/cockpit/")) return false;
7859
8150
  const send = (status, type, body) => {
7860
8151
  res.writeHead(status, {
7861
8152
  "content-type": type,
@@ -7864,6 +8155,12 @@ function handleCockpit(req, res, limits, readRows = cachedRows(), sessionMap = l
7864
8155
  });
7865
8156
  res.end(body);
7866
8157
  };
8158
+ if (detail) {
8159
+ if (req.method !== "GET") return send(405, "text/plain", "GET only"), true;
8160
+ if (!SESSION_ID_RE.test(detail[1])) return send(400, "application/json", "{\"error\":\"invalid session id\"}"), true;
8161
+ sessionDetail(detail[1]).then((d) => d ? send(200, "application/json", JSON.stringify(d)) : send(404, "application/json", "{\"error\":\"no such session\"}"), (e) => send(500, "application/json", JSON.stringify({ error: e.message })));
8162
+ return true;
8163
+ }
7867
8164
  if (req.method === "POST" && (url.pathname === "/cockpit/queue/next" || url.pathname === "/cockpit/queue/front" || url.pathname === "/cockpit/queue/up" || url.pathname === "/cockpit/queue/move")) {
7868
8165
  if (!sameOriginAction(req)) return send(403, "application/json", JSON.stringify({
7869
8166
  ok: false,
@@ -10271,7 +10568,7 @@ async function stopInstance(cfg, target, opts = {}, deps = {}) {
10271
10568
  */
10272
10569
  async function cliInflight(cfg, deps = {}) {
10273
10570
  const [{ engineInflight }, { readSchedFile }, { instanceEngineKey }] = await Promise.all([
10274
- import("./cockpit-actions-DR9X514g.mjs"),
10571
+ import("./cockpit-actions-BfRMr2sm.mjs"),
10275
10572
  import("./scheduler-dmHXoRvm.mjs"),
10276
10573
  import("./local-instances-ZeRkkuS5.mjs")
10277
10574
  ]);
@@ -10867,5 +11164,5 @@ const WORKER_CONFIG_COMMANDS = [
10867
11164
  ];
10868
11165
 
10869
11166
  //#endregion
10870
- export { exportDefaultLayout as $, workerMcpCommand as A, mergeSettings as B, workerBenchCommand as C, workerFallbackCommand as D, workerControlsCommand as E, workerProvidersCommand as F, cachedRows as G, canBind as H, workerProxyCommand as I, handleCockpit as J, cachedSessionMap as K, workerRolesCommand as L, workerOnCommand as M, workerPolicyCommand as N, workerInstallCommand as O, workerPoolCommand as P, warmCockpit as Q, workerRouteCommand as R, stopInstance as S, workerConfigCommand as T, sizeOnDiskGb as U, allocatePort as V, SystemMonitor as W, setEndpointHealth as X, setActionHooks as Y, setRuntimeSampler as Z, portOpen as _, collectEnv as a, defaultRegistryCtx as at, startTier as b, currentInstances as c, providersCommand as ct, isUp as d, createEndpointHealth as et, killInstance as f, portListeners as g, parseCostTier as h, cliInflight as i, withWorkerAttribution as it, workerOffCommand as j, workerLocalCommand as k, fail as l, listServers as m, WORKER_CONFIG_COMMANDS as n, loadWorkerWindows as nt, collectTags as o, hashText as ot, launchDetached as p, cachedWorkerWindows as q, buildClassTarget as r, sessionDisplayName as rt, controlsArgv as s, saveRegistry as st, PROVIDER_EDIT_COMMANDS as t, loadSessionMap as tt, healthOk as u, serverLine as v, workerClassesCommand as w, stopAll as x, startInstance as y, installWorkers as z };
10871
- //# sourceMappingURL=worker-config-CJhEMk19.mjs.map
11167
+ export { exportDefaultLayout as $, workerMcpCommand as A, mergeSettings as B, workerBenchCommand as C, workerFallbackCommand as D, workerControlsCommand as E, workerProvidersCommand as F, cachedRows as G, canBind as H, workerProxyCommand as I, handleCockpit as J, cachedSessionMap as K, workerRolesCommand as L, workerOnCommand as M, workerPolicyCommand as N, workerInstallCommand as O, workerPoolCommand as P, warmCockpit as Q, workerRouteCommand as R, stopInstance as S, workerConfigCommand as T, sizeOnDiskGb as U, allocatePort as V, SystemMonitor as W, setEndpointHealth as X, setActionHooks as Y, setRuntimeSampler as Z, portOpen as _, collectEnv as a, withWorkerAttribution as at, startTier as b, currentInstances as c, saveRegistry as ct, isUp as d, loadBuildsConfig as et, killInstance as f, portListeners as g, parseCostTier as h, cliInflight as i, sessionDisplayName as it, workerOffCommand as j, workerLocalCommand as k, fail as l, providersCommand as lt, listServers as m, WORKER_CONFIG_COMMANDS as n, loadSessionMap as nt, collectTags as o, defaultRegistryCtx as ot, launchDetached as p, cachedWorkerWindows as q, buildClassTarget as r, loadWorkerWindows as rt, controlsArgv as s, hashText as st, PROVIDER_EDIT_COMMANDS as t, createEndpointHealth as tt, healthOk as u, serverLine as v, workerClassesCommand as w, stopAll as x, startInstance as y, installWorkers as z };
11168
+ //# sourceMappingURL=worker-config-eY5Qy4vl.mjs.map
@@ -1213,4 +1213,4 @@ async function zettelCommunities(backend, opts) {
1213
1213
 
1214
1214
  //#endregion
1215
1215
  export { zettelThemes as a, zettelExplore as c, zettelHealth as i, zettelGodNotes as n, zettelConverse as o, zettelSuggest as r, zettelSurprise as s, zettelCommunities as t };
1216
- //# sourceMappingURL=zettelkasten-BzMm8REH.mjs.map
1216
+ //# sourceMappingURL=zettelkasten-_h_tUm2T.mjs.map
@@ -1,7 +1,7 @@
1
1
  import "./config-Bu1_vbY8.mjs";
2
2
  import "./embedding-gate-6rZrWRXa.mjs";
3
3
  import "./embeddings-BVq3esBF.mjs";
4
- import { r as saveQueryResult } from "./query-feedback-BRfPtqki.mjs";
5
- import { a as zettelThemes, c as zettelExplore, i as zettelHealth, n as zettelGodNotes, o as zettelConverse, r as zettelSuggest, s as zettelSurprise, t as zettelCommunities } from "./zettelkasten-BzMm8REH.mjs";
4
+ import { r as saveQueryResult } from "./query-feedback-DN52N9C5.mjs";
5
+ import { a as zettelThemes, c as zettelExplore, i as zettelHealth, n as zettelGodNotes, o as zettelConverse, r as zettelSuggest, s as zettelSurprise, t as zettelCommunities } from "./zettelkasten-_h_tUm2T.mjs";
6
6
 
7
7
  export { zettelConverse, zettelExplore, zettelHealth, zettelSuggest, zettelSurprise, zettelThemes };
package/docs/cockpit.md CHANGED
@@ -169,6 +169,7 @@ An empty list shows every section. Unknown ids are ignored, and if all are unkno
169
169
  | `dailyDays` | 14 | Days in Tokens per day per model |
170
170
  | `sessionsRows` | 20 | Rows in Sessions by tokens |
171
171
  | `sessionsTopRows` | 10 | Sessions shown before the rest collapse into one "+N more sessions" row |
172
+ | `sessionDetailTurns` | 200 | Turns kept in the per-session drill-down (click a session row) |
172
173
  | `sessionNameMaxPct` | 35 | Session-name column cap, percent of the table width |
173
174
  | `workerRows` | 15 | Rows in Worker runs |
174
175
  | `lowRequestMin` | 5 | Models under this many requests become chips, not rings |
@@ -2,7 +2,7 @@
2
2
  "name": "@tekmidian/pai",
3
3
  "displayName": "PAI Knowledge OS",
4
4
  "description": "Personal AI Infrastructure — persistent memory and session continuity for AI coding assistants",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "author": "Matthias Nott",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/mnott/PAI",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekmidian/pai",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "PAI Knowledge OS — Personal AI Infrastructure with federated memory and project management",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
package/pai-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "@tekmidian/pai",
4
4
  "displayName": "PAI Knowledge OS",
5
5
  "description": "Personal AI Infrastructure — persistent memory, session continuity, and knowledge graph",
6
- "version": "1.0.3",
6
+ "version": "1.0.4",
7
7
  "author": "Matthias Nott",
8
8
  "license": "MIT",
9
9
  "homepage": "https://github.com/mnott/PAI",
@@ -2,6 +2,6 @@
2
2
  "name": "pai-context-preservation",
3
3
  "displayName": "PAI Context Preservation",
4
4
  "description": "Context compression and relay across compaction cycles",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "hooks": "./hooks/hooks.json"
7
7
  }
@@ -2,6 +2,6 @@
2
2
  "name": "pai-core",
3
3
  "displayName": "PAI Core",
4
4
  "description": "Core memory engine, session management, and project registry",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "hooks": "./hooks/hooks.json"
7
7
  }
@@ -2,5 +2,5 @@
2
2
  "name": "pai-creative",
3
3
  "displayName": "PAI Creative Studio",
4
4
  "description": "Art direction, story explanation, voice/prosody configuration",
5
- "version": "1.0.3"
5
+ "version": "1.0.4"
6
6
  }
@@ -2,6 +2,6 @@
2
2
  "name": "pai-observability",
3
3
  "displayName": "PAI Observability Suite",
4
4
  "description": "Automatic observation capture, classification, session summaries, and search history",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "hooks": "./hooks/hooks.json"
7
7
  }
@@ -2,7 +2,7 @@
2
2
  "name": "pai-productivity",
3
3
  "displayName": "PAI Productivity",
4
4
  "description": "Planning, journaling, reviewing, research, sharing, and cross-session task routing",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "hooks": "./hooks/hooks.json",
7
7
  "skills": ["./skills/"]
8
8
  }
@@ -2,5 +2,5 @@
2
2
  "name": "pai-semantic-search",
3
3
  "displayName": "PAI Semantic Search",
4
4
  "description": "Vector search with pgvector, cross-encoder reranking, hybrid mode, recency boost",
5
- "version": "1.0.3"
5
+ "version": "1.0.4"
6
6
  }
@@ -2,6 +2,6 @@
2
2
  "name": "pai-ui",
3
3
  "displayName": "PAI UI Customization",
4
4
  "description": "Terminal tab titles, statusline, tab coloring, and visual customization",
5
- "version": "1.0.3",
5
+ "version": "1.0.4",
6
6
  "hooks": "./hooks/hooks.json"
7
7
  }
@@ -2,5 +2,5 @@
2
2
  "name": "pai-zettelkasten",
3
3
  "displayName": "PAI Zettelkasten Intelligence",
4
4
  "description": "Luhmann-inspired vault intelligence with graph operations and Obsidian integration",
5
- "version": "1.0.3"
5
+ "version": "1.0.4"
6
6
  }
@@ -11,7 +11,7 @@ import { join } from 'path';
11
11
  import { historyDir, agentSessionsPath } from './lib/pai-paths.js';
12
12
  import { enrichEventWithAgentMetadata, isAgentSpawningCall } from './lib/metadata-extraction.js';
13
13
  import { isProbeSession } from './lib/project-utils.js';
14
- import { appendRawCapture, redactDeep } from './lib/raw-capture.js';
14
+ import { appendRawCapture, captureEventId, redactDeep } from './lib/raw-capture.js';
15
15
  import { writeJsonAtomic } from '../../config/json-store.js';
16
16
 
17
17
  interface HookEvent {
@@ -21,6 +21,9 @@ interface HookEvent {
21
21
  payload: Record<string, any>;
22
22
  timestamp: number;
23
23
  timestamp_local: string;
24
+ source: string;
25
+ fidelity: string;
26
+ eventId: string;
24
27
  }
25
28
 
26
29
  // Get local timestamp using system timezone
@@ -154,13 +157,22 @@ async function main() {
154
157
  }
155
158
 
156
159
  // Create base event object
160
+ const timestamp = Date.now();
157
161
  let event: HookEvent = {
158
162
  source_app: agentName,
159
163
  session_id: hookData.session_id || 'main',
160
164
  hook_event_type: eventType,
161
165
  payload: redactDeep(hookData),
162
- timestamp: Date.now(),
163
- timestamp_local: getLocalTimestamp()
166
+ timestamp,
167
+ timestamp_local: getLocalTimestamp(),
168
+ source: 'hook',
169
+ fidelity: 'observed',
170
+ eventId: captureEventId({
171
+ sessionId: hookData.session_id,
172
+ toolUseId: hookData.tool_use_id,
173
+ event: eventType,
174
+ ts: String(timestamp)
175
+ })
164
176
  };
165
177
 
166
178
  // Enrich with agent instance metadata if this is a Task tool call
@@ -5,9 +5,16 @@
5
5
 
6
6
  import { appendFileSync, chmodSync, existsSync, mkdirSync, renameSync, statSync } from 'fs';
7
7
  import { maskSecrets } from './loop-guard.js';
8
+ import { sha256 } from '../../../utils/hash.js';
8
9
 
9
10
  export const RAW_CAPTURE_MAX_BYTES = 10 * 1024 * 1024;
10
11
 
12
+ /** Deterministic 16-hex id for a captured event: sha256 of the joined parts. */
13
+ export function captureEventId(parts: { sessionId?: string; toolUseId?: string; event?: string; ts?: string }): string {
14
+ const joined = `${parts.sessionId ?? ""}|${parts.toolUseId ?? ""}|${parts.event ?? ""}|${parts.ts ?? ""}`;
15
+ return sha256(joined).slice(0, 16);
16
+ }
17
+
11
18
  /** Deep copy with every string value passed through the loop guard's secret-line masking. */
12
19
  export function redactDeep<T>(value: T): T {
13
20
  if (typeof value === 'string') return maskSecrets(value) as unknown as T;