shennian 0.3.65 → 0.3.66

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.
@@ -186,8 +186,8 @@
186
186
  },
187
187
  {
188
188
  "file": "src/commands/pair.js",
189
- "beforeBytes": 15955,
190
- "afterBytes": 8988
189
+ "beforeBytes": 16410,
190
+ "afterBytes": 9073
191
191
  },
192
192
  {
193
193
  "file": "src/commands/room.js",
@@ -626,8 +626,8 @@
626
626
  },
627
627
  {
628
628
  "file": "src/session/store.js",
629
- "beforeBytes": 25840,
630
- "afterBytes": 13364
629
+ "beforeBytes": 26645,
630
+ "afterBytes": 13781
631
631
  },
632
632
  {
633
633
  "file": "src/session/tool-detail-store.js",
@@ -1,12 +1,12 @@
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(`
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 x,saveEnvSnapshot as O}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";import{markAllSessionIndexesForRepublish as z}from"../session/store.js";const K=3e3,U=600*1e3;function l(o){process.stdout.write(`${JSON.stringify(o)}
2
+ `)}function f(o,e){l({type:"error",code:o,message:e}),process.exit(1)}function W(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 Y(o){const e=(await import("qrcode-terminal")).default;return new Promise(n=>{e.generate(o,F,i=>{console.log(i),n()})})}async function H(o,e,n={}){const i=Date.now()+U;for(n.json&&l({type:"pair.waiting"});Date.now()<i;){await new Promise(d=>setTimeout(d,K));let a;try{a=await fetch(`${o}/api/machines/pair/status?token=${encodeURIComponent(e)}`)}catch{continue}if(a.status===410&&(n.json?f("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&&l({type:"pair.claimed"}),{machineToken:t.machineToken,machineId:t.machineId,machineName:t.machineName??"",accountNamespaceId:t.accountNamespaceId}}return null}function G(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?l({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(),g=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: ${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};
5
+ `)),console.log(r.gray(` Machine: ${n} | OS: ${g} | 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:g,agentList:c,signal:p.signal,openUrl:o.openUrl,emit:T=>{a?l(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?l({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&&f(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:g,agentList:c,daemonInstallationId:D()})})}catch{const p=`Network error: cannot connect to ${e}`;a&&f("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&&f("PAIR_CREATE_FAILED",w),console.error(r.red(`\u2717 ${w}`)),process.exit(1)}const I=await y.json(),_=M(I.pairToken);a?l({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 Y(_),W(I.pairToken),console.log(),console.log(r.gray("Waiting for pairing... (Ctrl+C to cancel)")));const h=await H(e,I.pairToken,{json:a});h||(a&&f("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}),o.force&&z(),a&&l({type:"pair.saved",machineId:h.machineId}),a||console.log(r.green(`
10
+ \u2713 Paired successfully! Machine "${h.machineName}" is now linked.`))}async function V(o){const e=u(),i=E().map(m=>m.type),a=k.platform(),t=a==="win32"?"windows":a==="darwin"?"macos":"linux",d=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(!d.ok){const g=(await d.json().catch(()=>({}))).error??d.statusText;throw new Error(`Failed to register desktop machine: ${g}`)}const c=await d.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 X(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 me(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})`));O(),A(),console.log(r.gray(`
11
+ Starting background service...`)),x()||b()}function pe(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 X(e.api),a=await V({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&&f("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){l({type:"done",status:"already-paired",machineId:n.machineId});return}if(console.log(r.yellow(`Currently paired (machine ID: ${n.machineId})`)),!await G("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}),O(),A(),e.json?l({type:"daemon.starting"}):console.log(r.gray(`
12
+ Starting background service...`)),x()||b({quiet:!!e.json}),e.json&&(B(()=>L({cleanupStale:!0})),l({type:"done",status:"paired",machineId:u().machineId}))})}export{V as registerDesktopMachine,pe as registerPairCommand,$ as runPairFlow,me as runSmartStart};
@@ -84,6 +84,7 @@ export declare function purgeSessionRecord(sessionId: string): void;
84
84
  export declare function purgeCanonicalSession(sessionId: string): void;
85
85
  export declare function listPendingSessionIndexUpdates(): SessionIndexUpdate[];
86
86
  export declare function acknowledgeSessionIndexUpdate(sessionId: string, indexRevision: number): void;
87
+ export declare function markAllSessionIndexesForRepublish(): number;
87
88
  export declare function recordSessionIndexRejection(input: {
88
89
  sessionId: string;
89
90
  indexRevision: number;
@@ -1,2 +1,2 @@
1
- import p from"node:crypto";import d from"node:fs";import I from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as x,SESSION_SYNC_DATA_EPOCH as A,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as D}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as B}from"../native-fusion/native-source-deletions.js";let w=null,C=null;function M(){return D()}function u(){const t=I.join(M(),"canonical");return(!w||C!==t)&&(w=new _(t,c()),C=t),w}function P(){return I.join(M(),"session-index.json")}function g(){const t=P();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=c();for(const i of Object.values(e))if(i.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function v(t){V(P(),t)}function O(t){return b(t.payload,120)}function m(t){if(new B({rootDir:M(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function T(t){m(t.sessionId),c();const e=g(),n=e[t.sessionId],i=new Date().toISOString(),s=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,o=u().getManifest(t.sessionId),l=q(n,t),a=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),S=!!(a&&!r.includes(a)),f=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},R={daemonInstallationId:c(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??i,updatedAt:i,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??i,lastMessagePreview:f.preview,lastMessagePreviewMessageId:f.messageId,lastMessagePreviewAt:f.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:s,indexRevision:(n?.indexRevision??0)+1,...l,firstAvailableSeq:o?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(S?1:0),lastAttentionId:S?a:n?.lastAttentionId??null,seenAttentionIds:S&&a?[...r,a]:r,lastBodyCommittedAt:o?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:n?.indexQuarantineCode??null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt??null};return e[t.sessionId]=R,v(e),R}function U(){return Object.values(g())}function H(t){if(!t)throw new Error("sessionId is required");const e=g();e[t]&&(delete e[t],v(e))}function z(t){u().purgeSession(t)}function K(){const t=c();return L(),U().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:x,dataEpoch:A,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:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function X(t,e){const n=g(),i=n[t];if(!(!i||e<=i.syncedIndexRevision)){if(e>i.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...i,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},v(n)}}function Z(t){const e=g(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const s=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let o=t.disposition;o==="retry"&&s>=(t.maxAttempts??8)&&(o="terminal");const l=t.now??Date.now(),a=Math.min(5e3*2**Math.max(0,s-1),360*60*1e3),r=Math.round(a*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:o==="retry"?"pending":o,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:s,indexSyncNextAttemptAt:o==="retry"?new Date(l+a+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:o==="quarantined"?t.code:n.indexQuarantineCode??null},v(e),o}const N=2*6e4;function ee(t=Date.now(),e=N){const n=g(),i=[],s=new Date(t).toISOString();for(const o of Object.values(n)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim())continue;const l=o.status==="completed"||o.status==="failed",a=o.visibleMessageCount>=2;let r=null;if(o.agentType==="claude"&&a?r="agent_native_unavailable":l?r="terminal":a&&t-Date.parse(o.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const S=b(o.title,240);if(!S)continue;const f=p.randomUUID();n[o.sessionId]={...o,titleGenerationAttemptedAt:s},i.push({sessionId:o.sessionId,requestId:f,titleSeed:S,baseTitleRevision:o.titleRevision,eligibility:r})}return i.length>0&&v(n),i}function te(t,e){const i=g()[t];return!i||i.titleLockedByUser||y[i.titleSource]>=y.shennian_generated||e.titleRevision<=i.titleRevision?!1:(T({sessionId:t,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,status:i.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function ne(t,e,n){m(t),c();const i=u().append(t,e,n);return E(t,e,i.bodyRevision),i.bodyRevision}function ie(t,e,n){m(t),c();const i=u().upsert(t,e,n);return E(t,e,i.bodyRevision),i.bodyRevision}function se(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:x,dataEpoch:A,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:x,dataEpoch:A,daemonInstallationId:c(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function oe(t,e){m(t),c();const n=u().tombstone(t,e),i=g(),s=i[t];if(s){const o=e===s.lastMessagePreviewMessageId?h(t):{preview:s.lastMessagePreview??null,messageId:s.lastMessagePreviewMessageId??null,at:s.lastMessagePreviewAt??null};i[t]={...s,bodyRevision:n.bodyRevision,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at},v(i)}return n.bodyRevision}function E(t,e,n){const i=g(),s=i[t];if(!s)return;const o=O(e),l=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,a=u().getManifest(t),r=e.role==="user"&&o&&!s.title?{title:o,titleSource:"first_user_fallback",titleRevision:s.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,S=e.role==="user"&&!s.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;i[t]={...s,...r??{},...S??{},bodyRevision:n,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...l??{},firstAvailableSeq:a?.firstAvailableSeq??s.firstAvailableSeq,visibleMessageCount:a?.visibleMessageCount??s.visibleMessageCount,lastVisibleMessageId:a?.lastVisibleMessageId??s.lastVisibleMessageId,attentionCount:s.attentionCount??0,lastAttentionId:s.lastAttentionId??null,lastBodyCommittedAt:a?.lastBodyCommittedAt??s.lastBodyCommittedAt},v(i)}function h(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let i=n.entries.length-1;i>=0;i-=1){const s=n.entries[i],o=O(s.message);if(o)return{preview:o,messageId:s.message.id,at:new Date(s.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function L(){const t=g();let e=!1;const n=new Date().toISOString();for(const[i,s]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(s,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(s,"lastMessagePreviewAt"))continue;const o=h(i);t[i]={...s,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&v(t)}function q(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},i=e.title?.trim()??"";if(!i)return n;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=i===n.title&&e.titleSource===n.titleSource,l=e.titleRevision??n.titleRevision+(o?0:1);if(!Number.isSafeInteger(l)||l<0)throw new Error("invalid titleRevision");const a=y[e.titleSource],r=y[n.titleSource];return a<r||l<n.titleRevision||o&&l===n.titleRevision||l===n.titleRevision&&!o?n:{title:i,titleSource:e.titleSource,titleRevision:l,titleLockedByUser:s,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function ae(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let s=u().readRecent(t,n).messages;return e?.before!==void 0&&(s=s.filter(o=>o.ts<e.before)),[...s].sort((o,l)=>l.ts-o.ts)}function le(){return u().listSessionIds()}function re(){return u()}function V(t,e){const n=I.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const i=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let s;try{if(s=d.openSync(i,"wx",384),d.writeFileSync(s,`${JSON.stringify(e,null,2)}
2
- `,"utf8"),d.fsyncSync(s),d.closeSync(s),s=void 0,d.renameSync(i,t),process.platform!=="win32"){const o=d.openSync(n,"r");try{d.fsyncSync(o)}finally{d.closeSync(o)}}}finally{s!==void 0&&d.closeSync(s);try{d.unlinkSync(i)}catch{}}}export{k as PERSONAL_SYNC_V1_ROOT_NAME,X as acknowledgeSessionIndexUpdate,ne as appendMessage,te as applyGeneratedSessionTitle,ee as claimSessionTitleGenerationCandidates,re as getCanonicalSessionStore,c as getDaemonInstallationId,se as getMessageDeliveryStatus,K as listPendingSessionIndexUpdates,U as listSessionRecords,le as listSessions,z as purgeCanonicalSession,H as purgeSessionRecord,ae as readMessages,T as recordSession,Z as recordSessionIndexRejection,oe as tombstoneMessage,ie as upsertMessage};
1
+ import M from"node:crypto";import d from"node:fs";import x from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as I,SESSION_SYNC_DATA_EPOCH as A,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as D}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as B}from"../native-fusion/native-source-deletions.js";let w=null,C=null;function p(){return D()}function u(){const t=x.join(p(),"canonical");return(!w||C!==t)&&(w=new _(t,c()),C=t),w}function P(){return x.join(p(),"session-index.json")}function g(){const t=P();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=c();for(const s of Object.values(e))if(s.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function S(t){V(P(),t)}function O(t){return b(t.payload,120)}function m(t){if(new B({rootDir:p(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function T(t){m(t.sessionId),c();const e=g(),n=e[t.sessionId],s=new Date().toISOString(),i=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,o=u().getManifest(t.sessionId),l=q(n,t),a=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),v=!!(a&&!r.includes(a)),f=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},R={daemonInstallationId:c(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??s,updatedAt:s,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??s,lastMessagePreview:f.preview,lastMessagePreviewMessageId:f.messageId,lastMessagePreviewAt:f.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:i,indexRevision:(n?.indexRevision??0)+1,...l,firstAvailableSeq:o?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(v?1:0),lastAttentionId:v?a:n?.lastAttentionId??null,seenAttentionIds:v&&a?[...r,a]:r,lastBodyCommittedAt:o?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:n?.indexQuarantineCode??null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt??null};return e[t.sessionId]=R,S(e),R}function U(){return Object.values(g())}function H(t){if(!t)throw new Error("sessionId is required");const e=g();e[t]&&(delete e[t],S(e))}function z(t){u().purgeSession(t)}function K(){const t=c();return L(),U().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:I,dataEpoch:A,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:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function X(t,e){const n=g(),s=n[t];if(!(!s||e<=s.syncedIndexRevision)){if(e>s.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...s,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},S(n)}}function Z(){const t=g();let e=0;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(i.deletedAt||i.indexQuarantineCode||!i.localReady)continue;const o=i.indexRevision+1;t[s]={...i,indexRevision:o,indexSyncState:"pending",indexSyncStateRevision:o,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e+=1}return e>0&&S(t),e}function ee(t){const e=g(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const i=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let o=t.disposition;o==="retry"&&i>=(t.maxAttempts??8)&&(o="terminal");const l=t.now??Date.now(),a=Math.min(5e3*2**Math.max(0,i-1),360*60*1e3),r=Math.round(a*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:o==="retry"?"pending":o,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:i,indexSyncNextAttemptAt:o==="retry"?new Date(l+a+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:o==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),o}const N=2*6e4;function te(t=Date.now(),e=N){const n=g(),s=[],i=new Date(t).toISOString();for(const o of Object.values(n)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim())continue;const l=o.status==="completed"||o.status==="failed",a=o.visibleMessageCount>=2;let r=null;if(o.agentType==="claude"&&a?r="agent_native_unavailable":l?r="terminal":a&&t-Date.parse(o.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const v=b(o.title,240);if(!v)continue;const f=M.randomUUID();n[o.sessionId]={...o,titleGenerationAttemptedAt:i},s.push({sessionId:o.sessionId,requestId:f,titleSeed:v,baseTitleRevision:o.titleRevision,eligibility:r})}return s.length>0&&S(n),s}function ne(t,e){const s=g()[t];return!s||s.titleLockedByUser||y[s.titleSource]>=y.shennian_generated||e.titleRevision<=s.titleRevision?!1:(T({sessionId:t,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId,modelId:s.modelId,status:s.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function ie(t,e,n){m(t),c();const s=u().append(t,e,n);return E(t,e,s.bodyRevision),s.bodyRevision}function se(t,e,n){m(t),c();const s=u().upsert(t,e,n);return E(t,e,s.bodyRevision),s.bodyRevision}function oe(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:I,dataEpoch:A,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:I,dataEpoch:A,daemonInstallationId:c(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function ae(t,e){m(t),c();const n=u().tombstone(t,e),s=g(),i=s[t];if(i){const o=e===i.lastMessagePreviewMessageId?h(t):{preview:i.lastMessagePreview??null,messageId:i.lastMessagePreviewMessageId??null,at:i.lastMessagePreviewAt??null};s[t]={...i,bodyRevision:n.bodyRevision,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at},S(s)}return n.bodyRevision}function E(t,e,n){const s=g(),i=s[t];if(!i)return;const o=O(e),l=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,a=u().getManifest(t),r=e.role==="user"&&o&&!i.title?{title:o,titleSource:"first_user_fallback",titleRevision:i.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,v=e.role==="user"&&!i.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;s[t]={...i,...r??{},...v??{},bodyRevision:n,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...l??{},firstAvailableSeq:a?.firstAvailableSeq??i.firstAvailableSeq,visibleMessageCount:a?.visibleMessageCount??i.visibleMessageCount,lastVisibleMessageId:a?.lastVisibleMessageId??i.lastVisibleMessageId,attentionCount:i.attentionCount??0,lastAttentionId:i.lastAttentionId??null,lastBodyCommittedAt:a?.lastBodyCommittedAt??i.lastBodyCommittedAt},S(s)}function h(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let s=n.entries.length-1;s>=0;s-=1){const i=n.entries[s],o=O(i.message);if(o)return{preview:o,messageId:i.message.id,at:new Date(i.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function L(){const t=g();let e=!1;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewAt"))continue;const o=h(s);t[s]={...i,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function q(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},s=e.title?.trim()??"";if(!s)return n;if(!e.titleSource)throw new Error("titleSource is required with title");const i=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==i)throw new Error("titleLockedByUser conflicts with titleSource");const o=s===n.title&&e.titleSource===n.titleSource,l=e.titleRevision??n.titleRevision+(o?0:1);if(!Number.isSafeInteger(l)||l<0)throw new Error("invalid titleRevision");const a=y[e.titleSource],r=y[n.titleSource];return a<r||l<n.titleRevision||o&&l===n.titleRevision||l===n.titleRevision&&!o?n:{title:s,titleSource:e.titleSource,titleRevision:l,titleLockedByUser:i,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function le(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let i=u().readRecent(t,n).messages;return e?.before!==void 0&&(i=i.filter(o=>o.ts<e.before)),[...i].sort((o,l)=>l.ts-o.ts)}function re(){return u().listSessionIds()}function de(){return u()}function V(t,e){const n=x.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const s=`${t}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let i;try{if(i=d.openSync(s,"wx",384),d.writeFileSync(i,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),d.fsyncSync(i),d.closeSync(i),i=void 0,d.renameSync(s,t),process.platform!=="win32"){const o=d.openSync(n,"r");try{d.fsyncSync(o)}finally{d.closeSync(o)}}}finally{i!==void 0&&d.closeSync(i);try{d.unlinkSync(s)}catch{}}}export{k as PERSONAL_SYNC_V1_ROOT_NAME,X as acknowledgeSessionIndexUpdate,ie as appendMessage,ne as applyGeneratedSessionTitle,te as claimSessionTitleGenerationCandidates,de as getCanonicalSessionStore,c as getDaemonInstallationId,oe as getMessageDeliveryStatus,K as listPendingSessionIndexUpdates,U as listSessionRecords,re as listSessions,Z as markAllSessionIndexesForRepublish,z as purgeCanonicalSession,H as purgeSessionRecord,le as readMessages,T as recordSession,ee as recordSessionIndexRejection,ae as tombstoneMessage,se as upsertMessage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {