shennian 0.3.68 → 0.3.69

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.
@@ -166,8 +166,8 @@
166
166
  },
167
167
  {
168
168
  "file": "src/commands/manager.js",
169
- "beforeBytes": 6853,
170
- "afterBytes": 4479
169
+ "beforeBytes": 6845,
170
+ "afterBytes": 4471
171
171
  },
172
172
  {
173
173
  "file": "src/commands/pair-authorization.js",
@@ -316,8 +316,8 @@
316
316
  },
317
317
  {
318
318
  "file": "src/native-fusion/codex-user-input.js",
319
- "beforeBytes": 4098,
320
- "afterBytes": 1925
319
+ "beforeBytes": 4583,
320
+ "afterBytes": 2164
321
321
  },
322
322
  {
323
323
  "file": "src/native-fusion/config.js",
@@ -591,13 +591,13 @@
591
591
  },
592
592
  {
593
593
  "file": "src/session/history-subscriptions.js",
594
- "beforeBytes": 8639,
595
- "afterBytes": 4283
594
+ "beforeBytes": 8974,
595
+ "afterBytes": 4411
596
596
  },
597
597
  {
598
598
  "file": "src/session/index-publisher.js",
599
- "beforeBytes": 7582,
600
- "afterBytes": 3747
599
+ "beforeBytes": 8089,
600
+ "afterBytes": 3965
601
601
  },
602
602
  {
603
603
  "file": "src/session/manager.js",
@@ -626,8 +626,8 @@
626
626
  },
627
627
  {
628
628
  "file": "src/session/store.js",
629
- "beforeBytes": 26645,
630
- "afterBytes": 13781
629
+ "beforeBytes": 28486,
630
+ "afterBytes": 14629
631
631
  },
632
632
  {
633
633
  "file": "src/session/tool-detail-store.js",
@@ -1 +1 @@
1
- import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,a=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!a||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:a,managerSessionId:s}}async function t(o,a){const s=u(),r=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...a,managerSessionId:s.managerSessionId})}),d=await r.json().catch(()=>({ok:!1,error:r.statusText}));if(!r.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${r.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const a=o.command("manager",{hidden:!0}).description("Manager Session local tools"),s=a.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude, gemini, cursor, opencode, pi, or custom:<name>").requiredOption("--workdir <path>","Worker workdir; must match Manager workdir").option("--model <model>","Worker model id").option("--message <text>","Worker prompt").option("--message-file <path>","Read worker prompt from file").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/start",{agentType:e.agent,workDir:e.workdir,modelId:e.model,message:g(e)});e.json?m(n):console.log(n.session?.sessionId??"ok")}),s.command("send").description("Queue a message for a managed worker; runs immediately if idle").requiredOption("--session-id <id>","Worker session id").option("--message <text>","Message text").option("--message-file <path>","Read message from file").option("--direct","Bypass queue and send immediately").action(async e=>{await t("/sessions/send",{sessionId:e.sessionId,message:g(e),enqueue:!e.direct}),console.log("ok")});const r=s.command("queue").description("Inspect and edit pending worker messages");r.command("list").description("List pending messages for a managed worker").requiredOption("--session-id <id>","Worker session id").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/queue",{sessionId:e.sessionId});if(e.json)m(n);else{const c=n.queue?.pending??[];for(const i of c)console.log(`${i.id} ${i.text.replace(/\s+/g," ").slice(0,120)}`)}}),r.command("edit").description("Edit a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").option("--message <text>","Replacement message text").option("--message-file <path>","Read replacement message from file").action(async e=>{await t("/sessions/queue/edit",{sessionId:e.sessionId,queueMessageId:e.messageId,message:g(e)}),console.log("ok")}),r.command("delete").description("Delete a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").action(async e=>{await t("/sessions/queue/delete",{sessionId:e.sessionId,queueMessageId:e.messageId}),console.log("ok")});const d=async e=>{await t("/sessions/stop",{sessionId:e.sessionId}),console.log("ok")};s.command("stop").aliases(["terminate","kill"]).description("Stop or terminate a running managed worker").requiredOption("--session-id <id>","Worker session id").action(d),s.command("read").description("Read a managed worker transcript").requiredOption("--session-id <id>","Worker session id").option("--limit <n>","Message limit","200").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/read",{sessionId:e.sessionId,limit:Number(e.limit)});e.json?m(n):m(n.messages??[])}),a.command("memory").description("Manager project memory helpers").command("path").description("Print project memory directory path").action(async()=>{const e=await t("/memory/path",{});console.log(e.path)})}export{I as registerManagerCommand};
1
+ import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,a=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!a||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:a,managerSessionId:s}}async function t(o,a){const s=u(),r=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...a,managerSessionId:s.managerSessionId})}),d=await r.json().catch(()=>({ok:!1,error:r.statusText}));if(!r.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${r.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const a=o.command("manager",{hidden:!0}).description("Manager Session local tools"),s=a.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude, cursor, opencode, pi, or custom:<name>").requiredOption("--workdir <path>","Worker workdir; must match Manager workdir").option("--model <model>","Worker model id").option("--message <text>","Worker prompt").option("--message-file <path>","Read worker prompt from file").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/start",{agentType:e.agent,workDir:e.workdir,modelId:e.model,message:g(e)});e.json?m(n):console.log(n.session?.sessionId??"ok")}),s.command("send").description("Queue a message for a managed worker; runs immediately if idle").requiredOption("--session-id <id>","Worker session id").option("--message <text>","Message text").option("--message-file <path>","Read message from file").option("--direct","Bypass queue and send immediately").action(async e=>{await t("/sessions/send",{sessionId:e.sessionId,message:g(e),enqueue:!e.direct}),console.log("ok")});const r=s.command("queue").description("Inspect and edit pending worker messages");r.command("list").description("List pending messages for a managed worker").requiredOption("--session-id <id>","Worker session id").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/queue",{sessionId:e.sessionId});if(e.json)m(n);else{const c=n.queue?.pending??[];for(const i of c)console.log(`${i.id} ${i.text.replace(/\s+/g," ").slice(0,120)}`)}}),r.command("edit").description("Edit a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").option("--message <text>","Replacement message text").option("--message-file <path>","Read replacement message from file").action(async e=>{await t("/sessions/queue/edit",{sessionId:e.sessionId,queueMessageId:e.messageId,message:g(e)}),console.log("ok")}),r.command("delete").description("Delete a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").action(async e=>{await t("/sessions/queue/delete",{sessionId:e.sessionId,queueMessageId:e.messageId}),console.log("ok")});const d=async e=>{await t("/sessions/stop",{sessionId:e.sessionId}),console.log("ok")};s.command("stop").aliases(["terminate","kill"]).description("Stop or terminate a running managed worker").requiredOption("--session-id <id>","Worker session id").action(d),s.command("read").description("Read a managed worker transcript").requiredOption("--session-id <id>","Worker session id").option("--limit <n>","Message limit","200").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/read",{sessionId:e.sessionId,limit:Number(e.limit)});e.json?m(n):m(n.messages??[])}),a.command("memory").description("Manager project memory helpers").command("path").description("Print project memory directory path").action(async()=>{const e=await t("/memory/path",{});console.log(e.path)})}export{I as registerManagerCommand};
@@ -1,5 +1,5 @@
1
- import{normalizeText as o}from"./parser-common.js";function _(t){return Array.isArray(t)?t.map(e=>{if(typeof e=="string")return e;if(typeof e!="object"||e===null)return"";const n=e;return typeof n.text=="string"?n.text:""}).map(e=>o(e)).filter(Boolean).join(`
1
+ import{normalizeText as s}from"./parser-common.js";function E(t){return Array.isArray(t)?t.map(e=>{if(typeof e=="string")return e;if(typeof e!="object"||e===null)return"";const n=e;return typeof n.text=="string"?n.text:""}).map(e=>s(e)).filter(Boolean).join(`
2
2
 
3
- `):""}function l(t){return o(t.replace(/<image>\s*<\/image>/gi,""))}function d(t){const e=o(t);if(!e)return{text:"",strippedWrapper:!1};const n=e.match(/My request for Codex:\s*([\s\S]*)$/i);if(n?.[1])return{text:l(n[1]),strippedWrapper:!0};const p=e.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);return p?.[1]?{text:l(p[1]),strippedWrapper:!0}:{text:e,strippedWrapper:!1}}const g=/^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i,m=/^<\/image>\s*$/i;function C(t){if(!Array.isArray(t))return{text:"",imagePaths:[],strippedWrapper:!1};const e=new Set,n=[];for(let r=0;r+2<t.length;r+=1){const s=t[r],i=t[r+1],u=t[r+2];if(typeof s!="object"||s===null||typeof i!="object"||i===null||typeof u!="object"||u===null)continue;const a=s,x=i,c=u;if(a.type!=="input_text"||typeof a.text!="string"||x.type!=="input_image"||c.type!=="input_text"||typeof c.text!="string"||!m.test(o(c.text)))continue;const f=o(a.text).match(g)?.[1];f&&(n.push(f),e.add(r),e.add(r+2))}const p=t.map((r,s)=>{if(e.has(s))return"";if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const i=r;return i.type==="input_text"&&typeof i.text=="string"?i.text:""}).map(r=>o(r)).filter(Boolean).join(`
3
+ `):""}function x(t){return s(t.replace(/<image>\s*<\/image>/gi,""))}const g=/^<in-app-browser-context\b(?=[^>]*\bsource=["']ambient-ui-state["'])[^>]*>[\s\S]*?<\/in-app-browser-context>\s*\n+##\s+My request(?: for Codex)?:\s*([\s\S]*)$/i;function m(t){const e=s(t);if(!e)return{text:"",strippedWrapper:!1};const n=e.match(g);if(n?.[1])return{text:x(n[1]),strippedWrapper:!0};const p=e.match(/My request for Codex:\s*([\s\S]*)$/i);if(p?.[1])return{text:x(p[1]),strippedWrapper:!0};const a=e.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);return a?.[1]?{text:x(a[1]),strippedWrapper:!0}:{text:e,strippedWrapper:!1}}const y=/^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i,h=/^<\/image>\s*$/i;function b(t){if(!Array.isArray(t))return{text:"",imagePaths:[],strippedWrapper:!1};const e=new Set,n=[];for(let r=0;r+2<t.length;r+=1){const i=t[r],o=t[r+1],u=t[r+2];if(typeof i!="object"||i===null||typeof o!="object"||o===null||typeof u!="object"||u===null)continue;const c=i,l=o,f=u;if(c.type!=="input_text"||typeof c.text!="string"||l.type!=="input_image"||f.type!=="input_text"||typeof f.text!="string"||!h.test(s(f.text)))continue;const d=s(c.text).match(y)?.[1];d&&(n.push(d),e.add(r),e.add(r+2))}const p=t.map((r,i)=>{if(e.has(i))return"";if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const o=r;return o.type==="input_text"&&typeof o.text=="string"?o.text:""}).map(r=>s(r)).filter(Boolean).join(`
4
4
 
5
- `);return{...d(p),imagePaths:n}}function E(t){if(t.namespace!=="codex_app"||typeof t.output!="string")return null;const e=o(t.output);if(!e.startsWith("<codex_delegation>")||!e.endsWith("</codex_delegation>"))return null;const n=e.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];return n?o(n):null}export{E as parseCodexDelegationInput,C as parseCodexResponseUserInput,_ as parseCodexTextElements,d as stripCodexUserMessageWrapper};
5
+ `);return{...m(p),imagePaths:n}}function R(t){if(t.namespace!=="codex_app"||typeof t.output!="string")return null;const e=s(t.output);if(!e.startsWith("<codex_delegation>")||!e.endsWith("</codex_delegation>"))return null;const n=e.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];return n?s(n):null}export{R as parseCodexDelegationInput,b as parseCodexResponseUserInput,E as parseCodexTextElements,m as stripCodexUserMessageWrapper};
@@ -1 +1 @@
1
- import{SESSION_HISTORY_LIMITS as d}from"@shennian/wire";import{getCanonicalSessionStore as b,listSessionRecords as f}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as l,getPersonalSyncIdentity as c}from"../personal-sync/epoch.js";class m{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=b(),o=e=>f().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(l(s),u(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=d.maxSubscriptionsPerSession)throw new Error("subscription_limit_exceeded");const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const t=this.store.readPage(s.sessionId,{limit:h(s.limit,d.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=t.bodyRevision,e.lastSentRevision=t.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:t.bodyRevision,firstAvailableSeq:t.firstAvailableSeq,lastAvailableSeq:t.lastAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8")),e.active=!0;for(const p of e.buffered)this.publishMutation(e,p);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),t}}handlePage(i){const s=i.params;l(s);const o=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:h(s.limit,d.defaultPageMessages),revision:o.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:o.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;l(s),u(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,o]of this.subscriptions.entries())o.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{...c(this.store.daemonInstallationId),sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){u(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function u(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function h(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>d.maxPageMessages)throw new Error("invalid history limit");return s}export{m as PersonalHistorySubscriptions};
1
+ import{SESSION_HISTORY_LIMITS as d}from"@shennian/wire";import{getCanonicalSessionStore as b,listSessionRecords as f}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as l,getPersonalSyncIdentity as c}from"../personal-sync/epoch.js";class m{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=b(),o=e=>f().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(l(s),u(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=d.maxSubscriptionsPerSession){this.client.sendRes({type:"res",id:i.id,ok:!1,error:"subscription_limit_exceeded",rejection:{code:"subscription_limit_exceeded",retryable:!0,requiredAction:"retry"}});return}const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const t=this.store.readPage(s.sessionId,{limit:p(s.limit,d.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=t.bodyRevision,e.lastSentRevision=t.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:t.bodyRevision,firstAvailableSeq:t.firstAvailableSeq,lastAvailableSeq:t.lastAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8")),e.active=!0;for(const h of e.buffered)this.publishMutation(e,h);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),t}}handlePage(i){const s=i.params;l(s);const o=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:p(s.limit,d.defaultPageMessages),revision:o.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:o.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;l(s),u(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,o]of this.subscriptions.entries())o.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{...c(this.store.daemonInstallationId),sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){u(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function u(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function p(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>d.maxPageMessages)throw new Error("invalid history limit");return s}export{m as PersonalHistorySubscriptions};
@@ -1 +1 @@
1
- import x from"node:crypto";import{acknowledgeSessionIndexUpdate as v,applyGeneratedSessionTitle as h,claimSessionTitleGenerationCandidates as R,listPendingSessionIndexUpdates as q,listSessionRecords as w,purgeCanonicalSession as _,purgeSessionRecord as A,recordSessionIndexRejection as p}from"./store.js";import{recordPersonalSyncLocalMetric as d}from"../personal-sync/metrics.js";import{SessionSyncRejectedError as E}from"../relay/client.js";import{NativeSourceDeletionStore as k}from"../native-fusion/native-source-deletions.js";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import{purgeToolDetails as $}from"./tool-detail-store.js";import{purgeQueuedSessionData as b}from"./queue.js";import{getPersonalSyncIdentity as T}from"../personal-sync/epoch.js";const c=new WeakMap,u=new WeakSet;function N(i){const n=i,t=c.get(n);if(t)return u.add(n),t;const e=P(i).finally(()=>{c.get(n)===e&&c.delete(n),u.delete(n)&&F(i)});return c.set(n,e),e}async function P(i){const n=q(),t=[];for(const e of n)try{const o=await i.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4);o&&U(e,o),v(e.session.id,e.session.indexRevision),d("indexAcknowledged")}catch(o){const r=j(e,o,i);r==="retry"&&t.push(o),d("indexFailed"),r==="retry"&&d("indexDirtyRetained")}if(t.length>0)throw new AggregateError(t,"one or more session indexes failed");for(const e of R())try{const o=await i.sendReq({type:"req",id:`session-title-generate-${x.randomUUID()}`,method:"session.title.generate",params:{...T(),...e}},15e3);if(!o.ok)continue;const r=o.payload;r?.generated&&h(e.sessionId,r)&&u.add(i)}catch(o){console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${o instanceof Error?o.message:String(o)}`)}}function U(i,n){if(!n)return;const t=n.payload;if(!t||t.daemonInstallationId!==i.daemonInstallationId||t.dataEpoch!==i.dataEpoch||t.sessionId!==i.session.id||t.indexRevision!==i.session.indexRevision)throw new Error("invalid session index acknowledgement identity")}function j(i,n,t){const e=i.session.id,o=i.session.indexRevision;if(n instanceof E){const{rejection:s}=n;if(s.requiredAction==="purge_session"&&s.code==="session_deleted"){const a=w().find(S=>S.sessionId===e),m=Math.max(1,a?.deletionRevision??0,s.authoritativeRevision??0),g=new k({daemonInstallationId:D()});return g.recordSession({sessionId:e,deletionRevision:m}),a?.agentSessionId&&g.record({sessionId:e,deletionRevision:m,agentType:a.agentType,sourceSessionKey:a.agentSessionId}),b(e),$(e),_(e),A(e),"purged"}if(s.retryable&&s.requiredAction==="retry")return d("transientRetries"),p({sessionId:e,indexRevision:o,code:s.code,disposition:"retry"});const I=s.requiredAction==="quarantine_record"||s.code==="room_authority_conflict"||s.code==="invalid_payload"||s.code==="invalid_identity",f=p({sessionId:e,indexRevision:o,code:s.code,disposition:I?"quarantined":"terminal"});return d(f==="quarantined"?"quarantinedRecords":"permanentStops"),(s.requiredAction==="refresh_machine_ownership"||s.requiredAction==="upgrade_client"||s.requiredAction==="reset_epoch"||s.requiredAction==="stop")&&t.disconnect?.(),f}const r=n instanceof Error?n.message:String(n),l=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND/i.test(r),y=p({sessionId:e,indexRevision:o,code:l?"transport_unavailable":"unstructured_rejection",disposition:l?"retry":"terminal"});return d(l?"transientRetries":"permanentStops"),y}function F(i){N(i).catch(n=>{console.error("[session-index] sync failed; dirty revisions retained for retry",n)})}export{N as publishPendingSessionIndexes,F as requestSessionIndexPublish};
1
+ import h from"node:crypto";import{acknowledgeSessionIndexUpdate as R,applyGeneratedSessionTitle as q,claimSessionTitleGenerationCandidates as w,deferSessionTitleGeneration as _,listPendingSessionIndexUpdates as A,listSessionRecords as E,purgeCanonicalSession as k,purgeSessionRecord as D,recordSessionIndexRejection as u}from"./store.js";import{recordPersonalSyncLocalMetric as d}from"../personal-sync/metrics.js";import{SessionSyncRejectedError as T}from"../relay/client.js";import{NativeSourceDeletionStore as $}from"../native-fusion/native-source-deletions.js";import{getDaemonInstallationId as b}from"../personal-sync/identity.js";import{purgeToolDetails as M}from"./tool-detail-store.js";import{purgeQueuedSessionData as N}from"./queue.js";import{getPersonalSyncIdentity as P}from"../personal-sync/epoch.js";const c=new WeakMap,p=new WeakSet;function U(n){const i=n,o=c.get(i);if(o)return p.add(i),o;const e=j(n).finally(()=>{c.get(i)===e&&c.delete(i),p.delete(i)&&I(n)});return c.set(i,e),e}async function j(n){const i=A(),o=[];for(const e of i)try{const s=await n.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4);s&&F(e,s),R(e.session.id,e.session.indexRevision),d("indexAcknowledged")}catch(s){const r=G(e,s,n);r==="retry"&&o.push(s),d("indexFailed"),r==="retry"&&d("indexDirtyRetained")}if(o.length>0)throw new AggregateError(o,"one or more session indexes failed");for(const e of w())try{const s=await n.sendReq({type:"req",id:`session-title-generate-${h.randomUUID()}`,method:"session.title.generate",params:{...P(),...e}},15e3);if(!s.ok){f(n,e.sessionId);continue}const r=s.payload;r?.generated&&q(e.sessionId,r)?p.add(n):r?.generated||f(n,e.sessionId)}catch(s){f(n,e.sessionId),console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${s instanceof Error?s.message:String(s)}`)}}function f(n,i){const o=_(i);if(!o)return;const e=Math.max(0,Date.parse(o)-Date.now());setTimeout(()=>I(n),e).unref?.()}function F(n,i){if(!i)return;const o=i.payload;if(!o||o.daemonInstallationId!==n.daemonInstallationId||o.dataEpoch!==n.dataEpoch||o.sessionId!==n.session.id||o.indexRevision!==n.session.indexRevision)throw new Error("invalid session index acknowledgement identity")}function G(n,i,o){const e=n.session.id,s=n.session.indexRevision;if(i instanceof T){const{rejection:t}=i;if(t.requiredAction==="purge_session"&&t.code==="session_deleted"){const a=E().find(v=>v.sessionId===e),g=Math.max(1,a?.deletionRevision??0,t.authoritativeRevision??0),y=new $({daemonInstallationId:b()});return y.recordSession({sessionId:e,deletionRevision:g}),a?.agentSessionId&&y.record({sessionId:e,deletionRevision:g,agentType:a.agentType,sourceSessionKey:a.agentSessionId}),N(e),M(e),k(e),D(e),"purged"}if(t.retryable&&t.requiredAction==="retry")return d("transientRetries"),u({sessionId:e,indexRevision:s,code:t.code,disposition:"retry"});const x=t.requiredAction==="quarantine_record"||t.code==="room_authority_conflict"||t.code==="invalid_payload"||t.code==="invalid_identity",m=u({sessionId:e,indexRevision:s,code:t.code,disposition:x?"quarantined":"terminal"});return d(m==="quarantined"?"quarantinedRecords":"permanentStops"),(t.requiredAction==="refresh_machine_ownership"||t.requiredAction==="upgrade_client"||t.requiredAction==="reset_epoch"||t.requiredAction==="stop")&&o.disconnect?.(),m}const r=i instanceof Error?i.message:String(i),l=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND/i.test(r),S=u({sessionId:e,indexRevision:s,code:l?"transport_unavailable":"unstructured_rejection",disposition:l?"retry":"terminal"});return d(l?"transientRetries":"permanentStops"),S}function I(n){U(n).catch(i=>{console.error("[session-index] sync failed; dirty revisions retained for retry",i)})}export{U as publishPendingSessionIndexes,I as requestSessionIndexPublish};
@@ -50,6 +50,9 @@ export type LocalSessionRecord = {
50
50
  indexQuarantineCode?: string | null;
51
51
  /** Local attempt marker only. The ephemeral titleSeed is never stored. */
52
52
  titleGenerationAttemptedAt: string | null;
53
+ /** Local retry schedule for a request that did not produce a title. */
54
+ titleGenerationNextAttemptAt?: string | null;
55
+ titleGenerationAttempts?: number;
53
56
  };
54
57
  export { getDaemonInstallationId };
55
58
  export declare function recordSession(input: {
@@ -105,6 +108,7 @@ export type SessionTitleGenerationCandidate = {
105
108
  * written; the bounded seed returned to the caller never enters local JSON.
106
109
  */
107
110
  export declare function claimSessionTitleGenerationCandidates(nowMs?: number, graceMs?: number): SessionTitleGenerationCandidate[];
111
+ export declare function deferSessionTitleGeneration(sessionId: string, nowMs?: number): string | null;
108
112
  export declare function applyGeneratedSessionTitle(sessionId: string, result: Extract<SessionTitleGenerateResponse, {
109
113
  generated: true;
110
114
  }>): boolean;
@@ -1,2 +1,2 @@
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
+ import M from"node:crypto";import d from"node:fs";import A from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as x,SESSION_SYNC_DATA_EPOCH as I,SESSION_TITLE_SOURCE_PRIORITY as m,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as D,resolvePersonalSyncV1Path as E}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as T}from"../native-fusion/native-source-deletions.js";let p=null,C=null;function w(){return E()}function u(){const t=A.join(w(),"canonical");return(!p||C!==t)&&(p=new _(t,c()),C=t),p}function P(){return A.join(w(),"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 S(t){q(P(),t)}function O(t){return b(t.payload,120)}function y(t){if(new T({rootDir:w(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function B(t){y(t.sessionId),c();const e=g(),n=e[t.sessionId],i=new Date().toISOString(),o=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,s=u().getManifest(t.sessionId),a=L(n,t),l=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),f=!!(l&&!r.includes(l)),v=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:v.preview,lastMessagePreviewMessageId:v.messageId,lastMessagePreviewAt:v.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:o,indexRevision:(n?.indexRevision??0)+1,...a,firstAvailableSeq:s?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:s?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:s?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(f?1:0),lastAttentionId:f?l:n?.lastAttentionId??null,seenAttentionIds:f&&l?[...r,l]:r,lastBodyCommittedAt:s?.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:null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt&&n.titleGenerationNextAttemptAt!==void 0?n.titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:n?.titleGenerationNextAttemptAt??null,titleGenerationAttempts:n?.titleGenerationAttempts??0};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 G(),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:I,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},S(n)}}function Z(){const t=g();let e=0;const n=new Date().toISOString();for(const[i,o]of Object.entries(t)){if(o.deletedAt||o.indexQuarantineCode||!o.localReady)continue;const s=o.indexRevision+1;t[i]={...o,indexRevision:s,indexSyncState:"pending",indexSyncStateRevision:s,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 o=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let s=t.disposition;s==="retry"&&o>=(t.maxAttempts??8)&&(s="terminal");const a=t.now??Date.now(),l=Math.min(5e3*2**Math.max(0,o-1),360*60*1e3),r=Math.round(l*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:s==="retry"?"pending":s,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:o,indexSyncNextAttemptAt:s==="retry"?new Date(a+l+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:s==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),s}const N=2*6e4;function te(t=Date.now(),e=N){const n=g(),i=[],o=new Date(t).toISOString();for(const s of Object.values(n)){if(s.titleSource!=="first_user_fallback"||s.titleLockedByUser||s.titleGenerationAttemptedAt||!s.title.trim()||s.syncedIndexRevision<s.indexRevision||s.titleGenerationNextAttemptAt&&Date.parse(s.titleGenerationNextAttemptAt)>t)continue;const a=s.status==="completed"||s.status==="failed",l=s.visibleMessageCount>=2;let r=null;if(s.agentType==="claude"&&l?r="agent_native_unavailable":a?r="terminal":l&&t-Date.parse(s.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const f=b(s.title,240);if(!f)continue;const v=M.randomUUID();n[s.sessionId]={...s,titleGenerationAttemptedAt:o,titleGenerationNextAttemptAt:null,titleGenerationAttempts:(s.titleGenerationAttempts??0)+1},i.push({sessionId:s.sessionId,requestId:v,titleSeed:f,baseTitleRevision:s.titleRevision,eligibility:r})}return i.length>0&&S(n),i}function ne(t,e=Date.now()){const n=g(),i=n[t];if(!i?.titleGenerationAttemptedAt||i.titleSource!=="first_user_fallback")return null;const o=Math.max(1,i.titleGenerationAttempts??1),s=Math.min(10*6e4,3e4*2**Math.min(o-1,5)),a=new Date(e+s).toISOString();return n[t]={...i,titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:a},S(n),a}function ie(t,e){const i=g()[t];return!i||i.titleLockedByUser||m[i.titleSource]>=m.shennian_generated||e.titleRevision<=i.titleRevision?!1:(B({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 se(t,e,n){y(t),c();const i=u().append(t,e,n);return h(t,e,i.bodyRevision),i.bodyRevision}function oe(t,e,n){y(t),c();const i=u().upsert(t,e,n);return h(t,e,i.bodyRevision),i.bodyRevision}function ae(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:x,dataEpoch:I,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:x,dataEpoch:I,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 le(t,e){y(t),c();const n=u().tombstone(t,e),i=g(),o=i[t];if(o){const s=e===o.lastMessagePreviewMessageId?k(t):{preview:o.lastMessagePreview??null,messageId:o.lastMessagePreviewMessageId??null,at:o.lastMessagePreviewAt??null};i[t]={...o,bodyRevision:n.bodyRevision,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at},S(i)}return n.bodyRevision}function h(t,e,n){const i=g(),o=i[t];if(!o)return;const s=O(e),a=s?{lastMessagePreview:s,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,l=u().getManifest(t),r=e.role==="user"&&s&&!o.title?{title:s,titleSource:"first_user_fallback",titleRevision:o.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,f=e.role==="user"&&!o.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;i[t]={...o,...r??{},...f??{},bodyRevision:n,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...a??{},firstAvailableSeq:l?.firstAvailableSeq??o.firstAvailableSeq,visibleMessageCount:l?.visibleMessageCount??o.visibleMessageCount,lastVisibleMessageId:l?.lastVisibleMessageId??o.lastVisibleMessageId,attentionCount:o.attentionCount??0,lastAttentionId:o.lastAttentionId??null,lastBodyCommittedAt:l?.lastBodyCommittedAt??o.lastBodyCommittedAt},S(i)}function k(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 o=n.entries[i],s=O(o.message);if(s)return{preview:s,messageId:o.message.id,at:new Date(o.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 G(){const t=g();let e=!1;const n=new Date().toISOString();for(const[i,o]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(o,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(o,"lastMessagePreviewAt"))continue;const s=k(i);t[i]={...o,lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function L(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");if(n.title&&n.titleSource==="first_user_fallback"&&e.titleSource==="first_user_fallback")return n;const o=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==o)throw new Error("titleLockedByUser conflicts with titleSource");const s=i===n.title&&e.titleSource===n.titleSource,a=e.titleRevision??n.titleRevision+(s?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const l=m[e.titleSource],r=m[n.titleSource];return l<r||a<n.titleRevision||s&&a===n.titleRevision||a===n.titleRevision&&!s?n:{title:i,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:o,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function re(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let o=u().readRecent(t,n).messages;return e?.before!==void 0&&(o=o.filter(s=>s.ts<e.before)),[...o].sort((s,a)=>a.ts-s.ts)}function de(){return u().listSessionIds()}function ce(){return u()}function q(t,e){const n=A.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const i=`${t}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let o;try{if(o=d.openSync(i,"wx",384),d.writeFileSync(o,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),d.fsyncSync(o),d.closeSync(o),o=void 0,d.renameSync(i,t),process.platform!=="win32"){const s=d.openSync(n,"r");try{d.fsyncSync(s)}finally{d.closeSync(s)}}}finally{o!==void 0&&d.closeSync(o);try{d.unlinkSync(i)}catch{}}}export{D as PERSONAL_SYNC_V1_ROOT_NAME,X as acknowledgeSessionIndexUpdate,se as appendMessage,ie as applyGeneratedSessionTitle,te as claimSessionTitleGenerationCandidates,ne as deferSessionTitleGeneration,ce as getCanonicalSessionStore,c as getDaemonInstallationId,ae as getMessageDeliveryStatus,K as listPendingSessionIndexUpdates,U as listSessionRecords,de as listSessions,Z as markAllSessionIndexesForRepublish,z as purgeCanonicalSession,H as purgeSessionRecord,re as readMessages,B as recordSession,ee as recordSessionIndexRejection,le as tombstoneMessage,oe as upsertMessage};
@@ -3,8 +3,8 @@ import type { PersonalAttachmentReference, PersonalSyncIdentity, SessionBodyStor
3
3
  export type BuiltinAgentType = 'claude' | 'codex' | 'workbuddy' | 'gemini' | 'cursor' | 'openclaw' | 'opencode' | 'pi' | 'manager';
4
4
  export type CustomAgentType = `custom:${string}`;
5
5
  export type AgentType = BuiltinAgentType | CustomAgentType;
6
- export declare const DISABLED_BUILTIN_AGENT_TYPES: readonly ["openclaw"];
7
- export declare const AVAILABLE_BUILTIN_AGENT_TYPES: readonly ["claude", "codex", "workbuddy", "gemini", "cursor", "opencode", "pi"];
6
+ export declare const DISABLED_BUILTIN_AGENT_TYPES: readonly ["gemini", "openclaw"];
7
+ export declare const AVAILABLE_BUILTIN_AGENT_TYPES: readonly ["claude", "codex", "workbuddy", "cursor", "opencode", "pi"];
8
8
  export type ModelInfo = {
9
9
  id: string;
10
10
  name: string;
@@ -1,11 +1,13 @@
1
1
  export const DISABLED_BUILTIN_AGENT_TYPES = [
2
+ 'gemini',
2
3
  'openclaw',
3
4
  ];
4
5
  export const AVAILABLE_BUILTIN_AGENT_TYPES = [
5
6
  'claude',
6
7
  'codex',
7
8
  'workbuddy',
8
- 'gemini',
9
+ // Gemini CLI is intentionally hidden from new sessions. Keep the historical
10
+ // AgentType and adapter intact so persisted sessions remain readable.
9
11
  'cursor',
10
12
  'opencode',
11
13
  'pi',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.68",
3
+ "version": "0.3.69",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {