shennian 0.3.48 → 0.3.50
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.
- package/dist/publish-build-manifest.json +28 -23
- package/dist/src/commands/daemon.js +5 -5
- package/dist/src/commands/pair-browser.d.ts +1 -0
- package/dist/src/commands/pair-browser.js +1 -1
- package/dist/src/commands/pair.js +11 -11
- package/dist/src/config/index.d.ts +1 -0
- package/dist/src/index.js +3 -3
- package/dist/src/manager/registry.js +1 -2
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/personal-sync/account-namespace.d.ts +6 -0
- package/dist/src/personal-sync/account-namespace.js +1 -0
- package/dist/src/personal-sync/identity.js +1 -1
- package/dist/src/personal-sync/paths.d.ts +6 -0
- package/dist/src/personal-sync/paths.js +1 -1
- package/dist/src/room/device-authorization-client.d.ts +2 -0
- package/dist/src/session/canonical-store.js +4 -4
- package/dist/src/session/queue.js +4 -4
- package/dist/src/session/store.js +2 -2
- package/node_modules/@shennian/wire/dist/message-payload.d.ts +9 -0
- package/node_modules/@shennian/wire/dist/message-payload.js +53 -0
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +1 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"jsMinified": true,
|
|
4
4
|
"minifier": "esbuild",
|
|
5
|
-
"fileCount":
|
|
5
|
+
"fileCount": 125,
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"file": "bin/shennian.js",
|
|
@@ -156,8 +156,8 @@
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
"file": "src/commands/daemon.js",
|
|
159
|
-
"beforeBytes":
|
|
160
|
-
"afterBytes":
|
|
159
|
+
"beforeBytes": 39845,
|
|
160
|
+
"afterBytes": 19471
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"file": "src/commands/integration.js",
|
|
@@ -176,8 +176,8 @@
|
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
"file": "src/commands/pair-browser.js",
|
|
179
|
-
"beforeBytes":
|
|
180
|
-
"afterBytes":
|
|
179
|
+
"beforeBytes": 6655,
|
|
180
|
+
"afterBytes": 3699
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"file": "src/commands/pair-qr.js",
|
|
@@ -186,8 +186,8 @@
|
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"file": "src/commands/pair.js",
|
|
189
|
-
"beforeBytes":
|
|
190
|
-
"afterBytes":
|
|
189
|
+
"beforeBytes": 15955,
|
|
190
|
+
"afterBytes": 8988
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"file": "src/commands/room.js",
|
|
@@ -276,8 +276,8 @@
|
|
|
276
276
|
},
|
|
277
277
|
{
|
|
278
278
|
"file": "src/index.js",
|
|
279
|
-
"beforeBytes":
|
|
280
|
-
"afterBytes":
|
|
279
|
+
"beforeBytes": 22986,
|
|
280
|
+
"afterBytes": 11906
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
"file": "src/log-reporter.js",
|
|
@@ -291,8 +291,8 @@
|
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
"file": "src/manager/registry.js",
|
|
294
|
-
"beforeBytes":
|
|
295
|
-
"afterBytes":
|
|
294
|
+
"beforeBytes": 13737,
|
|
295
|
+
"afterBytes": 6649
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"file": "src/manager/retired-external-channel-compat.js",
|
|
@@ -321,8 +321,8 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"file": "src/native-fusion/local-repository.js",
|
|
324
|
-
"beforeBytes":
|
|
325
|
-
"afterBytes":
|
|
324
|
+
"beforeBytes": 20835,
|
|
325
|
+
"afterBytes": 10716
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -364,15 +364,20 @@
|
|
|
364
364
|
"beforeBytes": 54,
|
|
365
365
|
"afterBytes": 60
|
|
366
366
|
},
|
|
367
|
+
{
|
|
368
|
+
"file": "src/personal-sync/account-namespace.js",
|
|
369
|
+
"beforeBytes": 1412,
|
|
370
|
+
"afterBytes": 879
|
|
371
|
+
},
|
|
367
372
|
{
|
|
368
373
|
"file": "src/personal-sync/identity.js",
|
|
369
|
-
"beforeBytes":
|
|
370
|
-
"afterBytes":
|
|
374
|
+
"beforeBytes": 1928,
|
|
375
|
+
"afterBytes": 1024
|
|
371
376
|
},
|
|
372
377
|
{
|
|
373
378
|
"file": "src/personal-sync/paths.js",
|
|
374
|
-
"beforeBytes":
|
|
375
|
-
"afterBytes":
|
|
379
|
+
"beforeBytes": 1345,
|
|
380
|
+
"afterBytes": 830
|
|
376
381
|
},
|
|
377
382
|
{
|
|
378
383
|
"file": "src/region.js",
|
|
@@ -496,8 +501,8 @@
|
|
|
496
501
|
},
|
|
497
502
|
{
|
|
498
503
|
"file": "src/session/canonical-store.js",
|
|
499
|
-
"beforeBytes":
|
|
500
|
-
"afterBytes":
|
|
504
|
+
"beforeBytes": 29295,
|
|
505
|
+
"afterBytes": 14384
|
|
501
506
|
},
|
|
502
507
|
{
|
|
503
508
|
"file": "src/session/deletion-sync-state.js",
|
|
@@ -586,13 +591,13 @@
|
|
|
586
591
|
},
|
|
587
592
|
{
|
|
588
593
|
"file": "src/session/queue.js",
|
|
589
|
-
"beforeBytes":
|
|
590
|
-
"afterBytes":
|
|
594
|
+
"beforeBytes": 17581,
|
|
595
|
+
"afterBytes": 8290
|
|
591
596
|
},
|
|
592
597
|
{
|
|
593
598
|
"file": "src/session/store.js",
|
|
594
|
-
"beforeBytes":
|
|
595
|
-
"afterBytes":
|
|
599
|
+
"beforeBytes": 13716,
|
|
600
|
+
"afterBytes": 7553
|
|
596
601
|
},
|
|
597
602
|
{
|
|
598
603
|
"file": "src/session/tool-detail-store.js",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import c from"chalk";import i from"node:fs";import s from"node:path";import u from"node:os";import{execSync as a,spawn as de}from"node:child_process";import{randomUUID as le}from"node:crypto";import{fileURLToPath as pe}from"node:url";import{getShennianDir as me,loadConfig as j,resolveShennianPath as h,saveConfig as fe}from"../config/index.js";import{buildAugmentedPath as Se}from"../env-path.js";import{buildWindowsLauncherCommand as he,buildWindowsScheduledTaskCommands as ge,buildWindowsScheduledTaskXml as we,buildWindowsStartupVbs as ye,escapeXml as T,isWindowsCmdScript as ve,quoteCmdArg as W,quoteSystemdArg as Ee,suspendWindowsScheduledTask as
|
|
2
|
-
`)}function Y(e){const n=e?.trim();if(!n)return;const t=j();t.serverUrl=n,fe(t)}function z(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const _="com.shennian.agent",p=s.join(u.homedir(),"Library/LaunchAgents",`${_}.plist`),Me=s.join(u.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),
|
|
1
|
+
import c from"chalk";import i from"node:fs";import s from"node:path";import u from"node:os";import{execSync as a,spawn as de}from"node:child_process";import{randomUUID as le}from"node:crypto";import{fileURLToPath as pe}from"node:url";import{getShennianDir as me,loadConfig as j,resolveShennianPath as h,saveConfig as fe}from"../config/index.js";import{buildAugmentedPath as Se}from"../env-path.js";import{buildWindowsLauncherCommand as he,buildWindowsScheduledTaskCommands as ge,buildWindowsScheduledTaskXml as we,buildWindowsStartupVbs as ye,escapeXml as T,isWindowsCmdScript as ve,quoteCmdArg as W,quoteSystemdArg as Ee,suspendWindowsScheduledTask as Ae}from"./daemon-windows.js";import{waitForDaemonReadyStatus as L}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Pe,isShennianRunServiceCommand as Ie}from"./daemon-process.js";import{getCurrentVersion as F}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as Pn,buildWindowsScheduledTaskCommands as In,buildWindowsScheduledTaskSuspendCommands as kn,buildWindowsScheduledTaskXml as Nn,buildWindowsStartupVbs as Dn,suspendWindowsScheduledTask as bn}from"./daemon-windows.js";import{isShennianRunServiceCommand as Tn}from"./daemon-process.js";const ke=s.dirname(pe(import.meta.url)),S=me(),g=h("daemon.pid"),d=h("daemon.log"),x=h("remote-access.disabled"),N=h("daemon-launcher.json"),B=s.resolve(ke,"../../bin/shennian.js"),V=process.execPath,J=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_ACCOUNT_NAMESPACE_ID","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function Ne(e){return J.has(e)}function l(){const e=u.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function O(e){const n=e.replace(/\\/g,"/").toLowerCase(),t=u.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(t.endsWith("/")?t:`${t}/`)}function De(e){return i.existsSync(e.scriptPath)&&!O(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!O(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function be(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const r=JSON.parse(n),o=Number(r.pid);return!Number.isInteger(o)||o<=0?null:{pid:o,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(n,10);return isNaN(t)?null:{pid:t}}function A(){try{return be(i.readFileSync(g,"utf-8"))}catch{return null}}function E(e){try{return process.kill(e,0),!0}catch{return!1}}function G(e,n=3e3){const t=Date.now();for(;Date.now()-t<n;){if(!E(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!E(e)}function Re(){return i.existsSync(x)}function Te(){try{i.unlinkSync(x)}catch{}}function K(){return"global-cli"}function Le(){return`${process.pid}-${Date.now()}-${le()}`}function xe(e,n,t={}){i.mkdirSync(S,{recursive:!0}),i.writeFileSync(g,JSON.stringify({pid:e,instanceId:n,...t.version?{version:t.version}:{},launcher:t.launcher??K(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function yn(e,n){const t=A();if(!(t?.pid!==e||t.instanceId!==n))try{i.unlinkSync(g)}catch{}}function X(e,n=K(),t){i.mkdirSync(S,{recursive:!0}),i.writeFileSync(N,JSON.stringify({pid:e,launcher:n,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function Oe(e){if(e)try{if(JSON.parse(i.readFileSync(N,"utf-8")).instanceId!==e)return}catch{return}try{i.unlinkSync(N)}catch{}}function He(e){e&&X(e)}function _e(e,n){if(!e||!n)return"unknown";try{const t=JSON.parse(i.readFileSync(N,"utf-8"));if(t.pid!==e)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return H(e)}function H(e){if(l()==="win32")return"unknown";try{const n=a(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function q(e=process.pid){if(l()==="win32")return Pe(e);try{return a("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const o=Number(r[1]),f=r[2];return!Number.isInteger(o)||o===e?null:Ie(f)?o:null}).filter(t=>typeof t=="number")}catch{return[]}}function $e(e=process.env,n=S,t=u.tmpdir(),r=s.join(u.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||s.resolve(n)===s.resolve(r))return!1;const o=s.relative(s.resolve(t),s.resolve(n));return o!==""&&o!==".."&&!o.startsWith(`..${s.sep}`)&&!s.isAbsolute(o)||s.resolve(n)===s.resolve(e.SHENNIAN_HOME)}function Ce(e=process.env,n=S){return!$e(e,n)}function m(e={}){let n=A(),t=n?.pid??null,r=t!==null&&E(t),o=t!==null&&!r,f=n?.adopted===!0;const v=j();if(o&&e.cleanupStale){try{i.unlinkSync(g)}catch{}n=null,t=null,r=!1,o=!1}if(!r&&Ce()){const w=q()[0];if(w&&(t=w,r=!0,o=!1,f=!0,e.cleanupStale)){const U=Le();xe(w,U,{launcher:H(w),adopted:!0}),X(w,H(w)),n={pid:w,instanceId:U,adopted:!0}}}return{running:r,pid:t,stale:o,remoteAccessDisabled:Re(),launcher:_e(t,r),platform:l(),shennianDir:S,pidFile:g,logFile:d,...n?.instanceId?{instanceId:n.instanceId}:{},...f?{adopted:!0}:{},...v.machineId?{machineId:v.machineId}:{},paired:!!(v.machineToken&&v.machineId),...v.serverUrl?{serverUrl:v.serverUrl}:{}}}function y(e){process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
2
|
+
`)}function Y(e){const n=e?.trim();if(!n)return;const t=j();t.serverUrl=n,fe(t)}function z(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const _="com.shennian.agent",p=s.join(u.homedir(),"Library/LaunchAgents",`${_}.plist`),Me=s.join(u.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),D=h("autostart.cmd"),b=h("autostart.vbs"),R=h("autostart.xml"),Ue=s.join(Me,"ShennianAgent.vbs");function Q(e){const n=l()==="win32"?`where ${e}`:`command -v ${e}`;try{return a(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(o=>o.trim()).find(Boolean)??null}catch{return null}}function je(){const e=l()==="win32"?"npx.cmd":"npx",n=s.join(s.dirname(V),e);return i.existsSync(n)?n:Q("npx")}function P(){return De({nodeExec:V,scriptPath:B,shennianCommandPath:Q("shennian"),npxPath:je()})}function Z(){try{a(`schtasks /delete /tn "${k}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ee(){try{i.unlinkSync(Ue)}catch{}}function We(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||u.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function Fe(e){if(l()==="win32"&&ve(e.command)){const n=[e.command,...e.args].map(W).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Be(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ve(){const e=P();i.writeFileSync(D,he(e,d)),i.writeFileSync(b,ye(D)),ee(),Z();const n=we({userId:We(),command:s.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[W(b)]});i.writeFileSync(R,n,"utf8");const t=ge({taskName:k,xmlPath:R});try{return a(t.create,{stdio:"pipe",windowsHide:!0}),a(t.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Je(){Ae(k,e=>{a(e,{stdio:"pipe",windowsHide:!0})})}function ne(){const e={};for(const n of J)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=u.homedir(),e.PATH=Se({pathValue:e.PATH,env:e}),e.USER??=u.userInfo().username,l()==="win32"){const n=process.env.TEMP||process.env.TMP||u.tmpdir(),t=u.homedir();e.USERPROFILE??=t;const r=s.win32.parse(t);e.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=t.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||u.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ge(){const e=ne(),n=P(),t=Object.entries(e).map(([o,f])=>` <key>${T(o)}</key>
|
|
3
3
|
<string>${T(f)}</string>`).join(`
|
|
4
4
|
`),r=[n.command,...n.args].map(o=>` <string>${T(o)}</string>`).join(`
|
|
5
5
|
`);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -27,7 +27,7 @@ ${t}
|
|
|
27
27
|
<key>StandardErrorPath</key>
|
|
28
28
|
<string>${d}</string>
|
|
29
29
|
</dict>
|
|
30
|
-
</plist>`}const I=s.join(u.homedir(),".config/systemd/user/shennian.service");function Ke(){const e=ne(),n=
|
|
30
|
+
</plist>`}const I=s.join(u.homedir(),".config/systemd/user/shennian.service");function Ke(){const e=ne(),n=P(),t=Object.entries(e).map(([o,f])=>`Environment=${o}=${f}`).join(`
|
|
31
31
|
`),r=[n.command,...n.args].map(Ee).join(" ");return`[Unit]
|
|
32
32
|
Description=Shennian Agent Daemon
|
|
33
33
|
After=network.target
|
|
@@ -41,6 +41,6 @@ StandardOutput=append:${d}
|
|
|
41
41
|
StandardError=append:${d}
|
|
42
42
|
|
|
43
43
|
[Install]
|
|
44
|
-
WantedBy=default.target`}const k="ShennianAgent";function Xe(e){a(e,{stdio:"pipe",windowsHide:!0})}function qe(){const e=process.getuid?.();if(typeof e=="number")return e;const n=u.userInfo();return typeof n.uid=="number"?n.uid:n.username}function te(e=qe()){return`gui/${e}/${_}`}function Ye(e=Xe){try{return e(`launchctl unload "${p}" 2>/dev/null; launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{try{return e(`launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{return!1}}}function ze(){i.mkdirSync(S,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&
|
|
44
|
+
WantedBy=default.target`}const k="ShennianAgent";function Xe(e){a(e,{stdio:"pipe",windowsHide:!0})}function qe(){const e=process.getuid?.();if(typeof e=="number")return e;const n=u.userInfo();return typeof n.uid=="number"?n.uid:n.username}function te(e=qe()){return`gui/${e}/${_}`}function Ye(e=Xe){try{return e(`launchctl unload "${p}" 2>/dev/null; launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{try{return e(`launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{return!1}}}function ze(){i.mkdirSync(S,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&Ne(n)&&(e[n]=t);i.writeFileSync(h("env.json"),JSON.stringify(e,null,2))}function re(e={}){i.mkdirSync(S,{recursive:!0});const n=m({cleanupStale:!0});if(n.pid!==null&&n.running&&!$(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}ie(n);const t=i.openSync(d,"a"),r=Fe(P()),o=de(r.command,r.args,Be(r,t));o.unref(),i.closeSync(t),He(o.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${o.pid})`)),console.log(c.gray(` Logs: ${d}`)))}function $(e,n=A(),t=F()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==t}function Qe(e=l()){switch(e){case"darwin":return i.existsSync(p);case"linux":return i.existsSync(I);case"win32":return i.existsSync(D)&&i.existsSync(b)&&i.existsSync(R)}}function Ze(e,n,t,r=A(),o=F()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&t&&!$(e,r,o)}function ie(e=m({cleanupStale:!0})){if(!(!$(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),G(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),G(e.pid,2e3));try{A()?.pid===e.pid&&i.unlinkSync(g)}catch{}}catch{}}function oe(){i.mkdirSync(S,{recursive:!0});const e=l();switch(P().mode==="direct"&&O(B)&&console.warn(c.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),e){case"darwin":{const t=s.dirname(p);return i.mkdirSync(t,{recursive:!0}),i.writeFileSync(p,Ge()),Ye()}case"linux":{const t=s.dirname(I);i.mkdirSync(t,{recursive:!0}),i.writeFileSync(I,Ke());try{a("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{a(`loginctl enable-linger ${u.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return a("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ve()}}function en(){const n=m({cleanupStale:!0}).pid;if(n===null)return{};if(!E(n)){try{i.unlinkSync(g)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function se(e=5e3){const n=en();if(!n.stoppedPid)return n;if(!await M(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await M(n.stoppedPid,2e3)}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{i.unlinkSync(g)}catch{}Oe();for(const r of q())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await M(r,2e3)}catch{}return n}function nn(e={}){Y(z(e.api)),Te(),ie(),oe()||re({quiet:e.json}),e.json?y(L(()=>m({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function tn(e={}){switch(i.mkdirSync(S,{recursive:!0}),i.writeFileSync(x,new Date().toISOString()),l()){case"darwin":{if(i.existsSync(p))try{a(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{a("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{a(`schtasks /change /tn "${k}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{a(`schtasks /end /tn "${k}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await se(),r=m({cleanupStale:!0});if(e.json){y(t.error?{...r,error:t.error}:r);return}t.error?console.error(c.red(`\u2717 Failed to stop: ${t.error}`)):console.log(c.green("\u2713 Background service stopped"))}function ce(e){const n=m({cleanupStale:!0});if(Ze(n,e,Qe())){e.json?y(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}ze(),nn(e)}async function C(e={}){await tn(e)}async function M(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!E(e))return!0;await new Promise(r=>setTimeout(r,100))}return!E(e)}async function rn(e={}){Y(z(e.api));const n=l();n==="win32"&&Je();const t=await se();if(t.error){e.json?y({...m(),error:t.error}):console.error(c.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(n==="linux"||n==="win32")&&oe()){e.json&&y(L(()=>m({cleanupStale:!0})));return}re({quiet:e.json}),e.json&&y(L(()=>m({cleanupStale:!0})))}function ae(e={}){const n=m({cleanupStale:!0});if(e.json){y(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${d}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function ue(e){if(!i.existsSync(d)){console.log(c.gray("No logs yet"));return}try{const n=a(u.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${d}"`:`tail -n ${e.lines} "${d}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=i.readFileSync(d,"utf-8").split(`
|
|
45
45
|
`).slice(-e.lines);console.log(n.join(`
|
|
46
|
-
`))}}async function on(){switch(await C(),l()){case"darwin":{if(i.existsSync(p)){try{a(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}i.unlinkSync(p),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{a("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(i.existsSync(I)){i.unlinkSync(I);try{a("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Z();try{i.unlinkSync(
|
|
46
|
+
`))}}async function on(){switch(await C(),l()){case"darwin":{if(i.existsSync(p)){try{a(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}i.unlinkSync(p),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{a("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(i.existsSync(I)){i.unlinkSync(I);try{a("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Z();try{i.unlinkSync(D)}catch{}try{i.unlinkSync(b)}catch{}try{i.unlinkSync(R)}catch{}ee(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function vn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>ce(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>C(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>ae(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>ue({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,o)=>{o||console.error(c.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),ce(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await C(r)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),rn(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),ae(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),ue({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(on)}export{Be as buildDaemonSpawnOptions,Pn as buildWindowsLauncherCommand,In as buildWindowsScheduledTaskCommands,kn as buildWindowsScheduledTaskSuspendCommands,Nn as buildWindowsScheduledTaskXml,Dn as buildWindowsStartupVbs,Ze as canReuseRunningDaemonForStart,ne as captureEnvForService,Oe as clearDaemonLauncher,yn as clearDaemonPidIfOwner,Te as clearRemoteAccessDisabled,Le as createDaemonInstanceId,q as findRunningDaemonProcessIds,K as getCurrentProcessDaemonLauncher,m as getDaemonStatus,te as getLaunchdServiceTarget,oe as installService,Qe as isAutoStartServicePresent,O as isEphemeralCliPath,$e as isIsolatedDaemonE2eInstance,Re as isRemoteAccessDisabled,Ne as isSafeSnapshotEnvKey,Tn as isShennianRunServiceCommand,He as recordStartedDaemon,vn as registerDaemonCommand,Ye as reloadLaunchdAgent,De as resolveServiceLaunchSpec,ze as saveEnvSnapshot,Ce as shouldDiscoverGlobalDaemonProcesses,$ as shouldReplaceRunningDaemon,re as startDaemonProcess,bn as suspendWindowsScheduledTask,X as writeDaemonLauncher,xe as writeDaemonPid};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import I from"node:crypto";import{spawn as u}from"node:child_process";import{HttpDeviceAuthorizationClient as f}from"../room/device-authorization-client.js";import{hashActionPayload as
|
|
1
|
+
import I from"node:crypto";import{spawn as u}from"node:child_process";import{HttpDeviceAuthorizationClient as f}from"../room/device-authorization-client.js";import{hashActionPayload as p,hashPendingDeviceSecret as A}from"./pair-authorization.js";const g=3e3;class n extends Error{code;constructor(r,t){super(t),this.code=r,this.name="BrowserPairError"}}async function x(e){const r=new f(e.serverUrl),t=I.randomBytes(32).toString("base64url"),a={action:"pair_machine",agentList:[...e.agentList].sort(),machineName:e.machineName,machineOs:e.machineOs},o=p(a),i=await r.create({deviceSecretHash:A(t),machineName:e.machineName,machineOs:e.machineOs,agentList:e.agentList,actionType:"machine.pair",actionSummary:"Pair this machine with your Shennian account",actionPayloadHash:o,returnTo:"/spaces"}),d=_(i.publicLoginUrl,e.serverUrl);e.emit?.({type:"pair.browser.created",intentId:i.loginIntentId,url:d,expiresAt:i.expiresAt}),await(e.openUrl??P)(d).catch(()=>!1);const l=new Date(i.expiresAt).getTime(),m=e.pollIntervalMs??g;let h=!1;for(;Date.now()<l;){if(e.signal?.aborted)throw await r.cancel({loginIntentId:i.loginIntentId,deviceSecret:t}).catch(()=>{}),new n("PAIR_CANCELLED","Pairing was cancelled");let c;try{c=await r.getStatus({loginIntentId:i.loginIntentId,deviceSecret:t})}catch{await w(m,e.signal);continue}if(c.actionPayloadHash!==o)throw new n("PAIR_PAYLOAD_MISMATCH","Authorization request changed unexpectedly");if(c.status==="denied")throw new n("PAIR_DENIED","Pairing request was denied");if(c.status==="expired")throw new n("PAIR_EXPIRED","Pairing request expired");if(c.status==="claimed")throw new n("PAIR_ALREADY_CLAIMED","Pairing credentials were already claimed");if(c.status==="approved"){const s=await r.claim({loginIntentId:i.loginIntentId,deviceSecret:t});if(s.actionPayloadHash!==o||!s.machineId||!s.machineToken||!s.accountNamespaceId)throw new n("PAIR_INVALID_CLAIM","Authorization did not return machine credentials");return e.emit?.({type:"pair.claimed"}),{machineId:s.machineId,machineToken:s.machineToken,accountNamespaceId:s.accountNamespaceId,machineName:e.machineName,publicLoginUrl:d}}h||(h=!0,e.emit?.({type:"pair.waiting"})),await w(m,e.signal)}throw new n("PAIR_TIMEOUT","Timed out waiting for browser authorization")}function E(e){if(e.browser&&e.token)throw new n("PAIR_MODE_CONFLICT","Choose either --browser or --token, not both");return e.browser?"browser":e.token||e.json||!e.stdinIsTty||!e.stdoutIsTty?"token":"prompt"}function O(e){return e.trim()==="2"?"token":"browser"}async function P(e){if(process.env.SSH_CONNECTION||process.env.SSH_TTY)return!1;const{command:r,args:t}=L(process.platform,e);return new Promise(a=>{const o=u(r,t,{detached:!0,stdio:"ignore",windowsHide:!0});o.once("error",()=>a(!1)),o.once("spawn",()=>{o.unref(),a(!0)})})}function L(e,r){return e==="darwin"?{command:"open",args:[r]}:e==="win32"?{command:"explorer.exe",args:[r]}:{command:"xdg-open",args:[r]}}function _(e,r){let t,a;try{t=new URL(e),a=new URL(r)}catch{throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an invalid authorization URL")}if(!["http:","https:"].includes(t.protocol)||t.hostname!==a.hostname||t.username||t.password||t.search||t.hash||!/^\/authorize\/[A-Za-z0-9_-]{8,128}$/.test(t.pathname))throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an unsafe authorization URL");return t.toString()}function w(e,r){return new Promise(t=>{if(r?.aborted)return t();const a=setTimeout(t,e);r?.addEventListener("abort",()=>{clearTimeout(a),t()},{once:!0})})}export{n as BrowserPairError,L as browserOpenCommand,_ as normalizePairAuthorizationUrl,P as openExternalUrl,O as parsePairModeChoice,E as resolvePairMode,x as runBrowserPairFlow};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import r from"chalk";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import k from"node:os";import
|
|
2
|
-
`)}function g(o,e){
|
|
3
|
-
\u2717 Pairing code expired. Please re-run: shennian pair`)),process.exit(1))),!
|
|
1
|
+
import r from"chalk";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import k from"node:os";import R from"node:readline";import{loadConfig as u,saveConfig as N}from"../config/index.js";import{detectAgents as E}from"../agents/detect.js";import{clearRemoteAccessDisabled as A,getDaemonStatus as L,startDaemonProcess as b,installService as O,saveEnvSnapshot as x}from"./daemon.js";import{waitForDaemonReadyStatus as B}from"./daemon-start-wait.js";import{detectAndChooseServer as v}from"../region.js";import{buildPairQrPayload as M,PAIR_QR_RENDER_OPTIONS as F}from"./pair-qr.js";import{BrowserPairError as J,parsePairModeChoice as Q,resolvePairMode as C,runBrowserPairFlow as q}from"./pair-browser.js";const z=3e3,U=600*1e3;function d(o){process.stdout.write(`${JSON.stringify(o)}
|
|
2
|
+
`)}function g(o,e){d({type:"error",code:o,message:e}),process.exit(1)}function K(o){const e="\u2500".repeat(o.length+4);console.log(r.cyan(`\u250C${e}\u2510`)),console.log(r.cyan("\u2502 ")+r.bold.yellow(o)+r.cyan(" \u2502")),console.log(r.cyan(`\u2514${e}\u2518`))}async function W(o){const e=(await import("qrcode-terminal")).default;return new Promise(n=>{e.generate(o,F,i=>{console.log(i),n()})})}async function Y(o,e,n={}){const i=Date.now()+U;for(n.json&&d({type:"pair.waiting"});Date.now()<i;){await new Promise(l=>setTimeout(l,z));let a;try{a=await fetch(`${o}/api/machines/pair/status?token=${encodeURIComponent(e)}`)}catch{continue}if(a.status===410&&(n.json?g("PAIR_EXPIRED","Pairing code expired"):(console.error(r.red(`
|
|
3
|
+
\u2717 Pairing code expired. Please re-run: shennian pair`)),process.exit(1))),!a.ok)continue;const t=await a.json();if(t.status==="claimed"&&t.machineToken&&t.machineId&&t.accountNamespaceId)return n.json&&d({type:"pair.claimed"}),{machineToken:t.machineToken,machineId:t.machineId,machineName:t.machineName??"",accountNamespaceId:t.accountNamespaceId}}return null}function H(o){const e=R.createInterface({input:process.stdin,output:process.stdout});return new Promise(n=>{e.question(o,i=>{e.close(),n(i.trim().toLowerCase()==="y")})})}function j(){const o=R.createInterface({input:process.stdin,output:process.stdout});return console.log(r.white("Choose how to pair this machine:")),console.log(r.cyan(" 1. Browser authorization (recommended)")),console.log(r.gray(" 2. QR code / pairing token (servers and remote terminals)")),new Promise(e=>{o.question("Selection (1/2) [1]: ",n=>{o.close(),e(Q(n))})})}async function $(o){const{serverUrl:e,machineName:n,force:i,json:a}=o,t=u();if(t.machineToken&&t.machineId&&!i){a?d({type:"done",status:"already-paired",machineId:t.machineId}):(console.log(r.green(`\u2713 Already paired (machine ID: ${t.machineId})`)),console.log(r.gray(' Run "shennian pair" to re-pair to another account')));return}const c=E().map(p=>p.type),m=k.platform(),f=m==="win32"?"windows":m==="darwin"?"macos":"linux";a||(console.log(r.bold(`
|
|
4
4
|
Shennian CLI \u2014 Pairing
|
|
5
|
-
`)),console.log(r.gray(` Machine: ${n} | OS: ${f} | Agents: ${
|
|
6
|
-
If the browser did not open, copy this URL to any signed-in device.`)),console.log(r.gray("Waiting for authorization... (Ctrl+C to cancel)")))}});
|
|
7
|
-
\u2713 Paired successfully! Machine "${
|
|
8
|
-
\u2717 ${
|
|
9
|
-
\u2717 Timed out. Pairing code expired, please try again.`)),process.exit(1)),
|
|
10
|
-
\u2713 Paired successfully! Machine "${
|
|
11
|
-
Starting background service...`)),O()||b()}function
|
|
12
|
-
Starting background service...`)),O()||b({quiet:!!e.json}),e.json&&(B(()=>L({cleanupStale:!0})),
|
|
5
|
+
`)),console.log(r.gray(` Machine: ${n} | OS: ${f} | Agents: ${c.join(", ")||"none"}`)),console.log());let S=o.mode??C({json:a,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY});if(S==="prompt"&&(S=await j()),S==="browser"){const p=new AbortController,w=()=>p.abort();process.once("SIGINT",w);try{const s=await q({serverUrl:e,machineName:n,machineOs:f,agentList:c,signal:p.signal,openUrl:o.openUrl,emit:T=>{a?d(T):T.type==="pair.browser.created"&&(console.log(r.white("Approve this machine in your browser:")),console.log(r.cyan.underline(T.url)),console.log(r.gray(`
|
|
6
|
+
If the browser did not open, copy this URL to any signed-in device.`)),console.log(r.gray("Waiting for authorization... (Ctrl+C to cancel)")))}});N({...t,machineToken:s.machineToken,machineId:s.machineId,accountNamespaceId:s.accountNamespaceId,serverUrl:e}),a?d({type:"pair.saved",machineId:s.machineId}):console.log(r.green(`
|
|
7
|
+
\u2713 Paired successfully! Machine "${s.machineName}" is now linked.`));return}catch(s){const T=s instanceof J||typeof s=="object"&&s&&"code"in s&&typeof s.code=="string"?s.code:"PAIR_BROWSER_FAILED",P=s instanceof Error?s.message:"Browser pairing failed";a&&g(T,P),console.error(r.red(`
|
|
8
|
+
\u2717 ${P}`)),process.exit(1)}finally{process.removeListener("SIGINT",w)}}let y;try{y=await fetch(`${e}/api/machines/pair/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,os:f,agentList:c,daemonInstallationId:D()})})}catch{const p=`Network error: cannot connect to ${e}`;a&&g("NETWORK_ERROR",p),console.error(r.red(`\u2717 ${p}`)),process.exit(1)}if(!y.ok){const w=`Failed to create machine: ${(await y.json().catch(()=>({}))).error??y.statusText}`;a&&g("PAIR_CREATE_FAILED",w),console.error(r.red(`\u2717 ${w}`)),process.exit(1)}const I=await y.json(),_=M(I.pairToken);a?d({type:"pair.created",code:I.pairToken,machineId:I.machineId,expiresAt:new Date(Date.now()+U).toISOString()}):(console.log(r.white("Scan QR code or enter the pairing code in the Shennian app:")),console.log(),await W(_),K(I.pairToken),console.log(),console.log(r.gray("Waiting for pairing... (Ctrl+C to cancel)")));const h=await Y(e,I.pairToken,{json:a});h||(a&&g("PAIR_TIMEOUT","Timed out waiting for pairing"),console.error(r.red(`
|
|
9
|
+
\u2717 Timed out. Pairing code expired, please try again.`)),process.exit(1)),N({...t,machineToken:h.machineToken,machineId:h.machineId,accountNamespaceId:h.accountNamespaceId,serverUrl:e}),a&&d({type:"pair.saved",machineId:h.machineId}),a||console.log(r.green(`
|
|
10
|
+
\u2713 Paired successfully! Machine "${h.machineName}" is now linked.`))}async function G(o){const e=u(),i=E().map(m=>m.type),a=k.platform(),t=a==="win32"?"windows":a==="darwin"?"macos":"linux",l=await fetch(`${o.serverUrl}/api/machines/desktop/register`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o.authToken}`},body:JSON.stringify({machineId:e.machineId,name:o.machineName,os:t,agentList:i})});if(!l.ok){const f=(await l.json().catch(()=>({}))).error??l.statusText;throw new Error(`Failed to register desktop machine: ${f}`)}const c=await l.json();if(!c.machine?.id||!c.machineToken||!c.accountNamespaceId)throw new Error("Failed to register desktop machine: missing machine token");return e.machineId=c.machine.id,e.machineToken=c.machineToken,e.accountNamespaceId=c.accountNamespaceId,e.serverUrl=o.serverUrl,N(e),{machineId:c.machine.id,machineName:c.machine.name}}async function V(o){const e=o?.trim();if(e)return e;const n=process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim();if(n)return n;const i=u().serverUrl?.trim();return i||v()}async function de(o,e){const n=u();if(!n.machineToken||!n.machineId){const a=o??await v();await $({serverUrl:a,machineName:e,force:!1})}else console.log(r.green(`\u2713 Already paired (machine ID: ${n.machineId})`));x(),A(),console.log(r.gray(`
|
|
11
|
+
Starting background service...`)),O()||b()}function le(o){o.command("desktop-register",{hidden:!0}).description("Register this desktop-managed machine to the authenticated account").option("--api <url>","Server URL").option("--name <name>","Machine name",k.hostname()).action(async e=>{try{const n=process.env.SHENNIAN_DESKTOP_AUTH_TOKEN;if(!n)throw new Error("Missing desktop auth token");const i=await V(e.api),a=await G({serverUrl:i,authToken:n,machineName:e.name});console.log(JSON.stringify({ok:!0,...a}))}catch(n){console.error(n instanceof Error?n.message:String(n)),process.exit(1)}}),o.command("pair").description("Pair this machine to a Shennian account (re-pair if already paired)").option("--api <url>","Server URL override").option("--server <url>","Server URL override").option("--name <name>","Machine name",k.hostname()).option("--json","Emit machine-readable JSONL events").option("--browser","Pair by approving a browser authorization request").option("--token","Pair with the existing QR code / token flow").option("--force","Force re-pair even when a machine token exists").action(async e=>{const n=u();let i;try{i=C({browser:e.browser,token:e.token,json:e.json,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY})}catch(c){const m=c instanceof Error?c.message:"Invalid pairing mode";e.json&&g("PAIR_MODE_CONFLICT",m),console.error(r.red(`\u2717 ${m}`)),process.exit(1)}if(i==="prompt"&&(i=await j()),n.machineToken&&n.machineId&&!e.force){if(e.json){d({type:"done",status:"already-paired",machineId:n.machineId});return}if(console.log(r.yellow(`Currently paired (machine ID: ${n.machineId})`)),!await H("Re-pairing will unlink the current account. Continue? (y/N) ")){console.log(r.gray("Cancelled"));return}}const a=e.server??e.api??await v(),t=!!(e.force||n.machineToken&&n.machineId);await $({serverUrl:a,machineName:e.name,force:t,json:!!e.json,mode:i}),x(),A(),e.json?d({type:"daemon.starting"}):console.log(r.gray(`
|
|
12
|
+
Starting background service...`)),O()||b({quiet:!!e.json}),e.json&&(B(()=>L({cleanupStale:!0})),d({type:"done",status:"paired",machineId:u().machineId}))})}export{G as registerDesktopMachine,le as registerPairCommand,$ as runPairFlow,de as runSmartStart};
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{Command as te}from"commander";import S from"chalk";import re from"node:os";import
|
|
2
|
-
`),process.exitCode=1}function
|
|
3
|
-
Disconnecting...`)),w({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await
|
|
1
|
+
import{Command as te}from"commander";import S from"chalk";import re from"node:os";import b from"node:fs";import{loadConfig as d,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as ve}from"./commands/runtime.js";import{registerManagerCommand as Se}from"./commands/manager.js";import{registerUpgradeCommand as we}from"./commands/upgrade.js";import{registerToolsCommand as Ie}from"./commands/tools.js";import{registerIntegrationCommand as ye}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as Ce}from"./session/manager.js";import{SERVERS as P,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as Q}from"./env-path.js";Q();const q=Y();import{getCachedAgentInfos as Ae,resolveAgentInfos as Ne}from"./agents/model-registry.js";import{initCliLogReporter as Oe,reportLog as w}from"./log-reporter.js";import{isNativeFusionEnabled as $e}from"./native-fusion/config.js";import{NativeSessionFusionService as be}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as xe}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as Ge}from"./room/activation-service.js";import{RoomActivationAudit as He}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_REQUIRED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeActiveAccountNamespace as Qe}from"./personal-sync/account-namespace.js";const X=ae(),qe=J("daemon.pid");function Xe(){try{const o=b.readFileSync(qe,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),c=Number(r.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function Ze(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function _(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
|
|
2
|
+
`),process.exitCode=1}function eo(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new te;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(q),t.enablePositionalOptions(),ye(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(r,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[i,a]of Object.entries(e))process.env[i]||(process.env[i]=a);Q()}catch{}const r=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=Xe();if(e&&e!==process.pid)try{process.kill(e,0),c?await M(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const U=he(process.env,X)?[]:ge(process.pid);if(U.length>0)if(c)for(const e of U)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${U[0]}), skipping duplicate start`),process.exit(0);const l=pe();K(process.pid,l,{version:q}),W(process.pid,void 0,l),process.on("exit",()=>{z(process.pid,l),j(l)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const s=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??P.cn.url,p=Y(),A=Ue(),h=A.map(e=>e.type),N=Ae(A),I=new Me,y=new Ve,L=new ze(I),f=new Le(g),oe=new Fe({actionClient:f}),ne=new Je({actionClient:f});let x=null;const R=await _e({handlers:{...je({authority:L,actionClient:f,attachmentCache:oe,attachmentUploader:ne,observations:y,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let V=!1;const C=()=>{V||(V=!0,R.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",C),process.once("SIGTERM",C),!s.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await R.close(),process.exitCode=1;return}process.removeListener("SIGINT",C),process.removeListener("SIGTERM",C);const B=`${Ze(g)}/relay/machine`,O=s.machineToken;if(!O)return;try{s.accountNamespaceId=await Qe({serverUrl:g,machineToken:O,machineId:s.machineId})}catch(e){console.error(`[personal-sync] account namespace verification failed: ${e instanceof Error?e.message:String(e)}`),await R.close(),process.exitCode=1;return}s.machineId&&Oe(g,s.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${p}) agents: ${h.join(",")}`),w({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:h}});let k=null,D=null,E=0;const G=ce(s.autoUpgrade),m=new me({serverUrl:B,machineToken:O,cliVersion:p,agentList:h,autoUpgradePolicy:G,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),w({level:"info",wsEvent:"daemon.connected"}),N.some(a=>a.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:N}}),Ne(A,{serverUrl:s.serverUrl??g,authToken:s.machineToken??s.accessToken}).then(a=>{JSON.stringify(a)!==JSON.stringify(N)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:a}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:a,clearUpgradeAttempt:$})=>{const v=a();$(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),T.start(),D?.start();const e=++E,i=async()=>{try{await u.synchronizeSessionDeletions(),e===E&&m.getState()==="connected"&&k?.handleConnected()}catch(a){console.error("[session.deletions] startup catch-up failed",a),e===E&&m.getState()==="connected"&&setTimeout(()=>{i()},5e3).unref?.()}};i()},onDisconnected:e=>{E+=1,D?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${eo(e)}`),w({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),w({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),u.handleReq(e)}});k=$e()?new be(m):null,D=new Be({registry:I,observations:y,actionClient:f,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,a,$)=>{await x?.processMessages(e,i,a,$)}});const T=new Te({currentVersion:p,machineId:s.machineId??"unknown-machine",serverUrl:s.serverUrl??g,getIdleState:()=>u.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(m,"auto-upgrade",e,{currentVersion:p})}});T.setPolicy(G);const u=new Ce(m,k,p,T,new xe({registry:I,observations:y,bindingClient:f,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));x=new Ge({registry:I,observations:y,activate:e=>u.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:i})},abort:e=>u.abortRoomSession(e),audit:new He}),b.mkdirSync(X,{recursive:!0}),K(process.pid,l,{version:p}),W(process.pid,void 0,l),m.connect(),process.stdin.resume();let H=!1;const F=async()=>{if(!H){H=!0,console.log(S.gray(`
|
|
3
|
+
Disconnecting...`)),w({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await u.cleanup(),T.stop(),k?.stop(),D?.stop(),m.disconnect(),await R.close(),z(process.pid,l),j(l)}finally{process.exit(0)}}};process.on("SIGINT",()=>{F()}),process.on("SIGTERM",()=>{F()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>_()),t.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>_()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>_());const ee=t.command("config").description("View or modify configuration");ee.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${se()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=De(n.serverUrl);console.log(`Region: ${r} (${P[r].label})`)}console.log(JSON.stringify(n,null,2))}),ee.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const r=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(S.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;r.serverUrl=ke(c),ie(r),console.log(S.green(`\u2713 Server set to ${P[c].label}`)),console.log(S.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(S.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(t),fe(t),ve(t),Se(t),we(t),Ie(t),t.parse();
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import g from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{
|
|
2
|
-
`).trim()||null}function f(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{E as ManagerRegistry};
|
|
1
|
+
import g from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{isAgentHiddenPayload as w,sessionPreviewFromPayload as A}from"@shennian/wire";import{resolveShennianPath as T}from"../config/index.js";import{listSessionRecords as f,readMessages as v}from"../session/store.js";import{listProjectedSessions as M}from"../session/projection.js";const I=T("manager-registry.json");function u(){return new Date().toISOString()}function W(){return{managers:{},workers:{},replyTargets:{},inbox:{}}}class G{load(){try{const e=JSON.parse(g.readFileSync(I,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{}}}catch{return W()}}save(e){g.mkdirSync(S.dirname(I),{recursive:!0}),g.writeFileSync(I,JSON.stringify(e,null,2))}upsertManager(e){const t=this.load(),r=t.managers[e.sessionId],s=u(),a={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,status:e.status??r?.status??"idle",managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??s,updatedAt:s};return t.managers[e.sessionId]=a,this.save(t),a}getManager(e){return this.load().managers[e]}enqueueInboxEvent(e){const t=this.load(),r=t.managers[e.managerSessionId],s=t.workers[e.workerSessionId];if(!r||!s||s.managedBy!==r.sessionId||s.workDir!==r.workDir)return null;const a=u(),o=t.inbox[e.managerSessionId]??[],d=o.findIndex(c=>c.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?c.kind==="worker.health":c.kind===e.kind&&c.runId===e.runId)),l=d>=0?{...o[d],...e,updatedAt:a}:{...e,id:`mie_${m()}`,createdAt:a,updatedAt:a};return d>=0?o[d]=l:o.push(l),t.inbox[e.managerSessionId]=o,this.save(t),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((t,r)=>t.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,t){if(!t.length)return;const r=this.load(),s=new Set(t);r.inbox[e]=(r.inbox[e]??[]).filter(a=>!s.has(a.id)),this.save(r)}addWorker(e){const t=this.load(),r=t.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const s=e.sessionId??`sess_worker_${m()}`,a=u(),o={sessionId:s,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:a,updatedAt:a,lastActivityAt:a,summary:e.summary??null,agentSessionId:null,runId:null};return t.workers[s]=o,r.managedWorkerSessionIds.includes(s)||r.managedWorkerSessionIds.push(s),r.updatedAt=a,this.save(t),o}updateWorker(e,t){const r=this.load(),s=r.workers[e];if(!s)return;const a={...s,...t,updatedAt:u()};return r.workers[e]=a,this.save(r),a}listWorkers(e,t={}){const r=this.load(),s=r.managers[e];if(!s)return[];const a=Object.values(r.workers).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,t.runningSessionIds)),o=new Set(a.map(n=>n.sessionId)),d=f().filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!o.has(n.sessionId)).map(n=>this.sessionRecordToWorker(n,t.runningSessionIds)),l=new Set([...a,...d].map(n=>n.sessionId)),h=(t.projectedSessions??M()).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!l.has(n.id)).map(n=>this.projectedSessionToWorker(n,t.runningSessionIds));return[...a,...d,...h].sort((n,k)=>k.lastActivityAt.localeCompare(n.lastActivityAt))}getWorkerForManager(e,t){const r=this.load(),s=r.managers[e],a=r.workers[t];if(!s)return;if(a)return a.workDir!==s.workDir||a.agentType==="manager"?void 0:this.decorateManagedWorker(a);const o=f().find(d=>d.sessionId===t);if(!(!o||o.workDir!==s.workDir||o.agentType==="manager"))return this.sessionRecordToWorker(o)}decorateManagedWorker(e,t){const r=t?.has(e.sessionId)?"running":e.status,s=this.readTranscriptSummary(e.sessionId,e.summary??null),a=s.lastMessagePreview??e.lastMessagePreview??e.summary??null,o=e.userGoal??s.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??o??a??e.summary??e.sessionId,userGoal:o,lastMessagePreview:a,summary:e.summary??a,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}sessionRecordToWorker(e,t){const r=this.readTranscriptSummary(e.sessionId,e.lastMessagePreview??null),s=r.lastMessagePreview??e.lastMessagePreview??null,a=r.userGoal,o=t?.has(e.sessionId)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:o,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt,title:a??s??e.sessionId,userGoal:a,lastMessagePreview:s,summary:s??a??null,agentSessionId:e.agentSessionId??null,runId:null,canResume:!!e.agentSessionId,source:"local_index",role:"project_session"}}projectedSessionToWorker(e,t){const r=e.lastMessagePreview??null,s=e.title?.trim()||r,a=t?.has(e.id)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.id,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:a,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt||e.updatedAt,title:s??e.id,userGoal:s??null,lastMessagePreview:r,summary:r??s??null,agentSessionId:e.agentSessionId??null,runId:e.activity?.runId??null,canResume:!0,source:"app_projection",role:"project_session"}}readTranscriptSummary(e,t){const r=v(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:t};const s=[...r].sort((d,l)=>d.ts-l.ts),a=s.find(d=>d.role==="user"),o=[...s].reverse().find(d=>y(d));return{userGoal:a?p(y(a),160):null,lastMessagePreview:p(y(o),180)??t}}createReplyTarget(e){const t=this.load(),r={replyTarget:`rt_${m()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:u()};return t.replyTargets[r.replyTarget]=r,this.save(t),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(t=>t.managerSessionId===e).sort((t,r)=>r.createdAt.localeCompare(t.createdAt))[0]}}function y(i){return!i||w(i.payload)?null:A(i.payload,180)}function p(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{G as ManagerRegistry};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import p from"node:crypto";import l from"node:fs";import m from"node:path";import{resolvePersonalSyncV1Path as k}from"../personal-sync/paths.js";import{getCanonicalSessionStore as A,getDaemonInstallationId as v,listSessionRecords as B,recordSession as $}from"../session/store.js";import{readToolDetail as H,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as L}from"./native-source-deletions.js";const S=1;class R extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class V extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class oe{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}commitEvent(e){if(q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new V(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=W(e),c=this.ledgerFile(e.agentType,e.sourceEventKey),n=l.existsSync(c)?w(c):null;if(n&&n.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==a)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new R(e.sourceSessionKey);const g=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??g??U(s,e.agentType,e.sourceSessionKey);if(g&&r&&r.sessionId!==g)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const y=this.canonicalStore(),f=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",h=n?.canonicalMessageId??(f?null:e.claimCanonicalMessageId??P(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let E=y.getManifest(d)?.bodyRevision??0,T=!!n;const M=!f&&!!e.claimCanonicalMessageId;if(M){if(!h)throw new Error("managed echo claim requires a canonical message");const I=y.getMessageState(d,h);if(!I||I.tombstoned)throw new Error(`managed echo claim target is not durable: ${h}`)}if(!n&&!M&&!f){if(!h)throw new Error("native event requires a canonical message");const I=e.canonicalMutation==="upsert"?y.getMessageState(d,h):null,K={id:h,sessionId:d,role:e.role,ts:I?.message?.ts??e.ts,payload:z(I?.message?.payload,e.payload)},F=e.canonicalMutation==="upsert"?y.upsert(d,K):y.append(d,K);if(E=F.bodyRevision,T=F.duplicate,e.localToolDetail){const C=this.options.writeLocalToolDetail??J,j=this.options.readLocalToolDetail??H,N=G(j(d,e.localToolDetail.toolCallId),e.localToolDetail);C({sessionId:d,...N,ts:e.ts,detailRef:{toolIndex:0}})}}!n&&!f&&this.recordIndex(e,d);const D=new Date().toISOString();return n||u(c,{schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:h,canonicalRevision:E,cursor:e.cursor??null,mappedBy:f?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:D}),o.sources[i]={schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:E,createdAt:r?.createdAt??D,updatedAt:D},u(this.sourcesFile(),o),{sessionId:d,messageId:h,bodyRevision:E,duplicate:T,claimed:M,suppressed:f}}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){x(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,u(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const i=this.readManagedEchoState();let r=!1;for(const a of Object.values(i.claims))a.sessionId!==e||a.sourceAgentType!==s||(a.sourceSessionKey=o,r=!0);r&&u(this.managedEchoStateFile(),i)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],u(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(_(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),u(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),i=b(e,s);o.suppressions[i]&&(delete o.suppressions[i],u(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[c,n]of Object.entries(s.sources))n.sessionId===e&&(delete s.sources[c],o=!0);o&&u(this.sourcesFile(),s);const i=this.readManagedEchoState();let r=!1;for(const[c,n]of Object.entries(i.claims))n.sessionId===e&&(delete i.claims[c],r=!0);for(const[c,n]of Object.entries(i.suppressions))n.sessionId===e&&(delete i.suppressions[c],r=!0);r&&u(this.managedEchoStateFile(),i);const a=m.join(this.rootDir(),"ledger");if(l.existsSync(a))for(const c of l.readdirSync(a,{withFileTypes:!0})){if(!c.isFile()||!c.name.endsWith(".json"))continue;const n=m.join(a,c.name),g=w(n);if(g.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");g.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new L({rootDir:m.basename(this.rootDir())==="native-fusion"?m.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const i=o.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[b(e,s)];if(!r||this.isSourceDeleted(e,s))return!1;const a=(this.options.listLocalSessions??B)().find(g=>g.sessionId===r.sessionId);if(a?.titleLockedByUser||a?.titleSource==="agent_native"&&a.title===i)return!1;const c=p.createHash("sha256").update(`${e}\0${s}\0${i}`).digest("hex");return(this.options.recordLocalSession??$)({sessionId:r.sessionId,agentType:r.agentType,workDir:a?.workDir??"",agentSessionId:s,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${c}`}),!0}recordIndex(e,s){(this.options.recordLocalSession??$)({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?O(e.payload):void 0,title:e.title?.trim()||(e.role==="user"?O(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==S||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??v()}canonicalStore(){return this.options.canonicalStore??A()}rootDir(){return this.options.rootDir??k("native-fusion")}sourcesFile(){return m.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return m.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==S||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))x(o);for(const o of Object.values(s.suppressions))_(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return m.join(this.rootDir(),"ledger",`${o}.json`)}}function q(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function U(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function P(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function W(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function x(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function _(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function z(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const i=typeof o.name=="string"?o.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...s,...o,...r&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function G(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function O(t){const e=t.replace(/\s+/g," ").trim();return e?e.length>120?`${e.slice(0,120)}...`:e:null}function u(t,e){const s=m.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let i;try{i=l.openSync(o,"wx",384),l.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),l.fsyncSync(i),l.closeSync(i),i=void 0,l.renameSync(o,t)}finally{i!==void 0&&l.closeSync(i);try{l.unlinkSync(o)}catch{}}}function w(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{
|
|
1
|
+
import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as k}from"@shennian/wire";import{resolvePersonalSyncV1Path as A}from"../personal-sync/paths.js";import{getCanonicalSessionStore as v,getDaemonInstallationId as B,listSessionRecords as H,recordSession as $}from"../session/store.js";import{readToolDetail as J,writeToolDetail as L}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as R}from"./native-source-deletions.js";const y=1;class V extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class P extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class ne{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}commitEvent(e){if(q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new P(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=z(e),c=this.ledgerFile(e.agentType,e.sourceEventKey),n=l.existsSync(c)?w(c):null;if(n&&n.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==a)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new V(e.sourceSessionKey);const m=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??m??U(s,e.agentType,e.sourceSessionKey);if(m&&r&&r.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const S=this.canonicalStore(),f=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",g=n?.canonicalMessageId??(f?null:e.claimCanonicalMessageId??W(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let E=S.getManifest(d)?.bodyRevision??0,T=!!n;const M=!f&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const I=S.getMessageState(d,g);if(!I||I.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!n&&!M&&!f){if(!g)throw new Error("native event requires a canonical message");const I=e.canonicalMutation==="upsert"?S.getMessageState(d,g):null,F={id:g,sessionId:d,role:e.role,ts:I?.message?.ts??e.ts,payload:G(I?.message?.payload,e.payload)},K=e.canonicalMutation==="upsert"?S.upsert(d,F):S.append(d,F);if(E=K.bodyRevision,T=K.duplicate,e.localToolDetail){const C=this.options.writeLocalToolDetail??L,j=this.options.readLocalToolDetail??J,N=Q(j(d,e.localToolDetail.toolCallId),e.localToolDetail);C({sessionId:d,...N,ts:e.ts,detailRef:{toolIndex:0}})}}!n&&!f&&this.recordIndex(e,d);const D=new Date().toISOString();return n||u(c,{schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:g,canonicalRevision:E,cursor:e.cursor??null,mappedBy:f?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:D}),o.sources[i]={schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:E,createdAt:r?.createdAt??D,updatedAt:D},u(this.sourcesFile(),o),{sessionId:d,messageId:g,bodyRevision:E,duplicate:T,claimed:M,suppressed:f}}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){x(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,u(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const i=this.readManagedEchoState();let r=!1;for(const a of Object.values(i.claims))a.sessionId!==e||a.sourceAgentType!==s||(a.sourceSessionKey=o,r=!0);r&&u(this.managedEchoStateFile(),i)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],u(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(_(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),u(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),i=b(e,s);o.suppressions[i]&&(delete o.suppressions[i],u(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[c,n]of Object.entries(s.sources))n.sessionId===e&&(delete s.sources[c],o=!0);o&&u(this.sourcesFile(),s);const i=this.readManagedEchoState();let r=!1;for(const[c,n]of Object.entries(i.claims))n.sessionId===e&&(delete i.claims[c],r=!0);for(const[c,n]of Object.entries(i.suppressions))n.sessionId===e&&(delete i.suppressions[c],r=!0);r&&u(this.managedEchoStateFile(),i);const a=h.join(this.rootDir(),"ledger");if(l.existsSync(a))for(const c of l.readdirSync(a,{withFileTypes:!0})){if(!c.isFile()||!c.name.endsWith(".json"))continue;const n=h.join(a,c.name),m=w(n);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new R({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const i=o.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[b(e,s)];if(!r||this.isSourceDeleted(e,s))return!1;const a=(this.options.listLocalSessions??H)().find(m=>m.sessionId===r.sessionId);if(a?.titleLockedByUser||a?.titleSource==="agent_native"&&a.title===i)return!1;const c=p.createHash("sha256").update(`${e}\0${s}\0${i}`).digest("hex");return(this.options.recordLocalSession??$)({sessionId:r.sessionId,agentType:r.agentType,workDir:a?.workDir??"",agentSessionId:s,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${c}`}),!0}recordIndex(e,s){(this.options.recordLocalSession??$)({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?O(e.payload):void 0,title:e.title?.trim()||(e.role==="user"?O(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==y||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??B()}canonicalStore(){return this.options.canonicalStore??v()}rootDir(){return this.options.rootDir??A("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==y||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))x(o);for(const o of Object.values(s.suppressions))_(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return h.join(this.rootDir(),"ledger",`${o}.json`)}}function q(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function U(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function W(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function z(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function x(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function _(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function G(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const i=typeof o.name=="string"?o.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...s,...o,...r&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Q(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function O(t){return k(t,120)}function u(t,e){const s=h.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let i;try{i=l.openSync(o,"wx",384),l.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),l.fsyncSync(i),l.closeSync(i),i=void 0,l.renameSync(o,t)}finally{i!==void 0&&l.closeSync(i);try{l.unlinkSync(o)}catch{}}}function w(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{V as MissingUserBoundaryError,ne as NativeFusionLocalRepository,P as NativeSourceDeletedError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadConfig as o,saveConfig as t}from"../config/index.js";const i=/^[a-f0-9]{64}$/;async function r(e){const c=await(e.fetchImpl??fetch)(`${e.serverUrl.replace(/\/+$/,"")}/api/machines/identity`,{headers:{Authorization:`Bearer ${e.machineToken}`},signal:AbortSignal.timeout(1e4)});if(!c.ok)throw new Error(`account_namespace_unavailable:${c.status}`);const a=await c.json();if(typeof a.machineId!="string"||!a.machineId||typeof a.accountNamespaceId!="string"||!i.test(a.accountNamespaceId))throw new Error("account_namespace_invalid_response");if(e.machineId&&a.machineId!==e.machineId)throw new Error("account_namespace_machine_mismatch");const n=o();return(n.accountNamespaceId!==a.accountNamespaceId||n.machineId!==a.machineId)&&t({...n,machineId:a.machineId,accountNamespaceId:a.accountNamespaceId}),a.accountNamespaceId}export{r as synchronizeActiveAccountNamespace};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import c from"node:crypto";import n from"node:fs";import s from"node:path";import{
|
|
1
|
+
import c from"node:crypto";import n from"node:fs";import s from"node:path";import{resolvePersonalSyncV1RootPath as d}from"./paths.js";function p(){const t=s.join(d(),"installation.json");if(n.existsSync(t)){const e=JSON.parse(n.readFileSync(t,"utf8"));if(e.schemaVersion!==1||!e.daemonInstallationId)throw new Error(`invalid daemon installation identity: ${t}`);return e.daemonInstallationId}const i={schemaVersion:1,daemonInstallationId:c.randomUUID(),createdAt:new Date().toISOString()};return l(t,i),i.daemonInstallationId}function l(t,i){const e=s.dirname(t);n.mkdirSync(e,{recursive:!0,mode:448});const r=`${t}.tmp-${process.pid}-${c.randomBytes(6).toString("hex")}`;let o;try{if(o=n.openSync(r,"wx",384),n.writeFileSync(o,`${JSON.stringify(i,null,2)}
|
|
2
2
|
`,"utf8"),n.fsyncSync(o),n.closeSync(o),o=void 0,n.renameSync(r,t),process.platform!=="win32"){const a=n.openSync(e,"r");try{n.fsyncSync(a)}finally{n.closeSync(a)}}}finally{o!==void 0&&n.closeSync(o);try{n.unlinkSync(r)}catch{}}}export{p as getDaemonInstallationId};
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
export declare const PERSONAL_SYNC_V1_ROOT_NAME = "personal-sync-v1";
|
|
2
|
+
export declare class PersonalSyncAccountNamespaceError extends Error {
|
|
3
|
+
readonly code = "account_namespace_required";
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export declare function resolvePersonalSyncV1RootPath(...parts: string[]): string;
|
|
7
|
+
export declare function getActivePersonalSyncAccountNamespaceId(): string;
|
|
2
8
|
export declare function resolvePersonalSyncV1Path(...parts: string[]): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import o from"node:path";import{loadConfig as r,resolveShennianPath as t}from"../config/index.js";const a="personal-sync-v1",c=/^[a-f0-9]{64}$/;class s extends Error{code="account_namespace_required";constructor(){super("personal sync account namespace is unavailable; pair this daemon again"),this.name="PersonalSyncAccountNamespaceError"}}function p(...e){return o.join(t(a),...e)}function i(){const n=(process.env.VITEST==="true"||process.env.VITEST_WORKER_ID?process.env.SHENNIAN_ACCOUNT_NAMESPACE_ID?.trim():void 0)||r().accountNamespaceId?.trim();if(!n||!c.test(n))throw new s;return n}function N(...e){return p("accounts",i(),...e)}export{a as PERSONAL_SYNC_V1_ROOT_NAME,s as PersonalSyncAccountNamespaceError,i as getActivePersonalSyncAccountNamespaceId,N as resolvePersonalSyncV1Path,p as resolvePersonalSyncV1RootPath};
|
|
@@ -29,6 +29,7 @@ export interface DeviceAuthorizationClient {
|
|
|
29
29
|
actionPayloadHash: string;
|
|
30
30
|
machineId?: string;
|
|
31
31
|
machineToken?: string;
|
|
32
|
+
accountNamespaceId?: string;
|
|
32
33
|
}>;
|
|
33
34
|
cancel(input: {
|
|
34
35
|
loginIntentId: string;
|
|
@@ -75,6 +76,7 @@ export declare class HttpDeviceAuthorizationClient implements DeviceAuthorizatio
|
|
|
75
76
|
actionPayloadHash: string;
|
|
76
77
|
machineId?: string;
|
|
77
78
|
machineToken?: string;
|
|
79
|
+
accountNamespaceId?: string;
|
|
78
80
|
}>;
|
|
79
81
|
cancel(input: {
|
|
80
82
|
loginIntentId: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import M from"node:crypto";import o from"node:fs";import
|
|
2
|
-
`),
|
|
3
|
-
`)){if(!
|
|
4
|
-
`)){if(!
|
|
1
|
+
import M from"node:crypto";import o from"node:fs";import c from"node:path";const v=1,H=64,b="mutations-000001.jsonl";class q extends Error{constructor(e,s){super(e,s),this.name="CanonicalStoreError"}}class w extends q{constructor(e,s){super(e,s),this.name="CanonicalStoreCorruptionError"}}class p extends q{constructor(e){super(e),this.name="CanonicalStoreConflictError"}}class K{rootDir;daemonInstallationId;mutationListeners=new Set;constructor(e,s){if(!s)throw new q("daemonInstallationId is required");this.rootDir=c.resolve(e),this.daemonInstallationId=s}commit(e){k(e);const s=this.sessionDir(e.sessionId);this.assertHealthy(s),o.mkdirSync(this.messageStateDir(s),{recursive:!0,mode:448});let t=this.loadManifestOrRebuild(e.sessionId);const n=this.readMessageState(s,e.messageId),a=z(e);if(n&&n.contentHash===a&&n.tombstoned===(e.mutation==="tombstone"))return{bodyRevision:t.bodyRevision,sequence:n.sequence,mutation:this.findMutationByRevision(e.sessionId,n.lastRevision),duplicate:!0};if(e.mutation==="append"&&n)throw new p(`message ${e.messageId} already exists in session ${e.sessionId}; use upsert`);const d=n?.sequence??t.nextSequence,h=t.bodyRevision+1,S=e.committedAt??new Date().toISOString(),y={schemaVersion:v,mutationId:T(e.sessionId,e.messageId,e.mutation,n?.contentHash??"none",a),sessionId:e.sessionId,revision:h,mutation:e.mutation,messageId:e.messageId,sequence:d,...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{},committedAt:S,contentHash:a},l=c.join(s,b),u=_(l,`${JSON.stringify(y)}
|
|
2
|
+
`),r={schemaVersion:v,sessionId:e.sessionId,messageId:e.messageId,sequence:d,lastRevision:h,contentHash:a,tombstoned:e.mutation==="tombstone",...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{}};I(this.messageStateFile(s,d,e.messageId),r);const f=this.readSparseIndex(s,e.sessionId);(h===1||(h-1)%H===0)&&(f.entries.push({revision:h,offset:u}),I(c.join(s,"sparse-index.json"),f)),t={...t,bodyRevision:h,nextSequence:n?t.nextSequence:d+1,firstAvailableSeq:t.firstAvailableSeq??d,lastAvailableSeq:Math.max(t.lastAvailableSeq??d,d),mutationCount:t.mutationCount+1,visibleMessageCount:t.visibleMessageCount+(!n&&e.mutation!=="tombstone"?1:0)-(n&&!n.tombstoned&&e.mutation==="tombstone"?1:0),lastVisibleMessageId:e.mutation==="tombstone"?t.lastVisibleMessageId===e.messageId?this.findLastVisibleMessageId(s,e.messageId):t.lastVisibleMessageId:e.messageId,lastBodyCommittedAt:S,logByteLength:o.statSync(l).size,updatedAt:S},I(c.join(s,"manifest.json"),t);for(const x of this.mutationListeners)try{x(y)}catch(j){console.error("[canonical-store] mutation listener failed",j)}return{bodyRevision:h,sequence:d,mutation:y,duplicate:!1}}subscribe(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)}append(e,s,t){return this.commit({sessionId:e,mutation:"append",messageId:s.id,message:s,deliveryState:t})}upsert(e,s,t){return this.commit({sessionId:e,mutation:"upsert",messageId:s.id,message:s,deliveryState:t})}tombstone(e,s,t){return this.commit({sessionId:e,mutation:"tombstone",messageId:s,deliveryState:t})}readRecent(e,s){return this.readPage(e,{limit:s})}readPage(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return B();const a=this.loadManifestOrRebuild(e),d=this.listMessageStateFiles(n).filter(l=>s.beforeSequence===void 0||l.sequence<s.beforeSequence).sort((l,u)=>u.sequence-l.sequence),h=[];let S=0;for(const l of d){S+=1;const u=D(c.join(this.messageStateDir(n),l.name));if(!u.tombstoned&&u.message&&h.push(u),h.length>=t)break}h.sort((l,u)=>l.sequence-u.sequence);const y=h.length>0&&d.slice(S).some(l=>{const u=D(c.join(this.messageStateDir(n),l.name));return!u.tombstoned&&!!u.message});return{bodyRevision:a.bodyRevision,firstAvailableSeq:a.firstAvailableSeq,lastAvailableSeq:a.lastAvailableSeq,hasOlder:y,messages:h.map(l=>l.message),entries:h.map(l=>({sequence:l.sequence,revision:l.lastRevision,message:l.message,...l.deliveryState?{deliveryState:l.deliveryState}:{}}))}}readPageAtRevision(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return B();const a=this.loadManifestOrRebuild(e);if(!Number.isSafeInteger(s.revision)||s.revision<0||s.revision>a.bodyRevision)throw new q("snapshot revision is unavailable");const d=new Map,h=L(c.join(n,b),0);for(const r of h.split(`
|
|
3
|
+
`)){if(!r)continue;const f=A(r,e);if(f.revision>s.revision)break;d.set(f.messageId,{schemaVersion:v,sessionId:e,messageId:f.messageId,sequence:f.sequence,lastRevision:f.revision,contentHash:f.contentHash,tombstoned:f.mutation==="tombstone",...f.message?{message:f.message}:{},...f.deliveryState?{deliveryState:f.deliveryState}:{}})}const S=[...d.values()].filter(r=>!r.tombstoned&&r.message&&(s.beforeSequence===void 0||r.sequence<s.beforeSequence)).sort((r,f)=>f.sequence-r.sequence),y=S.slice(0,t).sort((r,f)=>r.sequence-f.sequence),u=[...d.values()].filter(r=>!r.tombstoned&&r.message).map(r=>r.sequence);return{bodyRevision:s.revision,firstAvailableSeq:u.length?Math.min(...u):null,lastAvailableSeq:u.length?Math.max(...u):null,hasOlder:S.length>y.length,messages:y.map(r=>r.message),entries:y.map(r=>({sequence:r.sequence,revision:r.lastRevision,message:r.message,...r.deliveryState?{deliveryState:r.deliveryState}:{}}))}}readDeltas(e,s,t){if(!Number.isSafeInteger(s)||s<0)throw new q("afterRevision must be a non-negative safe integer");const n=O(t),a=this.sessionDir(e);if(!o.existsSync(a))return[];const d=this.loadManifestOrRebuild(e);if(s>=d.bodyRevision)return[];const h=this.readSparseIndex(a,e),S=s+1,y=[...h.entries].reverse().find(r=>r.revision<=S),l=L(c.join(a,b),y?.offset??0),u=[];for(const r of l.split(`
|
|
4
|
+
`)){if(!r)continue;const f=A(r,e);if(f.revision>s&&u.push(f),u.length>=n)break}return u}getManifest(e){const s=this.sessionDir(e);return o.existsSync(s)?this.loadManifestOrRebuild(e):null}getMessageState(e,s){const t=this.sessionDir(e);if(!o.existsSync(t))return null;this.loadManifestOrRebuild(e);const n=this.readMessageState(t,s);return n?{sequence:n.sequence,lastRevision:n.lastRevision,tombstoned:n.tombstoned,message:n.message,deliveryState:n.deliveryState}:null}listSessionIds(){const e=c.join(this.rootDir,"sessions");if(!o.existsSync(e))return[];const s=[];for(const t of o.readdirSync(e,{withFileTypes:!0})){if(!t.isDirectory())continue;const n=c.join(e,t.name,"manifest.json");o.existsSync(n)&&s.push(D(n).sessionId)}return s.sort()}purgeSession(e){if(!e)throw new q("sessionId is required");const s=c.resolve(this.rootDir,"sessions"),t=c.resolve(s,F(e));if(c.dirname(t)!==s)throw new q("invalid session path");o.rmSync(t,{recursive:!0,force:!0})}findLastVisibleMessageId(e,s){for(const t of this.listMessageStateFiles(e).sort((n,a)=>a.sequence-n.sequence)){const n=D(c.join(this.messageStateDir(e),t.name));if(n.messageId!==s&&!n.tombstoned&&n.message)return n.messageId}return null}rebuild(e){const s=this.sessionDir(e);this.assertHealthy(s),o.mkdirSync(s,{recursive:!0,mode:448});const t=c.join(s,"manifest.json");if(o.existsSync(t))try{const m=JSON.parse(o.readFileSync(t,"utf8"));if(m.daemonInstallationId&&m.daemonInstallationId!==this.daemonInstallationId)throw new p(`canonical session ${e} belongs to a different daemon installation`)}catch(m){if(m instanceof p)throw m}const n=c.join(s,b);if(!o.existsSync(n)){const m=P(e,this.daemonInstallationId);return I(c.join(s,"manifest.json"),m),I(c.join(s,"sparse-index.json"),C(e)),m}const a=o.readFileSync(n);let d=a.length;if(a.length>0&&a[a.length-1]!==10){const m=a.lastIndexOf(10);d=m<0?0:m+1,o.truncateSync(n,d)}const h=a.subarray(0,d).toString("utf8"),S=new Map,y=C(e);let l=1,u=0,r=new Date(0).toISOString();try{for(const m of h.match(/.*\n/g)??[]){const R=m.slice(0,-1);if(!R){u+=Buffer.byteLength(m);continue}const g=A(R,e);if(g.revision!==l)throw new w(`non-contiguous revision in ${e}: expected ${l}, got ${g.revision}`);(g.revision===1||(g.revision-1)%H===0)&&y.entries.push({revision:g.revision,offset:u});const V=S.get(g.messageId);if(V&&V.sequence!==g.sequence)throw new w(`sequence changed for message ${g.messageId}`);S.set(g.messageId,{schemaVersion:v,sessionId:e,messageId:g.messageId,sequence:g.sequence,lastRevision:g.revision,contentHash:g.contentHash,tombstoned:g.mutation==="tombstone",...g.message?{message:g.message}:{},...g.deliveryState?{deliveryState:g.deliveryState}:{}}),l+=1,u+=Buffer.byteLength(m),r=g.committedAt}}catch(m){this.quarantineCorruptSegment(e,n,m)}const f=this.messageStateDir(s);o.rmSync(f,{recursive:!0,force:!0}),o.mkdirSync(f,{recursive:!0,mode:448});for(const m of S.values())I(this.messageStateFile(s,m.sequence,m.messageId),m);I(c.join(s,"sparse-index.json"),y);const x=[...S.values()].map(m=>m.sequence),j=[...S.values()].filter(m=>!m.tombstoned&&!!m.message).sort((m,R)=>m.sequence-R.sequence),$=l-1,N={schemaVersion:v,daemonInstallationId:this.daemonInstallationId,sessionId:e,bodyRevision:$,nextSequence:x.length===0?1:Math.max(...x)+1,firstAvailableSeq:x.length===0?null:Math.min(...x),lastAvailableSeq:x.length===0?null:Math.max(...x),mutationCount:$,visibleMessageCount:j.length,lastVisibleMessageId:j.at(-1)?.messageId??null,lastBodyCommittedAt:$>0?r:null,segment:b,logByteLength:d,updatedAt:r};return I(c.join(s,"manifest.json"),N),N}loadManifestOrRebuild(e){const s=this.sessionDir(e);this.assertHealthy(s);const t=c.join(s,"manifest.json"),n=c.join(s,b);if(!o.existsSync(t))return this.rebuild(e);try{const a=D(t);J(a,e,this.daemonInstallationId);const d=o.existsSync(n)?o.statSync(n).size:0;return a.logByteLength===d?a:this.rebuild(e)}catch(a){if(a instanceof p)throw a;return this.rebuild(e)}}findMutationByRevision(e,s){const t=this.readDeltas(e,s-1,1)[0];if(!t||t.revision!==s)throw new w(`missing mutation revision ${s} in ${e}`);return t}readSparseIndex(e,s){const t=c.join(e,"sparse-index.json");if(!o.existsSync(t))return C(s);const n=D(t);if(n.schemaVersion!==v||n.sessionId!==s||n.segment!==b)throw new w(`invalid sparse index for session ${s}`);return n}readMessageState(e,s){const t=`.${E(s)}.json`,n=this.listMessageStateFiles(e).find(d=>d.name.endsWith(t));if(!n)return null;const a=D(c.join(this.messageStateDir(e),n.name));if(a.messageId!==s)throw new w(`message state hash collision for ${s}`);return a}listMessageStateFiles(e){const s=this.messageStateDir(e);return o.existsSync(s)?o.readdirSync(s).map(t=>({name:t,match:/^(\d{16})\.[a-f0-9]{32}\.json$/.exec(t)})).filter(t=>!!t.match).map(t=>({name:t.name,sequence:Number(t.match[1])})):[]}quarantineCorruptSegment(e,s,t){const n=c.join(this.rootDir,"quarantine");o.mkdirSync(n,{recursive:!0,mode:448});const a=c.join(n,`${F(e)}-${Date.now()}-${b}`);throw o.renameSync(s,a),I(c.join(this.sessionDir(e),"CORRUPT.json"),{schemaVersion:v,sessionId:e,quarantined:a,detectedAt:new Date().toISOString(),reason:t instanceof Error?t.message:String(t)}),new w(`canonical segment for ${e} was quarantined at ${a}`,{cause:t})}assertHealthy(e){const s=c.join(e,"CORRUPT.json");if(o.existsSync(s))throw new w(`canonical session is quarantined: ${s}`)}sessionDir(e){return c.join(this.rootDir,"sessions",F(e))}messageStateDir(e){return c.join(e,"messages")}messageStateFile(e,s,t){return c.join(this.messageStateDir(e),`${String(s).padStart(16,"0")}.${E(t)}.json`)}}function k(i){if(!i.sessionId||!i.messageId)throw new q("sessionId and messageId are required");if(i.message&&(i.message.id!==i.messageId||i.message.sessionId!==i.sessionId))throw new q("message identity does not match commit identity");if(i.mutation!=="tombstone"&&!i.message)throw new q(`${i.mutation} mutation requires a message`)}function J(i,e,s){if(i.daemonInstallationId!==s)throw new p(`canonical session ${e} belongs to a different daemon installation`);if(i.schemaVersion!==v||i.sessionId!==e||i.segment!==b||!Number.isSafeInteger(i.bodyRevision)||i.bodyRevision<0)throw new Error(`invalid canonical manifest for ${e}`)}function A(i,e){let s;try{s=JSON.parse(i)}catch(t){throw new w(`malformed canonical mutation in ${e}`,{cause:t})}if(s.schemaVersion!==v||s.sessionId!==e||!Number.isSafeInteger(s.revision)||s.revision<1||!Number.isSafeInteger(s.sequence)||s.sequence<1||!["append","upsert","tombstone"].includes(s.mutation))throw new w(`invalid canonical mutation in ${e}`);return s}function P(i,e){return{schemaVersion:v,daemonInstallationId:e,sessionId:i,bodyRevision:0,nextSequence:1,firstAvailableSeq:null,lastAvailableSeq:null,mutationCount:0,visibleMessageCount:0,lastVisibleMessageId:null,lastBodyCommittedAt:null,segment:b,logByteLength:0,updatedAt:new Date(0).toISOString()}}function C(i){return{schemaVersion:v,sessionId:i,segment:b,entries:[]}}function B(){return{bodyRevision:0,firstAvailableSeq:null,lastAvailableSeq:null,hasOlder:!1,messages:[],entries:[]}}function O(i){if(!Number.isSafeInteger(i)||i<1||i>500)throw new q("limit must be an integer between 1 and 500");return i}function F(i){return M.createHash("sha256").update(i).digest("hex")}function E(i){return M.createHash("sha256").update(i).digest("hex").slice(0,32)}function z(i){return M.createHash("sha256").update(JSON.stringify({mutation:i.mutation,message:i.message??null,deliveryState:i.deliveryState??null})).digest("hex")}function T(i,e,s,t,n){return M.createHash("sha256").update(`${i}\0${e}\0${s}\0${t}\0${n}`).digest("hex")}function _(i,e){o.mkdirSync(c.dirname(i),{recursive:!0,mode:448});const s=o.openSync(i,"a",384);try{const t=o.fstatSync(s).size;return o.writeFileSync(s,e,"utf8"),o.fsyncSync(s),t}finally{o.closeSync(s)}}function I(i,e){const s=c.dirname(i);o.mkdirSync(s,{recursive:!0,mode:448});const t=`${i}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let n;try{n=o.openSync(t,"wx",384),o.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
5
5
|
`,"utf8"),o.fsyncSync(n),o.closeSync(n),n=void 0,o.renameSync(t,i),U(s)}finally{n!==void 0&&o.closeSync(n);try{o.unlinkSync(t)}catch{}}}function U(i){if(process.platform==="win32")return;const e=o.openSync(i,"r");try{o.fsyncSync(e)}finally{o.closeSync(e)}}function D(i){try{return JSON.parse(o.readFileSync(i,"utf8"))}catch(e){throw new w(`failed to read canonical JSON file ${i}`,{cause:e})}}function L(i,e){const s=o.openSync(i,"r");try{const t=o.fstatSync(s).size;if(e>=t)return"";const n=Buffer.allocUnsafe(t-e);return o.readSync(s,n,0,n.length,e),n.toString("utf8")}finally{o.closeSync(s)}}export{K as CanonicalSessionStore,p as CanonicalStoreConflictError,w as CanonicalStoreCorruptionError,q as CanonicalStoreError};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import c from"node:fs";import{randomUUID as p}from"node:crypto";import{buildUserMessagePayload as R}from"@shennian/wire";import{mergeProjectedSessions as T}from"./projection.js";import{parsePersonalAttachmentReferences as m}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"});const
|
|
1
|
+
import c from"node:fs";import{randomUUID as p}from"node:crypto";import{buildUserMessagePayload as R}from"@shennian/wire";import{mergeProjectedSessions as T}from"./projection.js";import{parsePersonalAttachmentReferences as m}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as S,markAttachmentReferences as k}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as q}from"../personal-sync/paths.js";import{appendMessage as b,getMessageDeliveryStatus as v,recordSession as E,tombstoneMessage as P,upsertMessage as w}from"./store.js";import{requestSessionIndexPublish as y}from"./index-publisher.js";function A(){return{sessions:{}}}function I(){return new Date().toISOString()}function $(s){return`enqueue-send-${s}-${p()}`}function l(){try{const s=JSON.parse(c.readFileSync(D(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return A();throw s}}function h(s){const e=q();c.mkdirSync(e,{recursive:!0,mode:448});const n=D(),t=`${n}.tmp-${process.pid}-${p()}`;try{c.writeFileSync(t,`${JSON.stringify(s,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"});const i=c.openSync(t,"r");if(typeof i=="number")try{c.fsyncSync(i)}finally{c.closeSync(i)}c.renameSync(t,n)}finally{try{c.unlinkSync(t)}catch{}}}function D(){return q("outbound-queue.json")}function Q(s){const e=I();return{id:s.queueMessageId||s.clientMessageId||`queue-${p()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:m(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function M(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:R(s.text,s.attachments)}}class H{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:l().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(T(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),o=m(t.attachments);o?.length&&S(i,o);const r={text:t.text,attachments:o,localized:!1},d=await this.isSessionBusy(t.sessionId),u=l(),f=u.sessions[t.sessionId]??[];if(!d&&!f.length){await this.opts.dispatchReq({...e,id:$(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const a=Q({...t,text:r.text,attachments:r.attachments}),g=t.origin==="external"?C(f,a):null;g||(u.sessions[t.sessionId]=[...f,a],h(u),E({sessionId:a.sessionId,agentType:a.agentType,sessionMode:a.sessionMode,managerConfig:a.managerConfig,workDir:a.workDir,agentSessionId:a.agentSessionId,modelId:a.modelId}),b(a.sessionId,M(a),"queued"),k(i,a.attachments??[]),y(n.client)),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??a.id,...v(t.sessionId,g?.clientMessageId??g?.id??a.clientMessageId??a.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),d||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=l(),o=i.sessions[t.sessionId]??[],r=o.findIndex(f=>f.id===t.queueMessageId);if(r<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const d=m(t.attachments),u=n.resolvePath(o[r].workDir);d?.length&&S(u,d),o[r]={...o[r],text:t.text,attachments:d,updatedAt:I()},i.sessions[t.sessionId]=o,h(i),w(t.sessionId,M(o[r]),"queued"),o[r].attachments?.length&&k(u,o[r].attachments??[]),y(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=l(),o=i.sessions[t.sessionId]??[],r=o.find(u=>u.id===t.queueMessageId),d=o.filter(u=>u.id!==t.queueMessageId);if(d.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}d.length?i.sessions[t.sessionId]=d:delete i.sessions[t.sessionId],h(i),P(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),y(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){const n=l();n.sessions[e]&&(delete n.sessions[e],h(n),this.draining.delete(e))}drainIdleQueues(){const e=l();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(l().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{w(e,M(o),"queued"),await this.dispatchQueuedMessage(o);const r=l(),d=(r.sessions[e]??[]).filter(u=>u.id!==i.id);d.length?r.sessions[e]=d:delete r.sessions[e],h(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=W(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
|
|
3
3
|
|
|
4
|
-
`),attachments:B(i.flatMap(o=>o.attachments??[])),updatedAt:
|
|
5
|
-
`)}function N(
|
|
4
|
+
`),attachments:B(i.flatMap(o=>o.attachments??[])),updatedAt:I()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function W(s){const e=new Set,n=[];for(const t of s){const i=x(t);e.has(i)||(e.add(i),n.push(t))}return n}function C(s,e){const n=x(e);return s.find(t=>x(t)===n)??null}function x(s){const e=N(s);return e?`id:${e}`:["body",j(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
|
|
5
|
+
`)}function N(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const n=typeof s.id=="string"?s.id.trim():"";return n&&!n.startsWith("queue-")?n:""}function B(s){const e=new Set,n=[];for(const t of s){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),n.push(t))}return n.length?n:void 0}function j(s){return String(s||"").replace(/\s+/g," ").trim()}export{H as ChatQueueManager};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import x from"node:crypto";import
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import x from"node:crypto";import l from"node:fs";import S from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as w,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as M}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as p,resolvePersonalSyncV1Path as k}from"../personal-sync/paths.js";import{CanonicalSessionStore as C}from"./canonical-store.js";import{getDaemonInstallationId as d}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as O}from"../native-fusion/native-source-deletions.js";let m=null,I=null;function v(){return k()}function a(){const t=S.join(v(),"canonical");return(!m||I!==t)&&(m=new C(t,d()),I=t),m}function R(){return S.join(v(),"session-index.json")}function u(){const t=R();if(!l.existsSync(t))return{};const e=JSON.parse(l.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const i=d();for(const n of Object.values(e))if(n.daemonInstallationId!==i)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function f(t){B(R(),t)}function E(t){return M(t.payload,120)}function g(t){if(new O({rootDir:v(),daemonInstallationId:d()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function q(t){g(t.sessionId),d();const e=u(),i=e[t.sessionId],n=new Date().toISOString(),s=a().getManifest(t.sessionId)?.bodyRevision??i?.bodyRevision??0,o=a().getManifest(t.sessionId),r=h(i,t),c={daemonInstallationId:d(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??i?.sessionMode,managerConfig:t.managerConfig??i?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??i?.agentSessionId??null,modelId:t.modelId??i?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??i?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??i?.managerDefaultWorkerModelId??null,status:t.status??i?.status??"active",createdAt:i?.createdAt??n,updatedAt:n,lastActivityAt:t.lastActivityAt??i?.lastActivityAt??n,lastMessagePreview:t.lastMessagePreview??i?.lastMessagePreview??null,bodyRevision:s,indexRevision:(i?.indexRevision??0)+1,...r,firstAvailableSeq:o?.firstAvailableSeq??i?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??i?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??i?.lastVisibleMessageId??null,lastBodyCommittedAt:o?.lastBodyCommittedAt??i?.lastBodyCommittedAt??null,deletionRevision:i?.deletionRevision??0,deletedAt:t.deletedAt??i?.deletedAt??null,syncedIndexRevision:i?.syncedIndexRevision??0};return e[t.sessionId]=c,f(e),c}function D(){return Object.values(u())}function $(t){if(!t)throw new Error("sessionId is required");const e=u();e[t]&&(delete e[t],f(e))}function F(t){a().purgeSession(t)}function j(){const t=d();return D().filter(e=>e.indexRevision>e.syncedIndexRevision).sort((e,i)=>e.indexRevision-i.indexRevision).map(e=>({protocolVersion:w,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:e.lastMessagePreview??null,bodyStorageMode:"machine_local",bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}))}function J(t,e){const i=u(),n=i[t];if(!(!n||e<=n.syncedIndexRevision)){if(e>n.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);i[t]={...n,syncedIndexRevision:e},f(i)}}function Y(t,e,i){g(t),d();const n=a().append(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function z(t,e,i){g(t),d();const n=a().upsert(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function G(t,e){const i=a().getMessageState(t,e);return!i||i.tombstoned||!i.message||!i.deliveryState?{state:"not_found",sessionId:t,clientMessageId:e}:{state:i.deliveryState,sessionId:t,clientMessageId:e,messageId:i.message.id,deliveryState:i.deliveryState,bodyRevision:i.lastRevision,committedAt:new Date(i.message.ts).toISOString()}}function H(t,e){g(t),d();const i=a().tombstone(t,e),n=u(),s=n[t];return s&&(n[t]={...s,bodyRevision:i.bodyRevision,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString()},f(n)),i.bodyRevision}function A(t,e,i){const n=u(),s=n[t];if(!s)return;const o=E(e),r=a().getManifest(t),c=e.role==="user"&&o&&!s.title?{title:o,titleSource:"first_user_fallback",titleRevision:s.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null;n[t]={...s,...c??{},bodyRevision:i,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:o??s.lastMessagePreview??null,firstAvailableSeq:r?.firstAvailableSeq??s.firstAvailableSeq,visibleMessageCount:r?.visibleMessageCount??s.visibleMessageCount,lastVisibleMessageId:r?.lastVisibleMessageId??s.lastVisibleMessageId,lastBodyCommittedAt:r?.lastBodyCommittedAt??s.lastBodyCommittedAt},f(n)}function h(t,e){const i={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},n=e.title?.trim()??"";if(!n)return i;if(!e.titleSource)throw new Error("titleSource is required with title");const s=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==s)throw new Error("titleLockedByUser conflicts with titleSource");const o=n===i.title&&e.titleSource===i.titleSource,r=e.titleRevision??i.titleRevision+(o?0:1);if(!Number.isSafeInteger(r)||r<0)throw new Error("invalid titleRevision");const c=y[e.titleSource],b=y[i.titleSource];return c<b||r<i.titleRevision||r===i.titleRevision&&!o?i:{title:n,titleSource:e.titleSource,titleRevision:r,titleLockedByUser:s,titleOriginEventId:e.titleOriginEventId??i.titleOriginEventId}}function K(t,e){const i=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let s=a().readRecent(t,i).messages;return e?.before!==void 0&&(s=s.filter(o=>o.ts<e.before)),[...s].sort((o,r)=>r.ts-o.ts)}function Q(){return a().listSessionIds()}function X(){return a()}function B(t,e){const i=S.dirname(t);l.mkdirSync(i,{recursive:!0,mode:448});const n=`${t}.tmp-${process.pid}-${x.randomBytes(6).toString("hex")}`;let s;try{if(s=l.openSync(n,"wx",384),l.writeFileSync(s,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),l.fsyncSync(s),l.closeSync(s),s=void 0,l.renameSync(n,t),process.platform!=="win32"){const o=l.openSync(i,"r");try{l.fsyncSync(o)}finally{l.closeSync(o)}}}finally{s!==void 0&&l.closeSync(s);try{l.unlinkSync(n)}catch{}}}export{p as PERSONAL_SYNC_V1_ROOT_NAME,J as acknowledgeSessionIndexUpdate,Y as appendMessage,X as getCanonicalSessionStore,d as getDaemonInstallationId,G as getMessageDeliveryStatus,j as listPendingSessionIndexUpdates,D as listSessionRecords,Q as listSessions,F as purgeCanonicalSession,$ as purgeSessionRecord,K as readMessages,q as recordSession,H as tombstoneMessage,z as upsertMessage};
|
|
@@ -58,4 +58,13 @@ export declare function isCodexProjectInstructionsPayload(payload: string): bool
|
|
|
58
58
|
export declare function isSystemControlPayload(payload: string): boolean;
|
|
59
59
|
export declare function stripGitDirectiveArtifacts(text: string): string;
|
|
60
60
|
export declare function extractPayloadText(payload: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Converts already-readable text into the only form allowed in the replicated
|
|
63
|
+
* personal-session index. A preview is content, not an opaque transport field:
|
|
64
|
+
* private control context and binary payloads are therefore rejected instead
|
|
65
|
+
* of being truncated into something that still leaks them.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sanitizeSessionPreview(value: string | null | undefined, maxLength?: number): string | null;
|
|
68
|
+
/** Extracts a safe, bounded preview from an envelope payload. */
|
|
69
|
+
export declare function sessionPreviewFromPayload(payload: string, maxLength?: number): string | null;
|
|
61
70
|
export {};
|
|
@@ -7,6 +7,20 @@ const ENVIRONMENT_CONTEXT_REQUIRED_TAG_RE = /<(cwd|shell|current_date|timezone)>
|
|
|
7
7
|
const CODEX_TURN_ABORTED_RE = /^\s*<turn_aborted>[^]*?<\/turn_aborted>\s*$/i;
|
|
8
8
|
const CODEX_PROJECT_INSTRUCTIONS_RE = /^\s*# AGENTS\.md instructions for [^\n]+\n+\s*<INSTRUCTIONS>\s*[^]*?<\/INSTRUCTIONS>\s*(?:<environment_context>[^]*?<\/environment_context>)?\s*$/i;
|
|
9
9
|
const APPROVAL_PENDING_PAYLOAD_PREFIX_RE = /^\s*\{\s*"v"\s*:\s*1\s*,\s*"type"\s*:\s*"approval_pending"/;
|
|
10
|
+
const PREVIEW_PRIVATE_CONTEXT_RE = /(?:<\/?(?:environment_context|instructions|system|developer|codex_internal_context|recommended_plugins)\b|#\s*AGENTS\.md instructions\b)/i;
|
|
11
|
+
const PREVIEW_DATA_URL_RE = /\bdata:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[a-z0-9+/=\s]+/i;
|
|
12
|
+
const PREVIEW_BASE64_BLOB_RE = /(?:^|\s)(?=[A-Za-z0-9+/]{80,}={0,2}(?=\s|$))(?=[A-Za-z0-9+/]*[A-Z])(?=[A-Za-z0-9+/]*[a-z])(?=[A-Za-z0-9+/]*[0-9])[A-Za-z0-9+/]{80,}={0,2}(?=\s|$)/;
|
|
13
|
+
const PREVIEW_HIDDEN_TYPES = new Set([
|
|
14
|
+
'tool',
|
|
15
|
+
'tool_use',
|
|
16
|
+
'tool_result',
|
|
17
|
+
'thinking',
|
|
18
|
+
'system',
|
|
19
|
+
'developer',
|
|
20
|
+
'context',
|
|
21
|
+
'environment_context',
|
|
22
|
+
'channel_delivery_status',
|
|
23
|
+
]);
|
|
10
24
|
function escapeMarkdownLabel(label) {
|
|
11
25
|
return label.replace(/[[\]\\]/g, '\\$&');
|
|
12
26
|
}
|
|
@@ -234,6 +248,45 @@ export function extractPayloadText(payload) {
|
|
|
234
248
|
}
|
|
235
249
|
return stripGitDirectiveArtifacts(payload);
|
|
236
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Converts already-readable text into the only form allowed in the replicated
|
|
253
|
+
* personal-session index. A preview is content, not an opaque transport field:
|
|
254
|
+
* private control context and binary payloads are therefore rejected instead
|
|
255
|
+
* of being truncated into something that still leaks them.
|
|
256
|
+
*/
|
|
257
|
+
export function sanitizeSessionPreview(value, maxLength = 160) {
|
|
258
|
+
if (typeof value !== 'string' || !Number.isSafeInteger(maxLength) || maxLength < 1)
|
|
259
|
+
return null;
|
|
260
|
+
const normalized = value.replace(/\r\n/g, '\n').trim();
|
|
261
|
+
if (!normalized)
|
|
262
|
+
return null;
|
|
263
|
+
if (PREVIEW_PRIVATE_CONTEXT_RE.test(normalized) ||
|
|
264
|
+
PREVIEW_DATA_URL_RE.test(normalized) ||
|
|
265
|
+
PREVIEW_BASE64_BLOB_RE.test(normalized) ||
|
|
266
|
+
parseObjectPayload(normalized))
|
|
267
|
+
return null;
|
|
268
|
+
const oneLine = normalized.replace(/\s+/g, ' ');
|
|
269
|
+
return oneLine.length > maxLength ? `${oneLine.slice(0, maxLength)}…` : oneLine;
|
|
270
|
+
}
|
|
271
|
+
/** Extracts a safe, bounded preview from an envelope payload. */
|
|
272
|
+
export function sessionPreviewFromPayload(payload, maxLength = 160) {
|
|
273
|
+
if (!payload || isToolPayload(payload) || isSystemControlPayload(payload))
|
|
274
|
+
return null;
|
|
275
|
+
if (isApprovalPendingPayload(payload))
|
|
276
|
+
return sanitizeSessionPreview('需要用户确认', maxLength);
|
|
277
|
+
const structured = parseStructuredMessagePayload(payload);
|
|
278
|
+
if (structured) {
|
|
279
|
+
const type = typeof structured.type === 'string' ? structured.type.toLowerCase() : '';
|
|
280
|
+
if (!type || PREVIEW_HIDDEN_TYPES.has(type))
|
|
281
|
+
return null;
|
|
282
|
+
return sanitizeSessionPreview(extractPayloadText(payload), maxLength);
|
|
283
|
+
}
|
|
284
|
+
// JSON objects that are not a known structured message can contain raw tool
|
|
285
|
+
// arguments/results. Never guess which property is safe for an index preview.
|
|
286
|
+
if (parseObjectPayload(payload))
|
|
287
|
+
return null;
|
|
288
|
+
return sanitizeSessionPreview(extractPayloadText(payload), maxLength);
|
|
289
|
+
}
|
|
237
290
|
function stringOrUndefined(value) {
|
|
238
291
|
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
239
292
|
}
|