shennian 0.3.65 → 0.3.67

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",
@@ -311,8 +311,8 @@
311
311
  },
312
312
  {
313
313
  "file": "src/native-fusion/codex-parser.js",
314
- "beforeBytes": 28748,
315
- "afterBytes": 11925
314
+ "beforeBytes": 29358,
315
+ "afterBytes": 12136
316
316
  },
317
317
  {
318
318
  "file": "src/native-fusion/codex-user-input.js",
@@ -331,8 +331,8 @@
331
331
  },
332
332
  {
333
333
  "file": "src/native-fusion/local-repository.js",
334
- "beforeBytes": 25241,
335
- "afterBytes": 12409
334
+ "beforeBytes": 25396,
335
+ "afterBytes": 12474
336
336
  },
337
337
  {
338
338
  "file": "src/native-fusion/native-source-deletions.js",
@@ -356,8 +356,8 @@
356
356
  },
357
357
  {
358
358
  "file": "src/native-fusion/service.js",
359
- "beforeBytes": 29648,
360
- "afterBytes": 13788
359
+ "beforeBytes": 30151,
360
+ "afterBytes": 14006
361
361
  },
362
362
  {
363
363
  "file": "src/native-fusion/session-header.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};
@@ -1,4 +1,4 @@
1
- import E from"node:fs";import R from"node:path";import{createHash as Y}from"node:crypto";import{buildApprovalPendingPayload as Z,buildUserMessagePayload as S,isSystemControlPayload as A,isToolPayload as k}from"@shennian/wire";import{buildImagePayload as P,buildToolPayload as D,findJsonlLookbackOffset as K,inferMimeType as U,makeCursor as H,normalizeText as y,parseStructuredString as O,readJsonlLines as ee,readTimestamp as te,safeParse as W,shouldIgnoreNativeScanPath as q}from"./parser-common.js";import{lookupCodexThreadName as L}from"./codex-files.js";import{parseCodexDelegationInput as ne,parseCodexResponseUserInput as re,parseCodexTextElements as z,stripCodexUserMessageWrapper as oe}from"./codex-user-input.js";import{listCodexRolloutFiles as ve,lookupCodexThreadName as Me}from"./codex-files.js";function N(e){if(typeof e=="string"){const i=e.trim();return i?{path:i,name:R.basename(i)||"image.png",mimeType:U(i),kind:"image"}:null}if(typeof e!="object"||e===null)return null;const n=e,r=typeof n.path=="string"?n.path:typeof n.file_path=="string"?n.file_path:typeof n.saved_path=="string"?n.saved_path:typeof n.image_path=="string"?n.image_path:"";if(!r.trim())return null;const t=typeof n.name=="string"&&n.name.trim()?n.name:R.basename(r)||"image.png",o=typeof n.mimeType=="string"&&n.mimeType.trim()?n.mimeType:U(r);return{path:r,name:t,mimeType:o,kind:"image"}}function B(e){const n=y(e);if(!n)return!1;if(A(n)||/^<environment_context>[\s\S]*<\/environment_context>$/i.test(n)&&n.includes("<current_date>")&&n.includes("<timezone>")&&n.includes("<workspace_roots>"))return!0;const r=n.startsWith("<recommended_plugins>")&&n.includes("</recommended_plugins>"),t=n.includes("# AGENTS.md instructions for ")&&n.includes("<INSTRUCTIONS>")&&n.includes("</INSTRUCTIONS>"),o=n.includes("<environment_context>")&&n.includes("</environment_context>");return t&&o&&(n.startsWith("# AGENTS.md instructions for ")||r)}function F(e){const n=z(e.text_elements),r=Array.isArray(e.input)?z(e.input):"",t=typeof e.message=="string"?oe(e.message):{text:"",strippedWrapper:!1},o=t.text,i=n||r||o,u=t.strippedWrapper?"codex_app_context_wrapper":void 0,a=Array.isArray(e.local_images)?e.local_images.map(N).filter(l=>l!=null):[];return!i&&a.length===0||A(i)&&a.length===0||B(i)&&a.length===0?null:a.length>0?{payload:S(i,a),titleText:i,repairHint:u}:{payload:i,titleText:i,repairHint:u}}function ie(e){return e==="turn_completed"||e==="turn_complete"||e==="task_complete"}function se(e){return/approval|permission|confirm|user[_-]?input|input[_-]?required/i.test(e)}function ce(e){const n=typeof e.command=="string"?e.command:Array.isArray(e.command)?e.command.join(" "):"",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:n?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${n}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";return Z({title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"})}function ue(e,n){return Array.isArray(e)?y(e.map(r=>{if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const t=r;return t.type!==n?"":typeof t.text=="string"?t.text:""}).filter(Boolean).join(`
1
+ import j from"node:fs";import R from"node:path";import{createHash as Z}from"node:crypto";import{buildApprovalPendingPayload as P,buildUserMessagePayload as U,isSystemControlPayload as A,isToolPayload as k}from"@shennian/wire";import{buildImagePayload as D,buildToolPayload as K,findJsonlLookbackOffset as O,inferMimeType as H,makeCursor as W,normalizeText as y,parseStructuredString as tt,readJsonlLines as et,readTimestamp as nt,safeParse as q,shouldIgnoreNativeScanPath as L}from"./parser-common.js";import{lookupCodexThreadName as z}from"./codex-files.js";import{parseCodexDelegationInput as rt,parseCodexResponseUserInput as ot,parseCodexTextElements as B,stripCodexUserMessageWrapper as it}from"./codex-user-input.js";import{listCodexRolloutFiles as Mt,lookupCodexThreadName as At}from"./codex-files.js";function N(t){if(typeof t=="string"){const i=t.trim();return i?{path:i,name:R.basename(i)||"image.png",mimeType:H(i),kind:"image"}:null}if(typeof t!="object"||t===null)return null;const n=t,r=typeof n.path=="string"?n.path:typeof n.file_path=="string"?n.file_path:typeof n.saved_path=="string"?n.saved_path:typeof n.image_path=="string"?n.image_path:"";if(!r.trim())return null;const e=typeof n.name=="string"&&n.name.trim()?n.name:R.basename(r)||"image.png",o=typeof n.mimeType=="string"&&n.mimeType.trim()?n.mimeType:H(r);return{path:r,name:e,mimeType:o,kind:"image"}}function F(t){const n=y(t);if(!n)return!1;if(A(n)||/^<environment_context>[\s\S]*<\/environment_context>$/i.test(n)&&n.includes("<current_date>")&&n.includes("<timezone>")&&n.includes("<workspace_roots>"))return!0;const r=n.startsWith("<recommended_plugins>")&&n.includes("</recommended_plugins>"),e=n.includes("# AGENTS.md instructions for ")&&n.includes("<INSTRUCTIONS>")&&n.includes("</INSTRUCTIONS>"),o=n.includes("<environment_context>")&&n.includes("</environment_context>");return e&&o&&(n.startsWith("# AGENTS.md instructions for ")||r)}function w(t){const n=B(t.text_elements),r=Array.isArray(t.input)?B(t.input):"",e=typeof t.message=="string"?it(t.message):{text:"",strippedWrapper:!1},o=e.text,i=n||r||o,s=e.strippedWrapper?"codex_app_context_wrapper":void 0,a=Array.isArray(t.local_images)?t.local_images.map(N).filter(l=>l!=null):[];return!i&&a.length===0||A(i)&&a.length===0||F(i)&&a.length===0?null:a.length>0?{payload:U(i,a),titleText:i,repairHint:s}:{payload:i,titleText:i,repairHint:s}}function ut(t){return t==="turn_aborted"?"aborted":t==="turn_completed"||t==="turn_complete"||t==="task_complete"?"completed":null}function ct(t){return/approval|permission|confirm|user[_-]?input|input[_-]?required/i.test(t)}function st(t){const n=typeof t.command=="string"?t.command:Array.isArray(t.command)?t.command.join(" "):"",r=typeof t.prompt=="string"?t.prompt:typeof t.reason=="string"?t.reason:typeof t.message=="string"?t.message:n?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${n}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";return P({title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"})}function at(t,n){return Array.isArray(t)?y(t.map(r=>{if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const e=r;return e.type!==n?"":typeof e.text=="string"?e.text:""}).filter(Boolean).join(`
2
2
 
3
- `)):""}function ae(e){if(!Array.isArray(e))return[];const n=[];for(const r of e){if(typeof r!="object"||r===null)continue;const t=r;if(t.type!=="input_image")continue;const o=N(t);o&&n.push(o)}return n}function w(e){if(e.type!=="message")return null;const n=e.role==="assistant"?"agent":e.role==="user"?"user":null;if(!n)return null;if(n==="agent"){const i=ue(e.content,"output_text");return A(i)?null:i?{role:n,payload:i,titleText:i}:null}const r=re(e.content),t=r.text,o=[...ae(e.content)];for(const i of r.imagePaths){const u=N(i);u&&(o.some(a=>a.path===u.path)||o.push(u))}return!t&&o.length===0||A(t)&&o.length===0||B(t)&&o.length===0?null:{role:n,payload:o.length>0?S(t,o):t,titleText:t,repairHint:r.strippedWrapper?"codex_app_context_wrapper":void 0}}function J(e){if(!e)return"";try{const n=JSON.parse(e);if(typeof n=="object"&&n!==null){const r=n;if(r.type==="user"&&typeof r.content=="string")return y(r.content)}}catch{}return y(e)}function le(e,n,r,t){return e.agentType!=="codex"||e.role!==n||k(e.payload)||Math.abs(e.ts-t)>300*1e3?!1:J(e.payload)===J(r)}function $(e,n,r,t){for(let o=e.length-1;o>=0;o-=1){const i=e[o];if(i){if(le(i,n,r,t))return o;if(i.agentType==="codex"&&Math.abs(i.ts-t)>300*1e3)break}}return-1}function v(e,n,r,t,o,i,u,a,l,c,s="agent",m=!1,p){u&&e.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:G(o,r,t),cursor:H(n,r),role:s,ts:i,payload:u,title:a,modelId:l,workDir:c,terminal:m,repairHint:p})}function b(e,n,r,t,o,i,u,a,l,c,s,m,p){const T=G(o,r,t),I=typeof p?.callId=="string"&&p.callId.trim()?p.callId.trim():null,d=I?Y("sha256").update(`${o}\0${I}`).digest("hex").slice(0,32):null,M=d?`tool:${d}`:void 0,f=d?`native:tool:${d}`:`native:${T}`,C=p?.completed&&d?2:1,g=m!==void 0?"completed":"running";e.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:T,cursor:H(n,r),role:"agent",ts:i,payload:D({name:u,toolCallId:f,detailRevision:C,status:g}),title:a,modelId:l,workDir:c,...M?{canonicalMessageKey:M,canonicalMutation:p?.completed?"upsert":"append"}:{},localToolDetail:{toolCallId:f,detailRevision:C,name:u,...s!==void 0?{args:s}:{},...m!==void 0?{result:m}:{},status:g}})}function G(e,n,r){return`codex:${e}:${n}:${r}`}function me(e,n,r,t,o,i,u,a,l){const c=typeof t.type=="string"?t.type:"";if(c){if(c==="message"){const s=w(t);if(!s||$(e,s.role,s.payload,i)>=0)return;v(e,n,r,`${c}:${s.role}`,o,i,s.payload,u,a,l,s.role,!1,s.repairHint);return}if(c==="function_call"){const s=typeof t.name=="string"?t.name:"function_call";b(e,n,r,c,o,i,s,u,a,l,O(t.arguments),void 0,{callId:t.call_id});return}if(c==="function_call_output"){const s=ne(t);if(s){v(e,n,r,"delegation:user",o,i,s,u||s.slice(0,80),a,l,"user");return}b(e,n,r,c,o,i,"function_call_output",u,a,l,void 0,t.output,{callId:t.call_id,completed:!0});return}if(c==="custom_tool_call"){const s=typeof t.name=="string"?t.name:"custom_tool_call",m=t.input!==void 0?{input:t.input}:void 0;b(e,n,r,c,o,i,s,u,a,l,m,void 0,{callId:t.call_id});return}if(c==="custom_tool_call_output"){const s=typeof t.name=="string"?t.name:"custom_tool_call_output",m=t.output??t.result??t.content;b(e,n,r,c,o,i,s,u,a,l,void 0,m,{callId:t.call_id,completed:!0});return}if(c==="web_search_call"){const s=typeof t.action=="object"&&t.action!==null?t.action:null;b(e,n,r,c,o,i,"web_search",u,a,l,s??t.query);return}}}function fe(e,n,r,t,o,i,u,a,l){const c=typeof t.type=="string"?t.type:"";if(c){if(c==="user_message"){const s=F(t);if(!s)return;v(e,n,r,c,o,i,s.payload,u,a,l,"user",!1,s.repairHint);return}if(c==="agent_message"){const s=typeof t.message=="string"?y(t.message):"";if(!s||A(s))return;v(e,n,r,c,o,i,s,u,a,l);return}if(c==="turn_aborted"||c==="error"){const s=typeof t.message=="string"?y(t.message):typeof t.error=="string"?y(t.error):"";if(!s)return;v(e,n,r,c,o,i,s,u,a,l,"agent",!0);return}if(se(c)){v(e,n,r,c,o,i,ce(t),u,a,l,"agent");return}if(c==="exec_command_end"){const s=Array.isArray(t.command)?t.command:t.parsed_cmd??t.command,m=t.exit_code??t.exitCode,p=[typeof t.aggregated_output=="string"?t.aggregated_output:"",m!=null?`
4
- (exit ${String(m)})`:""].join("");b(e,n,r,c,o,i,"command_execution",u,a,l,{command:s},p);return}if(c==="patch_apply_end"){b(e,n,r,c,o,i,"apply_patch",u,a,l,t.changes,{success:t.success,stdout:t.stdout,stderr:t.stderr});return}if(c==="mcp_tool_call_end"){const s=typeof t.invocation=="object"&&t.invocation!==null?t.invocation:null,m=typeof s?.tool=="string"?s.tool:"mcp_tool_call",p={server:s?.server,...typeof s?.arguments=="object"&&s.arguments!==null?s.arguments:{}};b(e,n,r,c,o,i,m,u,a,l,p,t.result);return}if(c==="dynamic_tool_call_request"){const s=typeof t.tool=="string"?t.tool:"dynamic_tool_call";b(e,n,r,c,o,i,s,u,a,l,t.arguments,void 0,{callId:t.callId??t.call_id});return}if(c==="dynamic_tool_call_response"){const s=typeof t.tool=="string"?t.tool:"dynamic_tool_call";b(e,n,r,c,o,i,s,u,a,l,void 0,t.result??t.output,{callId:t.callId??t.call_id,completed:!0});return}if(c==="web_search_end"){const s=typeof t.action=="object"&&t.action!==null?t.action:null;b(e,n,r,c,o,i,"web_search",u,a,l,s??t.query,t.query);return}if(c==="image_generation_end"){const s=typeof t.saved_path=="string"?t.saved_path.trim():"";if(!s)return;v(e,n,r,c,o,i,P(s),u,a,l);return}c==="view_image_tool_call"&&b(e,n,r,c,o,i,"view_image",u,a,l,t.path??t.image_path??t)}}function j(e=!1){return{sourceSessionKey:"",workDir:"",modelId:null,title:"",skipImport:e}}function Q(e){if((typeof e.thread_source=="string"?e.thread_source.trim().toLowerCase():"")==="subagent")return!0;const r=typeof e.source=="object"&&e.source!==null?e.source:null;if(r&&typeof r.subagent=="object"&&r.subagent!==null)return!0;const t=typeof e.parent_thread_id=="string"&&e.parent_thread_id.trim().length>0,o=typeof e.agent_nickname=="string"||typeof e.agent_role=="string"||typeof e.multi_agent_version=="string";return t&&o}function pe(e){const n=E.openSync(e,"r");try{const r=[];let t=0,o="";for(;t<1024*1024;){const l=Buffer.alloc(16384),c=E.readSync(n,l,0,l.length,t);if(c<=0)break;const s=l.subarray(0,c);r.push(s),t+=c;const m=Buffer.concat(r),p=m.indexOf(10);if(p>=0){o=m.subarray(0,p).toString("utf8");break}}!o&&r.length>0&&(o=Buffer.concat(r).toString("utf8"));const i=W(o);if(!i||i.type!=="session_meta")return j();const u=typeof i.payload=="object"&&i.payload!==null?i.payload:null;if(!u)return j();if(Q(u))return j(!0);const a=typeof u.id=="string"?u.id:"";return{sourceSessionKey:a,workDir:typeof u.cwd=="string"?u.cwd:"",modelId:V(u),title:L(a)??"",skipImport:!1}}finally{E.closeSync(n)}return j()}function V(e){return(typeof e.model=="string"?e.model.trim():"")||null}function Ce(e,n){const r=[],t=pe(e);let{sourceSessionKey:o,workDir:i,modelId:u,title:a,skipImport:l}=t,c=t.title;if(l||q(i))return{nextOffset:E.statSync(e).size,events:[]};let s=!1;const m=new Set,p=ee(e,K(e,n),(T,I)=>{const d=W(T);if(!d)return;const M=typeof d.type=="string"?d.type:"",f=typeof d.payload=="object"&&d.payload!==null?d.payload:null,C=te(d.timestamp);if(!(!f||!C)){if(M==="session_meta"){if(Q(f)){r.length=0,l=!0;return}if(o=typeof f.id=="string"?f.id:o,i=typeof f.cwd=="string"?f.cwd:i,q(i)){r.length=0,l=!0;return}u=V(f)??u;const g=L(o);g&&(c=g,a=g);return}if(!l&&o){if(M==="response_item"){const g=w(f);g?.role==="user"&&g.titleText&&!a&&(a=g.titleText.slice(0,80)),me(r,e,I,f,o,C,a,u,i);return}if(M==="event_msg"){const g=typeof f.type=="string"?f.type:"";if(ie(g)){let _=!1;for(let x=r.length-1;x>=0;x-=1){const h=r[x];if(h?.agentType==="codex"&&h.role==="agent"&&!k(h.payload)){h.terminal=!0,I>=n&&Number(h.cursor?.slice(e.length+1))<n&&m.add(h.sourceEventKey),_=!0,s=!1;break}}if(!_)for(let x=r.length-1;x>=0;x-=1){const h=r[x];if(!(h?.agentType!=="codex"||h.role!=="agent")){h.terminal=!0,I>=n&&Number(h.cursor?.slice(e.length+1))<n&&m.add(h.sourceEventKey),_=!0,s=!1;break}}_||(s=!0);return}if(g==="user_message"){const _=F(f);if(_?.titleText&&!a&&(a=_.titleText.slice(0,80)),_){const x=$(r,"user",_.payload,C);x>=0&&r.splice(x,1)}}else if(g==="agent_message"){const _=typeof f.message=="string"?y(f.message):"";if(_&&!A(_)){const x=$(r,"agent",_,C);x>=0&&r.splice(x,1)}}const X=r.length;fe(r,e,I,f,o,C,a,u,i),s&&r.length>X&&(r[r.length-1].terminal=!0,s=!1)}}}});for(const T of r)T.titleSource=c&&T.title===c?"agent_native":"first_user_fallback";return{nextOffset:p,events:r.filter(T=>m.has(T.sourceEventKey)||Number(T.cursor?.slice(e.length+1))>=n)}}export{ve as listCodexRolloutFiles,Me as lookupCodexThreadName,Ce as parseCodexRolloutChunk};
3
+ `)):""}function lt(t){if(!Array.isArray(t))return[];const n=[];for(const r of t){if(typeof r!="object"||r===null)continue;const e=r;if(e.type!=="input_image")continue;const o=N(e);o&&n.push(o)}return n}function J(t){if(t.type!=="message")return null;const n=t.role==="assistant"?"agent":t.role==="user"?"user":null;if(!n)return null;if(n==="agent"){const i=at(t.content,"output_text");return A(i)?null:i?{role:n,payload:i,titleText:i}:null}const r=ot(t.content),e=r.text,o=[...lt(t.content)];for(const i of r.imagePaths){const s=N(i);s&&(o.some(a=>a.path===s.path)||o.push(s))}return!e&&o.length===0||A(e)&&o.length===0||F(e)&&o.length===0?null:{role:n,payload:o.length>0?U(e,o):e,titleText:e,repairHint:r.strippedWrapper?"codex_app_context_wrapper":void 0}}function G(t){if(!t)return"";try{const n=JSON.parse(t);if(typeof n=="object"&&n!==null){const r=n;if(r.type==="user"&&typeof r.content=="string")return y(r.content)}}catch{}return y(t)}function mt(t,n,r,e){return t.agentType!=="codex"||t.role!==n||k(t.payload)||Math.abs(t.ts-e)>300*1e3?!1:G(t.payload)===G(r)}function $(t,n,r,e){for(let o=t.length-1;o>=0;o-=1){const i=t[o];if(i){if(mt(i,n,r,e))return o;if(i.agentType==="codex"&&Math.abs(i.ts-e)>300*1e3)break}}return-1}function v(t,n,r,e,o,i,s,a,l,c,u="agent",m=!1,p){s&&t.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:Q(o,r,e),cursor:W(n,r),role:u,ts:i,payload:s,title:a,modelId:l,workDir:c,terminal:m,...m?{terminalState:e==="turn_aborted"?"aborted":e==="error"?"failed":"completed"}:{},repairHint:p})}function b(t,n,r,e,o,i,s,a,l,c,u,m,p){const T=Q(o,r,e),I=typeof p?.callId=="string"&&p.callId.trim()?p.callId.trim():null,_=I?Z("sha256").update(`${o}\0${I}`).digest("hex").slice(0,32):null,M=_?`tool:${_}`:void 0,f=_?`native:tool:${_}`:`native:${T}`,C=p?.completed&&_?2:1,g=m!==void 0?"completed":"running";t.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:T,cursor:W(n,r),role:"agent",ts:i,payload:K({name:s,toolCallId:f,detailRevision:C,status:g}),title:a,modelId:l,workDir:c,...M?{canonicalMessageKey:M,canonicalMutation:p?.completed?"upsert":"append"}:{},localToolDetail:{toolCallId:f,detailRevision:C,name:s,...u!==void 0?{args:u}:{},...m!==void 0?{result:m}:{},status:g}})}function Q(t,n,r){return`codex:${t}:${n}:${r}`}function ft(t,n,r,e,o,i,s,a,l){const c=typeof e.type=="string"?e.type:"";if(c){if(c==="message"){const u=J(e);if(!u||$(t,u.role,u.payload,i)>=0)return;v(t,n,r,`${c}:${u.role}`,o,i,u.payload,s,a,l,u.role,!1,u.repairHint);return}if(c==="function_call"){const u=typeof e.name=="string"?e.name:"function_call";b(t,n,r,c,o,i,u,s,a,l,tt(e.arguments),void 0,{callId:e.call_id});return}if(c==="function_call_output"){const u=rt(e);if(u){v(t,n,r,"delegation:user",o,i,u,s||u.slice(0,80),a,l,"user");return}b(t,n,r,c,o,i,"function_call_output",s,a,l,void 0,e.output,{callId:e.call_id,completed:!0});return}if(c==="custom_tool_call"){const u=typeof e.name=="string"?e.name:"custom_tool_call",m=e.input!==void 0?{input:e.input}:void 0;b(t,n,r,c,o,i,u,s,a,l,m,void 0,{callId:e.call_id});return}if(c==="custom_tool_call_output"){const u=typeof e.name=="string"?e.name:"custom_tool_call_output",m=e.output??e.result??e.content;b(t,n,r,c,o,i,u,s,a,l,void 0,m,{callId:e.call_id,completed:!0});return}if(c==="web_search_call"){const u=typeof e.action=="object"&&e.action!==null?e.action:null;b(t,n,r,c,o,i,"web_search",s,a,l,u??e.query);return}}}function pt(t,n,r,e,o,i,s,a,l){const c=typeof e.type=="string"?e.type:"";if(c){if(c==="user_message"){const u=w(e);if(!u)return;v(t,n,r,c,o,i,u.payload,s,a,l,"user",!1,u.repairHint);return}if(c==="agent_message"){const u=typeof e.message=="string"?y(e.message):"";if(!u||A(u))return;v(t,n,r,c,o,i,u,s,a,l);return}if(c==="turn_aborted"||c==="error"){const u=typeof e.message=="string"?y(e.message):typeof e.error=="string"?y(e.error):"";if(!u)return;v(t,n,r,c,o,i,u,s,a,l,"agent",!0);return}if(ct(c)){v(t,n,r,c,o,i,st(e),s,a,l,"agent");return}if(c==="exec_command_end"){const u=Array.isArray(e.command)?e.command:e.parsed_cmd??e.command,m=e.exit_code??e.exitCode,p=[typeof e.aggregated_output=="string"?e.aggregated_output:"",m!=null?`
4
+ (exit ${String(m)})`:""].join("");b(t,n,r,c,o,i,"command_execution",s,a,l,{command:u},p);return}if(c==="patch_apply_end"){b(t,n,r,c,o,i,"apply_patch",s,a,l,e.changes,{success:e.success,stdout:e.stdout,stderr:e.stderr});return}if(c==="mcp_tool_call_end"){const u=typeof e.invocation=="object"&&e.invocation!==null?e.invocation:null,m=typeof u?.tool=="string"?u.tool:"mcp_tool_call",p={server:u?.server,...typeof u?.arguments=="object"&&u.arguments!==null?u.arguments:{}};b(t,n,r,c,o,i,m,s,a,l,p,e.result);return}if(c==="dynamic_tool_call_request"){const u=typeof e.tool=="string"?e.tool:"dynamic_tool_call";b(t,n,r,c,o,i,u,s,a,l,e.arguments,void 0,{callId:e.callId??e.call_id});return}if(c==="dynamic_tool_call_response"){const u=typeof e.tool=="string"?e.tool:"dynamic_tool_call";b(t,n,r,c,o,i,u,s,a,l,void 0,e.result??e.output,{callId:e.callId??e.call_id,completed:!0});return}if(c==="web_search_end"){const u=typeof e.action=="object"&&e.action!==null?e.action:null;b(t,n,r,c,o,i,"web_search",s,a,l,u??e.query,e.query);return}if(c==="image_generation_end"){const u=typeof e.saved_path=="string"?e.saved_path.trim():"";if(!u)return;v(t,n,r,c,o,i,D(u),s,a,l);return}c==="view_image_tool_call"&&b(t,n,r,c,o,i,"view_image",s,a,l,e.path??e.image_path??e)}}function E(t=!1){return{sourceSessionKey:"",workDir:"",modelId:null,title:"",skipImport:t}}function V(t){if((typeof t.thread_source=="string"?t.thread_source.trim().toLowerCase():"")==="subagent")return!0;const r=typeof t.source=="object"&&t.source!==null?t.source:null;if(r&&typeof r.subagent=="object"&&r.subagent!==null)return!0;const e=typeof t.parent_thread_id=="string"&&t.parent_thread_id.trim().length>0,o=typeof t.agent_nickname=="string"||typeof t.agent_role=="string"||typeof t.multi_agent_version=="string";return e&&o}function gt(t){const n=j.openSync(t,"r");try{const r=[];let e=0,o="";for(;e<1024*1024;){const l=Buffer.alloc(16384),c=j.readSync(n,l,0,l.length,e);if(c<=0)break;const u=l.subarray(0,c);r.push(u),e+=c;const m=Buffer.concat(r),p=m.indexOf(10);if(p>=0){o=m.subarray(0,p).toString("utf8");break}}!o&&r.length>0&&(o=Buffer.concat(r).toString("utf8"));const i=q(o);if(!i||i.type!=="session_meta")return E();const s=typeof i.payload=="object"&&i.payload!==null?i.payload:null;if(!s)return E();if(V(s))return E(!0);const a=typeof s.id=="string"?s.id:"";return{sourceSessionKey:a,workDir:typeof s.cwd=="string"?s.cwd:"",modelId:X(s),title:z(a)??"",skipImport:!1}}finally{j.closeSync(n)}return E()}function X(t){return(typeof t.model=="string"?t.model.trim():"")||null}function yt(t,n){const r=[],e=gt(t);let{sourceSessionKey:o,workDir:i,modelId:s,title:a,skipImport:l}=e,c=e.title;if(l||L(i))return{nextOffset:j.statSync(t).size,events:[]};let u=null;const m=new Set,p=et(t,O(t,n),(T,I)=>{const _=q(T);if(!_)return;const M=typeof _.type=="string"?_.type:"",f=typeof _.payload=="object"&&_.payload!==null?_.payload:null,C=nt(_.timestamp);if(!(!f||!C)){if(M==="session_meta"){if(V(f)){r.length=0,l=!0;return}if(o=typeof f.id=="string"?f.id:o,i=typeof f.cwd=="string"?f.cwd:i,L(i)){r.length=0,l=!0;return}s=X(f)??s;const g=z(o);g&&(c=g,a=g);return}if(!l&&o){if(M==="response_item"){const g=J(f);g?.role==="user"&&g.titleText&&!a&&(a=g.titleText.slice(0,80)),ft(r,t,I,f,o,C,a,s,i);return}if(M==="event_msg"){const g=typeof f.type=="string"?f.type:"",S=ut(g);if(S){let x=!1;for(let h=r.length-1;h>=0;h-=1){const d=r[h];if(d?.agentType==="codex"&&d.role==="agent"&&!k(d.payload)){d.terminal=!0,d.terminalState=S,I>=n&&Number(d.cursor?.slice(t.length+1))<n&&m.add(d.sourceEventKey),x=!0,u=null;break}}if(!x)for(let h=r.length-1;h>=0;h-=1){const d=r[h];if(!(d?.agentType!=="codex"||d.role!=="agent")){d.terminal=!0,d.terminalState=S,I>=n&&Number(d.cursor?.slice(t.length+1))<n&&m.add(d.sourceEventKey),x=!0,u=null;break}}x||(u=S);return}if(g==="user_message"){const x=w(f);if(x?.titleText&&!a&&(a=x.titleText.slice(0,80)),x){const h=$(r,"user",x.payload,C);h>=0&&r.splice(h,1)}}else if(g==="agent_message"){const x=typeof f.message=="string"?y(f.message):"";if(x&&!A(x)){const h=$(r,"agent",x,C);h>=0&&r.splice(h,1)}}const Y=r.length;pt(r,t,I,f,o,C,a,s,i),u&&r.length>Y&&(r[r.length-1].terminal=!0,r[r.length-1].terminalState=u,u=null)}}}});for(const T of r)T.titleSource=c&&T.title===c?"agent_native":"first_user_fallback";return{nextOffset:p,events:r.filter(T=>m.has(T.sourceEventKey)||Number(T.cursor?.slice(t.length+1))>=n)}}export{Mt as listCodexRolloutFiles,At as lookupCodexThreadName,yt as parseCodexRolloutChunk};
@@ -1,2 +1,2 @@
1
- import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{getCanonicalSessionStore as P,getDaemonInstallationId as H,listSessionRecords as F,recordSession as w}from"../session/store.js";import{readToolDetail as U,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as A}from"../personal-sync/metrics.js";const I=1;class q extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class W extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class le{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??F)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??G(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),S=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(S?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let y=f.getManifest(d)?.bodyRevision??0,T=!!r;const M=!S&&!!e.claimCanonicalMessageId;if(M){if(!u)throw new Error("managed echo claim requires a canonical message");const E=f.getMessageState(d,u);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!M&&!S){if(!u)throw new Error("native event requires a canonical message");const E=e.canonicalMessageKey?f.getMessageState(d,u):null,x=e.canonicalMutation==="upsert"||!!E,$=x?E??f.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},C=x?f.upsert(d,_):f.append(d,_);if(y=C.bodyRevision,T=C.duplicate,e.localToolDetail){const j=this.options.writeLocalToolDetail??J,N=this.options.readLocalToolDetail??U,B=Z(N(d,e.localToolDetail.toolCallId),e.localToolDetail);j({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(y=f.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??y),(!r||e.terminal)&&!S&&this.recordIndex(e,d,u,y);const K=new Date().toISOString();return r||g(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:y,cursor:e.cursor??null,mappedBy:S?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:y,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!M&&!S&&!T&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:y,duplicate:T,claimed:M,suppressed:S}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??F,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){k(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],g(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[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&g(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&g(this.managedEchoStateFile(),n);const c=h.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=h.join(c,a.name),m=D(r);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??F)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?v(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{};i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?v(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:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||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??H()}canonicalStore(){return this.options.canonicalStore??P()}rootDir(){return this.options.rootDir??R("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:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))k(o);for(const o of Object.values(s.suppressions))O(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 z(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 G(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(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 k(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 O(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 Y(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 n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Z(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 v(t){return L(t,120)}function g(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 n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
1
+ import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{getCanonicalSessionStore as P,getDaemonInstallationId as H,listSessionRecords as F,recordSession as w}from"../session/store.js";import{readToolDetail as U,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as A}from"../personal-sync/metrics.js";const I=1;class q extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class W extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class le{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??F)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??G(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),S=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(S?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let y=f.getManifest(d)?.bodyRevision??0,T=!!r;const M=!S&&!!e.claimCanonicalMessageId;if(M){if(!u)throw new Error("managed echo claim requires a canonical message");const E=f.getMessageState(d,u);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!M&&!S){if(!u)throw new Error("native event requires a canonical message");const E=e.canonicalMessageKey?f.getMessageState(d,u):null,x=e.canonicalMutation==="upsert"||!!E,$=x?E??f.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},C=x?f.upsert(d,_):f.append(d,_);if(y=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,v=this.options.readLocalToolDetail??U,B=Z(v(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(y=f.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??y),(!r||e.terminal)&&!S&&this.recordIndex(e,d,u,y);const K=new Date().toISOString();return r||g(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:y,cursor:e.cursor??null,mappedBy:S?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:y,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!M&&!S&&!T&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:y,duplicate:T,claimed:M,suppressed:S}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??F,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){k(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],g(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[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&g(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&g(this.managedEchoStateFile(),n);const c=h.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=h.join(c,a.name),m=D(r);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??F)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?j(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{};i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?j(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:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||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??H()}canonicalStore(){return this.options.canonicalStore??P()}rootDir(){return this.options.rootDir??R("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:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))k(o);for(const o of Object.values(s.suppressions))O(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 z(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 G(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(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 k(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 O(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 Y(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 n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Z(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 j(t){return L(t,120)}function g(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 n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
2
2
  `,"utf8"),l.fsyncSync(n),l.closeSync(n),n=void 0,l.renameSync(o,t)}finally{n!==void 0&&l.closeSync(n);try{l.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{q as MissingUserBoundaryError,le as NativeFusionLocalRepository,W as NativeSourceDeletedError};
@@ -1,2 +1,2 @@
1
- import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as O}from"./local-repository.js";import{listClaudeTranscriptFiles as k,listCodexRolloutFiles as R,listOpenCodeSessionFiles as L,listNativeTranscriptRoots as D,inspectNativeSessionFile as q,lookupCodexThreadName as W,parseClaudeTranscriptChunk as $,parseCodexRolloutChunk as U,parseOpenCodeSessionFile as z}from"./parsers.js";import{loadNativeScannerState as V,saveNativeScannerState as H}from"./state.js";const Q=5*6e4,Y=2*6e4,j=30*6e4,X=2e3,G=32,A=16,J=3e4,C=240*6e4;function Z(u){return u.replace(/\r\n/g,`
2
- `).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new O){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),c=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==c))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:c,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(c=>c.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-final`,payload:{state:"final",sessionId:e.sessionId,runId:e.runId,seq:2,owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=V(),t={...s.files},i=[],c=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...k(),...L()];!e&&this.watchEnabled&&this.watcher.refresh(g,D());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let a=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!a;if(a&&!f&&(d.size<a.offset||a.fileIdentity&&a.fileIdentity!==I)&&(a=void 0,x=!0),!!(a&&a.fileIdentity===I&&a.mtimeMs===d.mtimeMs&&a.offset>=d.size&&a.sources?.length)&&a){for(const r of a.sources??[])this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);a.classification==="live"&&a.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=a?.sources?.length&&!f?{status:"ready",createdAtMs:a.sessionCreatedAtMs??d.birthtimeMs,sources:a.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...a??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!a)a={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&a.mtimeMs===d.mtimeMs)continue;const E=f?0:a.offset,w=f?z(n,E):h?U(n,E):$(n,E);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(w.events,a,s.fusionEpochMs,x,d.mtimeMs,w.nextOffset);M=r.events,S=r.cursor}else if(x&&a.classification==="historical")M=[],S={...a,offset:w.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(w.events,a,w.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),c.set(p.claimKey,p.suppression??null),p.suppression&&l.set(T(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,c);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of c)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(T(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,H(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const c=i[0];e-c.lastPublishedAt<J||(c.lastPublishedAt=e,this.emitExternalActivity(c,c.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const c=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>c||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!(!e.terminal||i.state!=="running")&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"completed"),this.emitExternalTerminal(i),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued")))}suppressionFor(e,s,t){const i=T(e.agentType,e.sourceSessionKey),c=s.get(i),l=c?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,c,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,c,s,t),null):l}clearSuppression(e,s,t,i,c){if(t){i.delete(e),c.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function T(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(c=>c.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,c){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:c,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
1
+ import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as k}from"./local-repository.js";import{listClaudeTranscriptFiles as O,listCodexRolloutFiles as R,listOpenCodeSessionFiles as D,listNativeTranscriptRoots as L,inspectNativeSessionFile as q,lookupCodexThreadName as W,parseClaudeTranscriptChunk as $,parseCodexRolloutChunk as U,parseOpenCodeSessionFile as z}from"./parsers.js";import{loadNativeScannerState as V,saveNativeScannerState as H}from"./state.js";const Q=5*6e4,Y=2*6e4,j=30*6e4,X=2e3,G=32,A=16,J=3e4,C=240*6e4;function Z(u){return u.replace(/\r\n/g,`
2
+ `).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new k){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=V(),t={...s.files},i=[],a=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...O(),...D()];!e&&this.watchEnabled&&this.watcher.refresh(g,L());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let c=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!c;if(c&&!f&&(d.size<c.offset||c.fileIdentity&&c.fileIdentity!==I)&&(c=void 0,x=!0),!!(c&&c.fileIdentity===I&&c.mtimeMs===d.mtimeMs&&c.offset>=d.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=c?.sources?.length&&!f?{status:"ready",createdAtMs:c.sessionCreatedAtMs??d.birthtimeMs,sources:c.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...c??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&c.mtimeMs===d.mtimeMs)continue;const E=f?0:c.offset,w=f?z(n,E):h?U(n,E):$(n,E);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(w.events,c,s.fusionEpochMs,x,d.mtimeMs,w.nextOffset);M=r.events,S=r.cursor}else if(x&&c.classification==="historical")M=[],S={...c,offset:w.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(w.events,c,w.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),a.set(p.claimKey,p.suppression??null),p.suppression&&l.set(T(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,a);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of a)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(T(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,H(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<J||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>a||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=T(e.agentType,e.sourceSessionKey),a=s.get(i),l=a?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,a,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,a,s,t),null):l}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function T(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,a){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
@@ -26,6 +26,8 @@ export type NativeScannerState = {
26
26
  codexAppContextWrapperFixed?: boolean;
27
27
  };
28
28
  export type ParsedNativeEvent = NativeSessionEventPayload & {
29
+ /** Terminal outcome observed in the native source; `terminal` remains the wire-compatible flag. */
30
+ terminalState?: 'completed' | 'failed' | 'aborted';
29
31
  repairHint?: NativeRepairHint;
30
32
  sourceSessionCreatedAtMs?: number;
31
33
  claimSessionId?: string | null;
@@ -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};
@@ -1,11 +1,14 @@
1
1
  import type { AgentType, ManagerConfig, SessionMode } from './session.ts';
2
- import type { RoomActivationMode } from './room.ts';
2
+ import type { AgentRepairAction, AgentRuntimeAvailability, RoomActivationMode } from './room.ts';
3
3
  export type AgentWorkspaceRoomLink = {
4
4
  bindingId: string;
5
5
  status: string;
6
6
  roomId: string;
7
7
  roomName: string;
8
8
  activationMode: RoomActivationMode;
9
+ /** Optional for compatibility with servers released before machine recovery. */
10
+ availability?: AgentRuntimeAvailability;
11
+ repairAction?: AgentRepairAction;
9
12
  };
10
13
  export type AgentWorkspaceSession = {
11
14
  id: string;
@@ -31,6 +34,9 @@ export type AgentWorkspace = {
31
34
  updatedAt: string;
32
35
  } | null;
33
36
  status: string;
37
+ /** Optional additive field; old cached projections safely treat absence as available. */
38
+ availability?: AgentRuntimeAvailability;
39
+ repairAction?: AgentRepairAction;
34
40
  pinnedAt?: string | null;
35
41
  machineId: string | null;
36
42
  machineName: string | null;
@@ -7,6 +7,13 @@ export type RoomMembershipRole = 'owner' | 'admin' | 'member';
7
7
  export type RoomMembershipStatus = 'active' | 'removed' | 'left';
8
8
  export type RoomMessageKind = 'text' | 'attachment' | 'mixed' | 'system';
9
9
  export type RoomActivationMode = 'auto_follow' | 'mention_only' | 'paused';
10
+ /** Runtime reachability is orthogonal to the owner's persisted activation mode. */
11
+ export type AgentRuntimeAvailability = 'available' | 'unavailable' | 'recovering';
12
+ export type AgentRepairAction = 'configure' | 'retry' | null;
13
+ export type RoomAgentParticipation = {
14
+ activationMode: RoomActivationMode;
15
+ availability: AgentRuntimeAvailability;
16
+ };
10
17
  export type RoomActivationCapability = 'managed' | 'idle_resume' | 'notify_only';
11
18
  export type RoomBindingMode = 'managed' | 'hook_native';
12
19
  export type RoomBindingStatus = 'pending_approval' | 'active' | 'paused' | 'detached';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.65",
3
+ "version": "0.3.67",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {