shennian 0.3.38 → 0.3.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +18 -18
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/commands/room.js +3 -3
- package/dist/src/native-fusion/codex-parser.js +4 -4
- package/dist/src/native-fusion/service.d.ts +8 -0
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/native-fusion/types.d.ts +10 -0
- package/dist/src/room/host-session-observations.d.ts +1 -1
- package/dist/src/room/host-session-observations.js +2 -2
- package/dist/src/room/managed-prompt-composer.js +2 -2
- package/dist/src/room/managed-room-binding.d.ts +1 -1
- package/dist/src/room/managed-room-binding.js +1 -1
- package/dist/src/room/session-registry.d.ts +1 -1
- package/dist/src/room/session-registry.js +2 -2
- package/dist/src/session/handlers/chat.js +2 -2
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +3 -1
- package/node_modules/@shennian/wire/dist/room.d.ts +6 -1
- package/node_modules/@shennian/wire/dist/room.js +18 -1
- package/node_modules/@shennian/wire/dist/session.d.ts +10 -0
- package/package.json +1 -1
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
"file": "src/agents/model-registry/service.js",
|
|
99
|
-
"beforeBytes":
|
|
100
|
-
"afterBytes":
|
|
99
|
+
"beforeBytes": 3262,
|
|
100
|
+
"afterBytes": 1409
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"file": "src/agents/model-registry/types.js",
|
|
@@ -196,8 +196,8 @@
|
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"file": "src/commands/room.js",
|
|
199
|
-
"beforeBytes":
|
|
200
|
-
"afterBytes":
|
|
199
|
+
"beforeBytes": 15343,
|
|
200
|
+
"afterBytes": 8914
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
"file": "src/commands/runtime.js",
|
|
@@ -311,8 +311,8 @@
|
|
|
311
311
|
},
|
|
312
312
|
{
|
|
313
313
|
"file": "src/native-fusion/codex-parser.js",
|
|
314
|
-
"beforeBytes":
|
|
315
|
-
"afterBytes":
|
|
314
|
+
"beforeBytes": 27675,
|
|
315
|
+
"afterBytes": 11480
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
"file": "src/native-fusion/config.js",
|
|
@@ -336,8 +336,8 @@
|
|
|
336
336
|
},
|
|
337
337
|
{
|
|
338
338
|
"file": "src/native-fusion/service.js",
|
|
339
|
-
"beforeBytes":
|
|
340
|
-
"afterBytes":
|
|
339
|
+
"beforeBytes": 12900,
|
|
340
|
+
"afterBytes": 6147
|
|
341
341
|
},
|
|
342
342
|
{
|
|
343
343
|
"file": "src/native-fusion/state.js",
|
|
@@ -391,8 +391,8 @@
|
|
|
391
391
|
},
|
|
392
392
|
{
|
|
393
393
|
"file": "src/room/host-session-observations.js",
|
|
394
|
-
"beforeBytes":
|
|
395
|
-
"afterBytes":
|
|
394
|
+
"beforeBytes": 9450,
|
|
395
|
+
"afterBytes": 5170
|
|
396
396
|
},
|
|
397
397
|
{
|
|
398
398
|
"file": "src/room/invite-bootstrap.js",
|
|
@@ -406,13 +406,13 @@
|
|
|
406
406
|
},
|
|
407
407
|
{
|
|
408
408
|
"file": "src/room/managed-prompt-composer.js",
|
|
409
|
-
"beforeBytes":
|
|
410
|
-
"afterBytes":
|
|
409
|
+
"beforeBytes": 4431,
|
|
410
|
+
"afterBytes": 4212
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
413
|
"file": "src/room/managed-room-binding.js",
|
|
414
|
-
"beforeBytes":
|
|
415
|
-
"afterBytes":
|
|
414
|
+
"beforeBytes": 6014,
|
|
415
|
+
"afterBytes": 3277
|
|
416
416
|
},
|
|
417
417
|
{
|
|
418
418
|
"file": "src/room/managed-room-ipc.js",
|
|
@@ -446,8 +446,8 @@
|
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"file": "src/room/session-registry.js",
|
|
449
|
-
"beforeBytes":
|
|
450
|
-
"afterBytes":
|
|
449
|
+
"beforeBytes": 27201,
|
|
450
|
+
"afterBytes": 15360
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
"file": "src/session/archive-zip.js",
|
|
@@ -471,8 +471,8 @@
|
|
|
471
471
|
},
|
|
472
472
|
{
|
|
473
473
|
"file": "src/session/handlers/chat.js",
|
|
474
|
-
"beforeBytes":
|
|
475
|
-
"afterBytes":
|
|
474
|
+
"beforeBytes": 31311,
|
|
475
|
+
"afterBytes": 13268
|
|
476
476
|
},
|
|
477
477
|
{
|
|
478
478
|
"file": "src/session/handlers/control.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getAgentConfigSummary as
|
|
1
|
+
import{getAgentConfigSummary as l}from"../config-status.js";import{isCacheFresh as g,readCache as f,writeCache as m}from"./cache.js";import{discoverModelsForAgent as A}from"./discovery.js";import{getAgentRuntimeSummary as y}from"../runtime-compatibility.js";function v(t){const s=new Set,e=[];for(const o of t)!o.id||s.has(o.id)||(s.add(o.id),e.push(o));return e}function h(t){return{supportsManagedAgent:t.type!=="pi"&&t.type!=="openclaw",supportsWorker:t.type!=="pi"&&t.type!=="openclaw",supportsManagerMode:t.type==="codex"||t.type==="claude"}}function I(t){const s=f();return t.map(e=>{const o=l(e.type),r=y(e);return{type:e.type,models:s.agents[e.type]?.models??e.models??[],...h(e),...o?{config:o}:{},...r?{runtime:r}:{}}})}async function q(t,s){const e=f();for(const o of t)delete e.agents[o.type];return m(e),w(t,s)}async function w(t,s){const e=f();let o=!1;const r=[];for(const n of t){const c=e.agents[n.type];let i;if(g(c,n.version))i=c.models;else{let p=null;try{p=v(await A(n,s)),i=p.length>0?p:c?.models??n.models??[]}catch{i=c?.models??n.models??[]}if(p&&p.length>0){const a={version:n.version,refreshedAt:Date.now(),models:i};JSON.stringify(c)!==JSON.stringify(a)&&(e.agents[n.type]=a,o=!0)}}const u=l(n.type),d=y(n);r.push({type:n.type,models:i,...h(n),...u?{config:u}:{},...d?{runtime:d}:{}})}return o&&m(e),r}export{I as getCachedAgentInfos,q as refreshAgentInfos,w as resolveAgentInfos};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import g from"node:crypto";import x from"node:fs";import R from"node:path";import{PublicAttachmentCache as L}from"../room/public-attachment-cache.js";import{PublicRoomClient as j,PublicRoomClientError as U}from"../room/public-room-client.js";import{callDaemonIpc as z}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as
|
|
2
|
-
`)}catch(r){if(r instanceof U){
|
|
3
|
-
`),t.fail()}export{
|
|
1
|
+
import g from"node:crypto";import x from"node:fs";import R from"node:path";import{PublicAttachmentCache as L}from"../room/public-attachment-cache.js";import{PublicRoomClient as j,PublicRoomClientError as U}from"../room/public-room-client.js";import{callDaemonIpc as z}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as D}from"../room/managed-context.js";const M="https://shennian.net/install.md",$={write:t=>{process.stdout.write(t)},fail:()=>{process.exitCode=1}};function K(t,e={}){const a=e.client??new j,r=e.cache??new L({client:a}),s=e.io??$,d=t.command("room").description("Use one explicitly referenced Shennian Room");d.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async o=>p("room.create",s,async()=>{b("create");const n=o.name.trim();(!n||n.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const c=await a.create(n),m=u(c.room,"invalid_room_response");return typeof m.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation."),{...c,sharing:{inviteUrl:m.inviteUrl,installGuideUrl:M}}})),d.command("join <invite-url>").description("Join or request access through an official Room invitation").action(async o=>p("room.join",s,async()=>(b("join"),a.join(k(o))))),d.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async o=>p("room.status",s,()=>{const n=_();return n?(o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(n,"status",{})):a.status(w(o))})),d.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(o,n)=>{await p("room.read",s,async()=>{n.before&&n.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!S(n.before)&&i("invalid_sequence","--before sequence is invalid."),n.after&&!S(n.after)&&i("invalid_sequence","--after sequence is invalid.");const c=Number(n.limit);(!Number.isInteger(c)||c<1||c>50)&&i("invalid_limit","--limit must be between 1 and 50.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:c});const l=w(o),f=await a.read(l,{before:n.before,after:n.after,limit:c});return T(f,l,r)})}),d.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",C,[]).action(async(o,n)=>{await p("room.send",s,async()=>{const c=n.text?.trim()??"";!c&&n.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"send",{clientMessageId:`managed:${g.randomUUID()}`,text:c,files:n.file});const l=w(o),f=[];for(const I of n.file)f.push(await N(a,l,I));return a.send(l,{clientMessageId:`cli:${g.randomUUID()}`,...c?{text:c}:{},...f.length>0?{attachmentIds:f}:{}})})}),d.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(o,n)=>{await p("room.attachment.get",s,async()=>{const c=_(),m=c?o:n;return(!m||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(m))&&i("invalid_attachment_ref","attachment-id is invalid."),c?y(c,"attachment.get",{attachmentId:m}):r.materialize(w(o),m)})}),d.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async o=>p("room.leave",s,()=>(b("leave"),a.leave(A(o))))),d.command("list",{hidden:!0}).action(()=>h(s,"command_removed","Room listing is not available to Agents.",null)),d.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Room search is not available to Agents.",null)),d.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Managed Agents are created in the Shennian App or Web interface.",null))}async function p(t,e,a){try{const r=await a();e.write(`${JSON.stringify({ok:!0,command:t,result:r})}
|
|
2
|
+
`)}catch(r){if(r instanceof U){h(e,r.code,r.message,r.nextAction);return}if(r instanceof q){h(e,r.code,r.message,null);return}h(e,"room_command_failed","Shennian Room command failed.",null)}}function _(){return process.env[D]?.trim()||null}async function y(t,e,a){return z({method:"managed-room.execute",params:{context:t,action:e,...a},timeoutMs:e==="send"||e==="attachment.get"?18e4:15e3})}function b(t){_()&&i("managed_room_action_forbidden",`${t} is not available in managed Room context.`)}function w(t){return t||i("room_ref_required","Use an official Room URL returned by Shennian."),A(t)}function A(t){let e;try{e=new URL(t)}catch{i("invalid_room_ref","Use an official Room URL returned by Shennian.")}const a=e.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(e.protocol!=="https:"||e.hostname!=="app.shennian.net"&&e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_room_ref","Use an official Room URL returned by Shennian."),a[1]}function k(t){let e;try{e=new URL(t)}catch{i("invalid_invite_url","Use an official Shennian Room invitation URL.")}const a=e.pathname.match(/^\/spaces\/join\/([A-Za-z0-9_-]{32,256})$/);return(e.protocol!=="https:"||e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_invite_url","Use an official Shennian Room invitation URL."),a[1]}function S(t){return/^(0|[1-9]\d{0,19})$/.test(t)}function C(t,e){return[...e,t]}async function N(t,e,a){const r=R.resolve(a);let s;try{s=await x.promises.lstat(r)}catch{i("file_not_found",`File does not exist: ${a}`)}(!s.isFile()||s.isSymbolicLink())&&i("invalid_file",`Not a regular file: ${a}`),(s.size<1||s.size>100*1024*1024)&&i("invalid_file_size","Each file must be between 1 byte and 100 MB.");const d=await x.promises.readFile(r),v=g.createHash("sha256").update(d).digest("hex"),o=O(r),n=await t.initializeAttachment(e,{clientUploadId:`cli:${g.randomUUID()}`,name:R.basename(r),mediaKind:o.startsWith("image/")?"image":o.startsWith("audio/")?"audio":"file",mimeType:o,byteSize:s.size,sha256:v}),c=u(n.attachment,"invalid_attachment_response"),m=u(n.upload,"invalid_attachment_response");(typeof c.id!="string"||typeof m.url!="string"||m.method!=="PUT")&&i("invalid_attachment_response","Shennian returned an invalid attachment response.");const l=u(m.headers,"invalid_attachment_response");return Object.values(l).some(f=>typeof f!="string")&&i("invalid_attachment_response","Shennian returned invalid upload headers."),E(m.url),await t.upload(m.url,l,d),await t.completeAttachment(e,c.id),c.id}function O(t){const e=R.extname(t).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[e]??"application/octet-stream"}function T(t,e,a){return Array.isArray(t.messages)||i("invalid_server_response","Shennian returned invalid messages."),{messages:t.messages.map(r=>{const s=u(r,"invalid_server_response"),d=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:d.map(v=>{const o=u(v,"invalid_server_response");return(typeof o.attachmentId!="string"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||typeof o.sha256!="string")&&i("invalid_server_response","Shennian returned invalid attachment metadata."),{...o,...a.lookup(e,o)}})}})}}function u(t,e){return(!t||typeof t!="object"||Array.isArray(t))&&i(e,"Invalid structured response."),t}function E(t){let e;try{e=new URL(t)}catch{i("invalid_attachment_response","Invalid upload URL.")}const a=e.protocol==="http:"&&(e.hostname==="localhost"||e.hostname==="127.0.0.1");(e.protocol!=="https:"&&!a||e.username||e.password||e.hash)&&i("invalid_attachment_response","Invalid upload URL.")}function i(t,e){throw new U(t,e)}function h(t,e,a,r){t.write(`${JSON.stringify({ok:!1,error:e,message:a,nextAction:r})}
|
|
3
|
+
`),t.fail()}export{K as registerRoomCommand};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import T from"node:fs";import v from"node:os";import b from"node:path";import{buildApprovalPendingPayload as Q,buildUserMessagePayload as k,isSystemControlPayload as j,isToolPayload as q}from"@shennian/wire";import{buildImagePayload as V,buildToolPayload as X,inferMimeType as E,makeCursor as Y,makeEventKey as Z,normalizeText as d,parseStructuredString as P,readJsonlLines as N,readTimestamp as D,safeParse as I}from"./parser-common.js";function O(e){if(typeof e=="string"){const i=e.trim();return i?{path:i,name:b.basename(i)||"image.png",mimeType:E(i),kind:"image"}:null}if(typeof e!="object"||e===null)return null;const r=e,n=typeof r.path=="string"?r.path:typeof r.file_path=="string"?r.file_path:typeof r.saved_path=="string"?r.saved_path:"";if(!n.trim())return null;const t=typeof r.name=="string"&&r.name.trim()?r.name:b.basename(n)||"image.png",o=typeof r.mimeType=="string"&&r.mimeType.trim()?r.mimeType:E(n);return{path:n,name:t,mimeType:o,kind:"image"}}function R(e){return Array.isArray(e)?e.map(n=>{if(typeof n=="string")return n;if(typeof n=="object"&&n!==null){const t=n;return typeof t.text=="string"?t.text:""}return""}).map(n=>d(n)).filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`):""}function U(e){const r=
|
|
3
|
+
`):""}function U(e){const r=d(e);if(!r)return{text:"",strippedWrapper:!1};const n=r.match(/My request for Codex:\s*([\s\S]*)$/i);return n&&n[1]?{text:K(n[1]),strippedWrapper:!0}:{text:r,strippedWrapper:!1}}function K(e){return d(e.replace(/<image>\s*<\/image>/gi,""))}function w(e){const r=d(e);if(!r)return!1;if(j(r)||/^<environment_context>[\s\S]*<\/environment_context>$/i.test(r)&&r.includes("<current_date>")&&r.includes("<timezone>")&&r.includes("<workspace_roots>"))return!0;const n=r.startsWith("# AGENTS.md instructions for ")&&r.includes("<INSTRUCTIONS>")&&r.includes("</INSTRUCTIONS>"),t=r.includes("<environment_context>")&&r.includes("</environment_context>");return n&&t}function F(e){const r=R(e.text_elements),n=Array.isArray(e.input)?R(e.input):"",t=typeof e.message=="string"?U(e.message):{text:"",strippedWrapper:!1},o=t.text,i=r||n||o,c=t.strippedWrapper?"codex_app_context_wrapper":void 0,f=Array.isArray(e.local_images)?e.local_images.map(O).filter(a=>a!=null):[];return!i&&f.length===0||j(i)&&f.length===0||w(i)&&f.length===0?null:f.length>0?{payload:k(i,f),titleText:i,repairHint:c}:{payload:i,titleText:i,repairHint:c}}function tt(e){return e==="turn_completed"||e==="turn_complete"||e==="task_complete"}function et(e){return/approval|permission|confirm|user[_-]?input|input[_-]?required/i.test(e)}function nt(e){const r=typeof e.command=="string"?e.command:Array.isArray(e.command)?e.command.join(" "):"",n=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:r?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${r}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";return Q({title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",content:n,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 $(e,r){return Array.isArray(e)?d(e.map(n=>{if(typeof n=="string")return n;if(typeof n!="object"||n===null)return"";const t=n;return t.type!==r?"":typeof t.text=="string"?t.text:""}).filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`)):""}function
|
|
6
|
-
(exit ${String(m)})`:""].join("");
|
|
5
|
+
`)):""}function rt(e){if(!Array.isArray(e))return[];const r=[];for(const n of e){if(typeof n!="object"||n===null)continue;const t=n;if(t.type!=="input_image")continue;const o=typeof t.path=="string"?t.path:typeof t.file_path=="string"?t.file_path:typeof t.saved_path=="string"?t.saved_path:typeof t.image_path=="string"?t.image_path:"";o.trim()&&r.push({path:o,name:b.basename(o)||"image.png",mimeType:E(o),kind:"image"})}return r}function z(e){if(e.type!=="message")return null;const r=e.role==="assistant"?"agent":e.role==="user"?"user":null;if(!r)return null;if(r==="agent"){const i=$(e.content,"output_text");return j(i)?null:i?{role:r,payload:i,titleText:i}:null}const n=U($(e.content,"input_text")),t=n.text,o=rt(e.content);return!t&&o.length===0||j(t)&&o.length===0||w(t)&&o.length===0?null:{role:r,payload:o.length>0?k(t,o):t,titleText:t,repairHint:n.strippedWrapper?"codex_app_context_wrapper":void 0}}function B(e){if(!e)return"";try{const r=JSON.parse(e);if(typeof r=="object"&&r!==null){const n=r;if(n.type==="user"&&typeof n.content=="string")return d(n.content)}}catch{}return d(e)}function ot(e,r,n,t){return e.agentType!=="codex"||e.role!==r||q(e.payload)||Math.abs(e.ts-t)>300*1e3?!1:B(e.payload)===B(n)}function W(e,r,n,t){for(let o=e.length-1;o>=0;o-=1){const i=e[o];if(i){if(ot(i,r,n,t))return o;if(i.agentType==="codex"&&Math.abs(i.ts-t)>300*1e3)break}}return-1}function C(e,r,n,t,o,i,c,f,a,u,s="agent",m=!1,p){c&&e.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:Z(r,n,t),cursor:Y(r,n),role:s,ts:i,payload:c,title:f,modelId:a,workDir:u,terminal:m,repairHint:p})}function x(e,r,n,t,o,i,c,f,a,u,s,m){C(e,r,n,t,o,i,X(c,s,m),f,a,u)}function it(e,r,n,t,o,i,c,f,a){const u=typeof t.type=="string"?t.type:"";if(u){if(u==="message"){const s=z(t);if(!s||W(e,s.role,s.payload,i)>=0)return;C(e,r,n,`${u}:${s.role}`,o,i,s.payload,c,f,a,s.role,!1,s.repairHint);return}if(u==="function_call"){const s=typeof t.name=="string"?t.name:"function_call";x(e,r,n,u,o,i,s,c,f,a,P(t.arguments));return}if(u==="function_call_output"){x(e,r,n,u,o,i,"function_call_output",c,f,a,t.call_id,t.output);return}if(u==="custom_tool_call"){const s=typeof t.name=="string"?t.name:"custom_tool_call",m=t.input!==void 0?{input:t.input}:void 0;x(e,r,n,u,o,i,s,c,f,a,m);return}if(u==="custom_tool_call_output"){const s=typeof t.name=="string"?t.name:"custom_tool_call_output",m=t.output??t.result??t.content;x(e,r,n,u,o,i,s,c,f,a,t.call_id,m);return}if(u==="web_search_call"){const s=typeof t.action=="object"&&t.action!==null?t.action:null;x(e,r,n,u,o,i,"web_search",c,f,a,s??t.query);return}}}function st(e,r,n,t,o,i,c,f,a){const u=typeof t.type=="string"?t.type:"";if(u){if(u==="user_message"){const s=F(t);if(!s)return;C(e,r,n,u,o,i,s.payload,c,f,a,"user",!1,s.repairHint);return}if(u==="agent_message"){const s=typeof t.message=="string"?d(t.message):"";if(!s||j(s))return;C(e,r,n,u,o,i,s,c,f,a);return}if(u==="turn_aborted"||u==="error"){const s=typeof t.message=="string"?d(t.message):typeof t.error=="string"?d(t.error):"";if(!s)return;C(e,r,n,u,o,i,s,c,f,a,"agent",!0);return}if(et(u)){C(e,r,n,u,o,i,nt(t),c,f,a,"agent");return}if(u==="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?`
|
|
6
|
+
(exit ${String(m)})`:""].join("");x(e,r,n,u,o,i,"command_execution",c,f,a,{command:s},p);return}if(u==="patch_apply_end"){x(e,r,n,u,o,i,"apply_patch",c,f,a,t.changes,{success:t.success,stdout:t.stdout,stderr:t.stderr});return}if(u==="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:{}};x(e,r,n,u,o,i,m,c,f,a,p,t.result);return}if(u==="dynamic_tool_call_request"){const s=typeof t.tool=="string"?t.tool:"dynamic_tool_call";x(e,r,n,u,o,i,s,c,f,a,t.arguments);return}if(u==="dynamic_tool_call_response"){const s=typeof t.tool=="string"?t.tool:"dynamic_tool_call";x(e,r,n,u,o,i,s,c,f,a,t.callId??t.call_id,t.result??t.output);return}if(u==="web_search_end"){const s=typeof t.action=="object"&&t.action!==null?t.action:null;x(e,r,n,u,o,i,"web_search",c,f,a,s??t.query,t.query);return}if(u==="image_generation_end"){const s=typeof t.saved_path=="string"?t.saved_path.trim():"";if(!s)return;C(e,r,n,u,o,i,V(s),c,f,a);return}u==="view_image_tool_call"&&x(e,r,n,u,o,i,"view_image",c,f,a,t.path??t.image_path??t)}}function A(e=!1){return{sourceSessionKey:"",workDir:"",modelId:null,title:"",skipImport:e}}function H(e){if((typeof e.thread_source=="string"?e.thread_source.trim().toLowerCase():"")==="subagent")return!0;const n=typeof e.source=="object"&&e.source!==null?e.source:null;if(n&&typeof n.subagent=="object"&&n.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 ut(e){const r=T.openSync(e,"r");try{const n=[];let t=0,o="";for(;t<1024*1024;){const a=Buffer.alloc(16384),u=T.readSync(r,a,0,a.length,t);if(u<=0)break;const s=a.subarray(0,u);n.push(s),t+=u;const m=Buffer.concat(n),p=m.indexOf(10);if(p>=0){o=m.subarray(0,p).toString("utf8");break}}!o&&n.length>0&&(o=Buffer.concat(n).toString("utf8"));const i=I(o);if(!i||i.type!=="session_meta")return A();const c=typeof i.payload=="object"&&i.payload!==null?i.payload:null;if(!c)return A();if(H(c))return A(!0);const f=typeof c.id=="string"?c.id:"";return{sourceSessionKey:f,workDir:typeof c.cwd=="string"?c.cwd:"",modelId:L(c),title:J(f)??"",skipImport:!1}}finally{T.closeSync(r)}return A()}function L(e){return(typeof e.model=="string"?e.model.trim():"")||null}function ct(e,r){if((typeof e.id=="string"?e.id:typeof e.thread_id=="string"?e.thread_id:typeof e.session_id=="string"?e.session_id:"")!==r)return null;const o=(typeof e.thread_name=="string"?e.thread_name:typeof e.title=="string"?e.title:"").trim();return o?o.slice(0,120):null}function J(e){if(!e)return null;const r=b.join(v.homedir(),".codex","session_index.jsonl");if(!T.existsSync(r))return null;let n=null;return N(r,0,t=>{if(!t.trim())return;const o=I(t);if(!o)return;const i=ct(o,e);i&&(n=i)}),n}function gt(){const e=[b.join(v.homedir(),".codex","sessions"),b.join(v.homedir(),".codex","archived_sessions")],r=[],n=t=>{for(const o of T.readdirSync(t,{withFileTypes:!0})){const i=b.join(t,o.name);o.isDirectory()?n(i):o.isFile()&&o.name.endsWith(".jsonl")&&r.push(i)}};for(const t of e)T.existsSync(t)&&n(t);return[...new Set(r)].sort()}function _t(e,r){const n=[];let{sourceSessionKey:t,workDir:o,modelId:i,title:c,skipImport:f}=ut(e);if(f)return{nextOffset:T.statSync(e).size,events:[]};let a=!1;return{nextOffset:N(e,r,(s,m)=>{const p=I(s);if(!p)return;const S=typeof p.type=="string"?p.type:"",l=typeof p.payload=="object"&&p.payload!==null?p.payload:null,M=D(p.timestamp);if(!(!l||!M)){if(S==="session_meta"){if(H(l)){n.length=0,f=!0;return}t=typeof l.id=="string"?l.id:t,o=typeof l.cwd=="string"?l.cwd:o,i=L(l)??i,c||(c=J(t)??"");return}if(!f&&t){if(S==="response_item"){const y=z(l);y?.role==="user"&&y.titleText&&!c&&(c=y.titleText.slice(0,80)),it(n,e,m,l,t,M,c,i,o);return}if(S==="event_msg"){const y=typeof l.type=="string"?l.type:"";if(tt(y)){let g=!1;for(let _=n.length-1;_>=0;_-=1){const h=n[_];if(h?.agentType==="codex"&&h.role==="agent"&&!q(h.payload)){h.terminal=!0,g=!0,a=!1;break}}if(!g)for(let _=n.length-1;_>=0;_-=1){const h=n[_];if(!(h?.agentType!=="codex"||h.role!=="agent")){h.terminal=!0,g=!0,a=!1;break}}g||(a=!0);return}if(y==="user_message"){const g=F(l);if(g?.titleText&&!c&&(c=g.titleText.slice(0,80)),g){const _=W(n,"user",g.payload,M);_>=0&&n.splice(_,1)}}else if(y==="agent_message"){const g=typeof l.message=="string"?d(l.message):"";if(g&&!j(g)){const _=W(n,"agent",g,M);_>=0&&n.splice(_,1)}}const G=n.length;st(n,e,m,l,t,M,c,i,o),a&&n.length>G&&(n[n.length-1].terminal=!0,a=!1)}}}}),events:n}}export{gt as listCodexRolloutFiles,_t as parseCodexRolloutChunk};
|
|
@@ -2,9 +2,11 @@ import type { CliRelayClient } from '../relay/client.js';
|
|
|
2
2
|
export declare class NativeSessionFusionService {
|
|
3
3
|
private client;
|
|
4
4
|
private timer;
|
|
5
|
+
private activeTimer;
|
|
5
6
|
private scanPromise;
|
|
6
7
|
private pendingClaims;
|
|
7
8
|
private suppressionWindows;
|
|
9
|
+
private externalObservations;
|
|
8
10
|
constructor(client: CliRelayClient);
|
|
9
11
|
start(): void;
|
|
10
12
|
stop(): void;
|
|
@@ -17,11 +19,17 @@ export declare class NativeSessionFusionService {
|
|
|
17
19
|
sourceSessionKey?: string | null;
|
|
18
20
|
text: string;
|
|
19
21
|
managedEchoPolicy?: 'suppress_following' | 'import_following';
|
|
22
|
+
externalRunId?: string | null;
|
|
20
23
|
}): void;
|
|
21
24
|
noteManagedSourceSession(sessionId: string, agentType: string, sourceSessionKey: string | null): void;
|
|
25
|
+
private ensureActiveScanning;
|
|
26
|
+
private emitExternalActivity;
|
|
27
|
+
private emitExternalTerminal;
|
|
22
28
|
private pruneState;
|
|
23
29
|
scanNow(): Promise<void>;
|
|
24
30
|
private runScan;
|
|
31
|
+
private refreshExternalObservations;
|
|
25
32
|
private tryClaimManagedEcho;
|
|
33
|
+
private observeExternalLifecycle;
|
|
26
34
|
private isSuppressed;
|
|
27
35
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as
|
|
2
|
-
`).trim()}function N(
|
|
1
|
+
import{randomUUID as I}from"node:crypto";import S from"node:fs";import{listClaudeTranscriptFiles as v,listCodexRolloutFiles as A,listOpenCodeSessionFiles as T,parseClaudeTranscriptChunk as w,parseCodexRolloutChunk as M,parseOpenCodeSessionFile as E}from"./parsers.js";import{loadNativeScannerState as C,saveNativeScannerState as _}from"./state.js";const g=6e4,m=g*2,b=30*6e4,y=20,P=2e3,O=3e4,d=240*6e4;function x(a){return a.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function N(a){return a.split(/[\\/]+/).includes(".codex")}class L{client;timer=null;activeTimer=null;scanPromise=null;pendingClaims=new Map;suppressionWindows=new Map;externalObservations=new Map;constructor(e){this.client=e}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},g))}stop(){this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null}handleConnected(){this.start(),this.scanNow().catch(e=>{console.error("[native-fusion] initial scan failed",e)})}registerManagedSend(e){if(!e.canonicalMessageId)return;const t=e.sourceAgentType??e.agentType;if(t!=="codex"&&t!=="claude"&&t!=="opencode")return;const n=e.canonicalMessageId;if(this.pendingClaims.set(n,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:t,canonicalMessageId:e.canonicalMessageId,text:x(e.text),createdAt:Date.now(),expiresAt:Date.now()+(e.externalRunId?d:m),sourceSessionKey:e.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null}),e.externalRunId&&e.sourceSessionKey){const s=`${t}:${e.sourceSessionKey}`,i=this.externalObservations.get(s)??[];i.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(s,i),this.emitExternalActivity(i[i.length-1],"queued"),this.ensureActiveScanning()}}noteManagedSourceSession(e,t,n){if(n)for(const s of this.pendingClaims.values())s.sessionId===e&&s.sourceAgentType===t&&(s.sourceSessionKey=n)}ensureActiveScanning(){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanNow().catch(e=>{console.error("[native-fusion] active observation scan failed",e)})},P),this.scanNow().catch(()=>{}))}emitExternalActivity(e,t){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}-${e.lastPublishedAt}`,payload:{state:t==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:t==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:t}})}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[t,n]of this.pendingClaims.entries())n.expiresAt<=e&&this.pendingClaims.delete(t);for(const[t,n]of this.suppressionWindows.entries())n.endsAt<=e&&this.suppressionWindows.delete(t)}async scanNow(){if(this.client.getState()==="connected")return this.scanPromise?this.scanPromise:(this.scanPromise=this.runScan().finally(()=>{this.scanPromise=null}),this.scanPromise)}async runScan(){this.pruneState(),this.refreshExternalObservations();const e=C(),t={...e.files},n=[],s=[...A(),...v(),...T()];for(const i of s){const o=S.statSync(i),r=e.files[i];if(!r){t[i]={offset:o.size,mtimeMs:o.mtimeMs};continue}const h=N(i),c=i.endsWith(".opencode-session.json"),l=c&&r?.mtimeMs!==o.mtimeMs?r.offset:r?.offset??0,f=c?E(i,l):h?M(i,l):w(i,l);t[i]={offset:f.nextOffset,mtimeMs:o.mtimeMs};for(const u of f.events){const p=this.tryClaimManagedEcho(u);if(p){n.push(p);continue}this.isSuppressed(u)||n.push(u)}}for(let i=0;i<n.length;i+=y){const o=n.slice(i,i+y);if(o.length===0)continue;const r=o.map(({repairHint:h,...c})=>c);await this.client.sendBufferedEvent({type:"event",event:"native.session.events",id:`native-fusion-${I()}`,payload:{events:r}},6e4)}for(const i of n)this.observeExternalLifecycle(i);e.files=t,_(e)}refreshExternalObservations(){const e=Date.now();for(const[t,n]of this.externalObservations.entries()){const s=n.filter(o=>e-o.createdAt<d);if(s.length===0){this.externalObservations.delete(t);continue}this.externalObservations.set(t,s);const i=s[0];e-i.lastPublishedAt<O||(i.lastPublishedAt=e,this.emitExternalActivity(i,i.state))}}tryClaimManagedEcho(e){const t=Date.now();for(const[n,s]of this.pendingClaims.entries()){if(s.sourceAgentType!==e.agentType||s.expiresAt<=t||e.role!=="user"||!s.sourceSessionKey||s.sourceSessionKey!==e.sourceSessionKey)continue;const i=s.externalRunId?d:m;if(!(Math.abs(e.ts-s.createdAt)>i)&&x(e.payload)===s.text)return this.pendingClaims.delete(n),s.managedEchoPolicy==="suppress_following"&&this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:s.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(t,e.ts)+b}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:s.canonicalMessageId,managedEchoPolicy:s.managedEchoPolicy}}return null}observeExternalLifecycle(e){const t=`${e.agentType}:${e.sourceSessionKey}`,n=this.externalObservations.get(t);if(!n?.length)return;const s=n[0];e.claimCanonicalMessageId===s.canonicalMessageId&&s.state==="queued"&&(s.state="running",s.lastPublishedAt=Date.now(),this.emitExternalActivity(s,"running")),!(!e.terminal||s.state!=="running")&&(this.emitExternalTerminal(s),n.shift(),n.length===0?this.externalObservations.delete(t):(this.externalObservations.set(t,n),n[0].lastPublishedAt=Date.now(),this.emitExternalActivity(n[0],"queued")))}isSuppressed(e){const t=`${e.agentType}:${e.sourceSessionKey}`,n=this.suppressionWindows.get(t);return!n||e.ts<n.startedAt?!1:e.ts>n.endsAt?(this.suppressionWindows.delete(t),!1):e.role==="user"?(this.suppressionWindows.delete(t),!1):!0}}export{L as NativeSessionFusionService};
|
|
@@ -21,6 +21,16 @@ export type PendingManagedClaim = {
|
|
|
21
21
|
expiresAt: number;
|
|
22
22
|
sourceSessionKey?: string | null;
|
|
23
23
|
managedEchoPolicy: 'suppress_following' | 'import_following';
|
|
24
|
+
externalRunId?: string | null;
|
|
25
|
+
};
|
|
26
|
+
export type ExternalOwnerObservation = {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
sourceSessionKey: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
canonicalMessageId: string;
|
|
31
|
+
state: 'queued' | 'running';
|
|
32
|
+
createdAt: number;
|
|
33
|
+
lastPublishedAt: number;
|
|
24
34
|
};
|
|
25
35
|
export type ManagedSuppressionWindow = {
|
|
26
36
|
sessionId: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(o,384),process.platform==="win32"&&r.existsSync(this.filePath)){const s=r.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw b();r.unlinkSync(this.filePath)}r.renameSync(o,this.filePath),process.platform!=="win32"&&r.chmodSync(this.filePath,384)}finally{try{r.unlinkSync(o)}catch{}}}}function f(e){return v(e.machineId),y(e.runtimeAdapter),A(e.sourceSessionKey),
|
|
1
|
+
import p from"node:crypto";import r from"node:fs";import u from"node:path";import{isRoomRuntimeAdapter as P}from"@shennian/wire";import{resolveShennianPath as H}from"../config/index.js";const h=/^[a-f0-9]{64}$/,I=/^[A-Za-z][A-Za-z0-9]{0,63}$/;class n extends Error{code;constructor(t,o){super(o),this.code=t,this.name="RoomHostSessionObservationError"}}class C{filePath;state;constructor(t=H("runtime","room-host-session-observations.json")){this.filePath=t,this.state=this.load()}observe(t){S(t);const o=f(t),s=this.state.records[o],a=(t.now??new Date).toISOString(),l=!!(s&&t.eventName==="SessionStart"&&(s.lifecycleState==="closed"||s.hostContextHash!==t.hostContextHash)),c=s?.lifecycleState==="closed"&&t.eventName!=="SessionStart"?"closed":t.lifecycleState,k=l?null:s?.lastActiveAt??null,_=l?null:s?.lastIdleAt??null,d={schemaVersion:1,observationKey:o,machineId:t.machineId,runtimeAdapter:t.runtimeAdapter,sourceSessionKey:t.sourceSessionKey,hostContextHash:t.hostContextHash,workspaceHash:t.workspaceHash??s?.workspaceHash??null,workspacePath:t.workspacePath??s?.workspacePath??null,lifecycleState:c,registrationEpoch:s?s.registrationEpoch+(l?1:0):1,lastEventName:t.eventName,observedAt:s?.observedAt??a,lastSeenAt:a,lastActiveAt:c==="active"?a:k,lastIdleAt:c==="idle"?a:_,closedAt:c==="closed"?s?.closedAt??a:null};return this.state.records[o]=d,this.persist(),m(d)}getBySource(t){const o=this.state.records[f(t)];return o?m(o):null}list(){return Object.values(this.state.records).sort((t,o)=>t.observedAt.localeCompare(o.observedAt)).map(m)}load(){try{const t=r.lstatSync(this.filePath);if(!t.isFile()||t.isSymbolicLink())throw b();return process.platform!=="win32"&&(t.mode&63)!==0&&r.chmodSync(this.filePath,384),g(JSON.parse(r.readFileSync(this.filePath,"utf8")))}catch(t){if(t.code==="ENOENT")return{schemaVersion:1,records:{}};throw t instanceof n?t:new n("corrupt_observation_store","Room host Session observations are corrupt.")}}persist(){const t=u.dirname(this.filePath);r.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(t,448);const o=`${this.filePath}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;try{if(r.writeFileSync(o,`${JSON.stringify(this.state,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(o,384),process.platform==="win32"&&r.existsSync(this.filePath)){const s=r.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw b();r.unlinkSync(this.filePath)}r.renameSync(o,this.filePath),process.platform!=="win32"&&r.chmodSync(this.filePath,384)}finally{try{r.unlinkSync(o)}catch{}}}}function f(e){return v(e.machineId),y(e.runtimeAdapter),A(e.sourceSessionKey),p.createHash("sha256").update(`${e.machineId}\0${e.runtimeAdapter}\0${e.sourceSessionKey}`,"utf8").digest("hex")}function S(e){v(e.machineId),y(e.runtimeAdapter),A(e.sourceSessionKey),h.test(e.hostContextHash)||i("host_context_hash"),e.workspaceHash!=null&&!h.test(e.workspaceHash)&&i("workspace_hash"),e.workspacePath!=null&&w(e.workspacePath),["active","idle","closed"].includes(e.lifecycleState)||i("lifecycle_state"),I.test(e.eventName)||i("event_name")}function v(e){/^[A-Za-z0-9_.:@/-]{1,512}$/.test(e)||i("machine_id")}function y(e){P(e)||i("runtime_adapter")}function A(e){(!e||e.length>1024||/[\0\r\n]/.test(e))&&i("source_session_key")}function g(e){if(!e||typeof e!="object"||e.schemaVersion!==1)throw new n("corrupt_observation_store","Unsupported observation store.");const t=e.records;if(!t||typeof t!="object"||Array.isArray(t))throw new n("corrupt_observation_store","Invalid observation records.");const o={};for(const[s,a]of Object.entries(t)){if(x(a),s!==a.observationKey||s!==f(a))throw new n("corrupt_observation_store","Observation key mismatch.");o[s]={...a,workspacePath:a.workspacePath??null,lastActiveAt:a.lastActiveAt??(a.lifecycleState==="active"?a.lastSeenAt:null),lastIdleAt:a.lastIdleAt??(a.lifecycleState==="idle"?a.lastSeenAt:null)}}return{schemaVersion:1,records:o}}function x(e){(!e||e.schemaVersion!==1)&&i("observation"),S({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:e.hostContextHash,workspaceHash:e.workspaceHash,workspacePath:e.workspacePath??null,lifecycleState:e.lifecycleState,eventName:e.lastEventName}),h.test(e.observationKey)||i("observation_key"),(!Number.isInteger(e.registrationEpoch)||e.registrationEpoch<1)&&i("registration_epoch");for(const t of[e.observedAt,e.lastSeenAt])Number.isFinite(Date.parse(t))||i("observation_time");for(const t of[e.lastActiveAt,e.lastIdleAt])t!=null&&!Number.isFinite(Date.parse(t))&&i("transition_time");e.closedAt!=null&&!Number.isFinite(Date.parse(e.closedAt))&&i("closed_at"),e.workspacePath!=null&&w(e.workspacePath)}function w(e){(!u.isAbsolute(e)||e.length>4096||/[\0\r\n]/.test(e))&&i("workspace_path")}function i(e){throw new n(`invalid_${e}`,`Invalid ${e}.`)}function b(){return new n("unsafe_observation_store","Room host Session observation store is not a regular file.")}function m(e){return structuredClone(e)}export{n as RoomHostSessionObservationError,C as RoomHostSessionObservationStore,f as buildRoomHostSessionObservationKey};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import $ from"node:crypto";const y="managed-room-cli-v1",d=["shennian room status","shennian room read [--before <sequence> | --after <sequence>] [--limit <1-50>]","shennian room send [--text <\u5185\u5BB9>] [--file <\u672C\u5730\u6587\u4EF6\u8DEF\u5F84> ...]","shennian room attachment get <attachment-id>"];function w(n){const t=e(n.agent.displayName,191,"\u5F53\u524D\u667A\u80FD\u4F53"),r=e(n.agent.description??"",1e3,"\u672A\u8BBE\u7F6E")||"\u672A\u8BBE\u7F6E",c=e(n.room.name,191,"\u5F53\u524D\u7FA4\u804A"),s=(n.room.members??[]).slice(0,100).map(o=>{const g=e(o.displayName,191,"\u7FA4\u6210\u5458"),
|
|
1
|
+
import $ from"node:crypto";const y="managed-room-cli-v1",d=["shennian room status","shennian room read [--before <sequence> | --after <sequence>] [--limit <1-50>]","shennian room send [--text <\u5185\u5BB9>] [--file <\u672C\u5730\u6587\u4EF6\u8DEF\u5F84> ...]","shennian room attachment get <attachment-id>"];function w(n){const t=e(n.agent.displayName,191,"\u5F53\u524D\u667A\u80FD\u4F53"),r=e(n.agent.description??"",1e3,"\u672A\u8BBE\u7F6E")||"\u672A\u8BBE\u7F6E",c=e(n.room.name,191,"\u5F53\u524D\u7FA4\u804A"),s=(n.room.members??[]).slice(0,100).map(o=>{const g=e(o.displayName,191,"\u7FA4\u6210\u5458"),u=o.principalType==="agent"?"\u667A\u80FD\u4F53":"\u771F\u4EBA",h=o.principalType==="agent"&&o.description?`\uFF5C${e(o.description,1e3,"")}`:"",f=o.principalType==="agent"&&o.ownerDisplayName?`\uFF5C\u6240\u5C5E\u6210\u5458\uFF1A${e(o.ownerDisplayName,191,"")}`:"";return`- ${g}\uFF5C${u}${h}${f}`}),l=[`[\u667A\u80FD\u4F53\u8EAB\u4EFD]
|
|
2
2
|
\u540D\u79F0\uFF1A${t}
|
|
3
3
|
\u516C\u5F00\u804C\u8D23\uFF1A${r}`,`[\u5F53\u524D\u7FA4\u804A]
|
|
4
4
|
\u7FA4\u804A\uFF1A${c}
|
|
@@ -21,4 +21,4 @@ ${d.join(`
|
|
|
21
21
|
`)}
|
|
22
22
|
--file \u53EF\u91CD\u590D\uFF0C\u4E00\u6761\u6D88\u606F\u6700\u591A 10 \u4E2A\u6587\u4EF6\uFF1B\u6B63\u6587\u548C\u6587\u4EF6\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u3002\u63D0\u53CA\u6210\u5458\u65F6\u5728\u6B63\u6587\u4E2D\u4F7F\u7528 @\u552F\u4E00\u6635\u79F0\u3002\u4E0D\u8981\u4F20\u5165\u6216\u67E5\u627E\u5185\u90E8 ID\u3002`].filter(Boolean).join(`
|
|
23
23
|
|
|
24
|
-
`);if(!p.trim()||!t||!c)throw new Error("managed_room_prompt_incomplete");return{systemPrompt:p,snapshot:{providerBaseVersion:"provider-owned",managedRoomPolicyVersion:y,sessionModeInstructionsHash:i?m(i):null,agentSystemPromptHash:a?m(a):null,trustedRoomContextHash:m(l),allowedRoomCommands:d}}}function e(n,t,r){return(String(n).replace(/[\u0000-\u001f\u007f]/g," ").replace(/\s+/g," ").trim().slice(0,t)||r).replace(/[<>\
|
|
24
|
+
`);if(!p.trim()||!t||!c)throw new Error("managed_room_prompt_incomplete");return{systemPrompt:p,snapshot:{providerBaseVersion:"provider-owned",managedRoomPolicyVersion:y,sessionModeInstructionsHash:i?m(i):null,agentSystemPromptHash:a?m(a):null,trustedRoomContextHash:m(l),allowedRoomCommands:d}}}function e(n,t,r){return(String(n).replace(/[\u0000-\u001f\u007f]/g," ").replace(/\s+/g," ").trim().slice(0,t)||r).replace(/[<>\u005b\u005d]/g,s=>`\\${s}`)}function M(n){return n==="auto_follow"?"\u81EA\u52A8\u53C2\u4E0E":n==="mention_only"?"\u4EC5\u88AB\u63D0\u53CA\u65F6\u53C2\u4E0E":"\u6682\u505C"}function m(n){return $.createHash("sha256").update(n,"utf8").digest("hex")}export{y as MANAGED_ROOM_POLICY_VERSION,w as composeManagedRoomPrompt};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentType } from '@shennian/wire';
|
|
2
2
|
import type { HttpRoomBindingClient } from './room-binding-client.js';
|
|
3
3
|
import type { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
4
4
|
import type { RoomSessionRegistry } from './session-registry.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import y from"node:crypto";class
|
|
1
|
+
import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import I from"node:crypto";class b{deps;constructor(e){this.deps=e}async bind(e){const{machineId:n,machineToken:o}=this.deps.getMachineCredentials();if(!n||!o)throw new Error("Machine is not paired with Shennian.");const t=c(e.agentType),d=this.deps.hostProcessIdentity(),s=this.deps.registry.register({machineId:n,runtimeAdapter:t,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:d,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});this.deps.observations.observe({machineId:n,runtimeAdapter:t,sourceSessionKey:e.agentSessionId,hostContextHash:g(d),workspaceHash:g(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedBind"});const i=await this.deps.bindingClient.create({machineToken:o,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:s.registryKey,activationCapability:"managed",activationMode:"auto_follow"});return this.deps.registry.setBinding({registryKey:s.registryKey,bindingId:i.binding.id,roomId:i.binding.roomId,agentIdentityId:i.binding.agentIdentityId,agentMembershipId:i.binding.agentMembershipId,status:i.binding.status,activationMode:i.binding.activationMode}),i}async restart(e){const{machineId:n,machineToken:o}=this.deps.getMachineCredentials();if(!n||!o)throw new Error("Machine is not paired with Shennian.");const t=c(e.agentType),d=this.deps.hostProcessIdentity(),s=this.deps.registry.register({machineId:n,runtimeAdapter:t,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:d,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});this.deps.observations.observe({machineId:n,runtimeAdapter:t,sourceSessionKey:e.agentSessionId,hostContextHash:g(d),workspaceHash:g(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedRestart"});const i=await this.deps.bindingClient.restart({machineToken:o,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:s.registryKey});this.deps.registry.setBinding({registryKey:s.registryKey,bindingId:i.binding.id,roomId:i.binding.roomId,agentIdentityId:i.binding.agentIdentityId,agentMembershipId:i.binding.agentMembershipId,status:i.binding.status,activationMode:i.binding.activationMode}),this.deps.registry.advanceCursor({registryKey:s.registryKey,cursor:"delivered",sequence:i.resumeSequence}),this.deps.registry.advanceCursor({registryKey:s.registryKey,cursor:"activated",sequence:i.resumeSequence});for(const a of this.deps.registry.list())a.registryKey!==s.registryKey&&a.nianSessionId===e.previousNianSessionId&&a.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(a.registryKey);return i}}function g(r){return I.createHash("sha256").update(r,"utf8").digest("hex")}function c(r){const e=y(r);if(e)return e;throw new Error(`Agent ${r} does not support managed Room binding.`)}export{b as TrustedManagedRoomBinder};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RoomActivationMode, type RoomBindingStatus, type RoomRuntimeAdapter } from '@shennian/wire';
|
|
2
2
|
export type RoomSessionCapabilities = {
|
|
3
3
|
canIdentifySession: boolean;
|
|
4
4
|
canBindAutomatically: boolean;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const s=u.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function
|
|
1
|
+
import w from"node:crypto";import u from"node:fs";import q from"node:path";import{isRoomRuntimeAdapter as C}from"@shennian/wire";import{resolveShennianPath as M}from"../config/index.js";const y=500,A=20,T=/^[A-Za-z0-9_.:@/-]{1,512}$/,I=new Set(["pending_approval","active","paused","detached"]),S=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class j{filePath;state;mutexTails=new Map;constructor(e=M("runtime","room-session-registry.json"),i=new Date){this.filePath=e,this.state=this.load(),this.markPersistedActivationsInterrupted(i)&&this.persist()}register(e){c(e.machineId,"machine_id"),f(e.runtimeAdapter),_(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),P(e.capabilities);const i=v(e),s=(e.now??new Date).toISOString(),n=this.state.records[i];if(Object.values(this.state.records).find(h=>h.registryKey!==i&&h.nianSessionId===e.nianSessionId&&h.lifecycleState!=="closed"))throw new o("nian_session_already_registered","This Shennian session is already registered to another native Agent session.");const r=!!(n&&n.hostProcessIdentity!==e.hostProcessIdentity),l={schemaVersion:1,registryKey:i,machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,panelContextKey:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,registrationEpoch:n?n.registrationEpoch+(r?1:0):1,lifecycleState:"active",capabilities:{...e.capabilities},binding:n?.binding??null,registeredAt:n?.registeredAt??s,lastSeenAt:s,updatedAt:s};return this.state.records[i]=l,this.persist(),d(l)}getBySource(e){const i=this.state.records[v(e)];return i?d(i):null}getByNianSessionId(e){c(e,"nian_session_id");const i=Object.values(this.state.records).find(s=>s.nianSessionId===e&&s.lifecycleState!=="closed");return i?d(i):null}getByRegistryKey(e){if(!/^[a-f0-9]{64}$/.test(e))throw new o("invalid_registry_key","Invalid Room session registry key.");const i=this.state.records[e];return i?d(i):null}list(){return Object.values(this.state.records).sort((e,i)=>e.registeredAt.localeCompare(i.registeredAt)).map(d)}setBinding(e){const i=this.requireRecord(e.registryKey);for(const[a,r]of Object.entries({binding_id:e.bindingId,room_id:e.roomId,agent_identity_id:e.agentIdentityId}))c(r,a);if(!I.has(e.status))throw new o("invalid_binding_status","Invalid binding status.");if(e.status==="pending_approval"&&e.agentMembershipId!==null||(e.status==="active"||e.status==="paused")&&e.agentMembershipId===null)throw new o("invalid_agent_membership_id","Pending Room bindings cannot claim an Agent membership.");e.agentMembershipId!==null&&c(e.agentMembershipId,"agent_membership_id");const s=e.activationMode??"auto_follow";if(!S.has(s))throw new o("invalid_activation_mode","Invalid activation mode.");if(i.binding&&i.binding.status!=="detached"&&(i.binding.bindingId!==e.bindingId||i.binding.roomId!==e.roomId))throw new o("session_already_bound","An Agent session can have only one active Room binding.");const n=i.binding?.activationMode;return i.binding={bindingId:e.bindingId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,agentMembershipId:e.agentMembershipId,status:e.status,activationMode:s,lastDeliveredSequence:i.binding?.lastDeliveredSequence??"0",lastActivatedSequence:i.binding?.lastActivatedSequence??"0",activationQueue:i.binding?.activationQueue??[],activeActivation:i.binding?.activeActivation??null,mentionContext:n==="mention_only"&&s==="mention_only"?i.binding?.mentionContext??[]:[],loopBreaker:i.binding?.loopBreaker??{windowStartedAt:null,activationCount:0,pausedUntil:null}},this.touchAndPersist(i,e.now),d(i)}detachBinding(e,i=new Date){const s=this.requireRecord(e);return s.binding&&(s.binding.status="detached",s.binding.activationMode="paused",s.binding.activationQueue=[],s.binding.mentionContext=[]),this.touchAndPersist(s,i),d(s)}advanceCursor(e){const i=this.requireBoundRecord(e.registryKey);p(e.sequence);const s=e.cursor==="delivered"?"lastDeliveredSequence":"lastActivatedSequence";if(BigInt(e.sequence)<BigInt(i.binding[s]))throw new o("cursor_regression","Room cursor cannot move backwards.");return i.binding[s]=e.sequence,this.touchAndPersist(i,e.now),d(i)}enqueueActivation(e,i,s=new Date,n=!1){const a=this.requireBoundRecord(e);m(i);const r=a.binding.activationQueue;if(!r.some(l=>l.causeMessageId===i.causeMessageId)){if(r.length>=y)throw new o("activation_queue_full","Agent activation queue is full.");r.push(d(i))}return n&&(a.binding.mentionContext=[]),this.touchAndPersist(a,s),d(a)}appendMentionContext(e,i,s=new Date){const n=this.requireBoundRecord(e);b(i);const a=n.binding.mentionContext??[];return a.some(r=>r.sequence===i.sequence)||(a.push(d(i)),n.binding.mentionContext=a.slice(-A)),this.touchAndPersist(n,s),d(n)}dequeueActivation(e,i,s=new Date){const n=this.requireBoundRecord(e);return n.binding.activationQueue=n.binding.activationQueue.filter(a=>a.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}markActivationDispatching(e,i,s=new Date){const n=this.requireBoundRecord(e),a=n.binding.activationQueue.find(r=>r.causeMessageId===i);if(!a)throw new o("activation_not_found","Agent activation is unavailable.");return n.binding.activeActivation={...d(a),dispatchState:"dispatching",dispatchedAt:s.toISOString(),interruptedAt:void 0},n.binding.activationQueue=n.binding.activationQueue.filter(r=>r.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}clearActiveActivation(e,i=new Date){const s=this.requireBoundRecord(e);return s.binding.activeActivation=null,this.touchAndPersist(s,i),d(s)}retryActiveActivation(e){const i=this.requireBoundRecord(e.registryKey),s=i.binding.activeActivation;if(!s)return{queued:!1,attempts:0};if(x(s))throw new o("activation_interrupted_requires_explicit_retry","Interrupted Agent activation requires an explicit retry.");return this.requeueActiveActivation(i,e)}requeueActiveActivation(e,i){const s=e.binding.activeActivation,n=i.now??new Date,{dispatchState:a,dispatchedAt:r,interruptedAt:l,...h}=s,g={...h,attempts:s.attempts+1};return e.binding.activeActivation=null,g.attempts<i.maxAttempts?(e.binding.activationQueue.some(R=>R.causeMessageId===g.causeMessageId)||e.binding.activationQueue.unshift(g),this.touchAndPersist(e,n),{queued:!0,attempts:g.attempts}):(e.binding.loopBreaker.pausedUntil=new Date(n.getTime()+i.pauseMs).toISOString(),this.touchAndPersist(e,n),{queued:!1,attempts:g.attempts})}markPersistedActivationsInterrupted(e){const i=e.toISOString();let s=!1;for(const n of Object.values(this.state.records)){const a=n.binding?.activeActivation;!a||x(a)||(a.dispatchState="interrupted",a.interruptedAt=i,n.updatedAt=i,s=!0)}return s}claimActivation(e){const i=this.requireBoundRecord(e.registryKey),s=e.now??new Date,n=i.binding.loopBreaker;return n.pausedUntil&&Date.parse(n.pausedUntil)>s.getTime()?{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!1}:((!n.windowStartedAt||s.getTime()-Date.parse(n.windowStartedAt)>=e.windowMs)&&(n.windowStartedAt=s.toISOString(),n.activationCount=0,n.pausedUntil=null),n.activationCount>=e.maxActivations?(n.pausedUntil=new Date(s.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,s),{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!0}):(n.activationCount+=1,this.touchAndPersist(i,s),{allowed:!0,pausedUntil:null}))}updateLifecycle(e,i,s=new Date){const n=this.requireRecord(e);return n.lifecycleState=i,i==="closed"&&n.binding&&(n.binding.status="detached",n.binding.activationMode="paused",n.binding.activationQueue=[],n.binding.mentionContext=[]),this.touchAndPersist(n,s),d(n)}async runExclusive(e,i){this.requireRecord(e);const s=this.mutexTails.get(e)??Promise.resolve();let n;const a=new Promise(l=>{n=l}),r=s.then(()=>a);this.mutexTails.set(e,r),await s;try{return await i()}finally{n(),this.mutexTails.get(e)===r&&this.mutexTails.delete(e)}}requireRecord(e){const i=this.state.records[e];if(!i)throw new o("session_registration_not_found","Agent session is not registered.");return i}requireBoundRecord(e){const i=this.requireRecord(e);if(!i.binding||i.binding.status!=="active"&&i.binding.status!=="paused")throw new o("session_not_bound","Agent session has no active Room binding.");return i}touchAndPersist(e,i=new Date){e.updatedAt=i.toISOString(),this.persist()}load(){try{const e=u.lstatSync(this.filePath);if(!e.isFile()||e.isSymbolicLink())throw new o("unsafe_session_registry","Room session registry is not a regular file.");process.platform!=="win32"&&(e.mode&63)!==0&&u.chmodSync(this.filePath,384);const i=JSON.parse(u.readFileSync(this.filePath,"utf8"));return D(i)}catch(e){if(k(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=q.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${w.randomBytes(6).toString("hex")}`;try{if(u.writeFileSync(i,`${JSON.stringify(this.state,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const s=u.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function v(t){return c(t.machineId,"machine_id"),f(t.runtimeAdapter),_(t.sourceSessionKey),w.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function D(t){if(!t||typeof t!="object"||t.schemaVersion!==1)throw new o("corrupt_session_registry","Unsupported Room session registry.");const e=t.records;if(!e||typeof e!="object"||Array.isArray(e))throw new o("corrupt_session_registry","Room session records are invalid.");const i={};for(const[s,n]of Object.entries(e)){if(B(n),s!==n.registryKey||s!==v(n))throw new o("corrupt_session_registry","Room session registry key mismatch.");i[s]={...n,binding:n.binding?{...n.binding,activeActivation:n.binding.activeActivation??null,mentionContext:n.binding.mentionContext??[]}:null}}return{schemaVersion:1,records:i}}function B(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),f(t.runtimeAdapter),_(t.sourceSessionKey),c(t.nianSessionId,"nian_session_id"),c(t.hostProcessIdentity,"host_process_identity"),t.panelContextKey!==t.nianSessionId)throw new o("corrupt_session_registry","Invalid panel context.");if(!Number.isInteger(t.registrationEpoch)||t.registrationEpoch<1)throw new o("corrupt_session_registry","Invalid registration epoch.");P(t.capabilities),t.binding&&O(t.binding)}function O(t){if(c(t.bindingId,"binding_id"),c(t.roomId,"room_id"),c(t.agentIdentityId,"agent_identity_id"),!I.has(t.status))throw new o("corrupt_session_registry","Invalid binding status.");if(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&t.agentMembershipId===null)throw new o("corrupt_session_registry","Invalid Agent membership.");if(t.agentMembershipId!==null&&c(t.agentMembershipId,"agent_membership_id"),!S.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(p(t.lastDeliveredSequence),p(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>y)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(m),t.activeActivation!=null&&m(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>A))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(b)}function b(t){if(p(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function m(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),p(t.sequence),!Number.isInteger(t.hopCount)||t.hopCount<0||t.hopCount>32)throw new o("invalid_hop_count","Invalid Agent activation hop count.");if(!Number.isInteger(t.attempts)||t.attempts<0||t.attempts>100)throw new o("invalid_activation_attempts","Invalid Agent activation attempts.");if(!Number.isFinite(new Date(t.enqueuedAt).getTime()))throw new o("invalid_activation_time","Invalid Agent activation time.");if(t.deliveryText!==void 0&&(typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3))throw new o("invalid_activation_text","Invalid Agent activation text.");if(t.systemPrompt!==void 0&&(typeof t.systemPrompt!="string"||!t.systemPrompt.trim()||t.systemPrompt.length>24e3))throw new o("invalid_activation_prompt","Invalid Agent activation prompt.");if(t.promptSnapshot!==void 0&&!E(t.promptSnapshot))throw new o("invalid_activation_prompt_snapshot","Invalid Agent activation prompt snapshot.");if(t.dispatchState!==void 0&&t.dispatchState!=="dispatching"&&t.dispatchState!=="interrupted")throw new o("invalid_activation_state","Invalid Agent activation state.");if(t.dispatchedAt!==void 0&&!Number.isFinite(new Date(t.dispatchedAt).getTime()))throw new o("invalid_activation_dispatch_time","Invalid Agent activation dispatch time.");if(t.dispatchState===void 0&&t.dispatchedAt!==void 0)throw new o("invalid_activation_dispatch_time","Queued Agent activation cannot have a dispatch time.");if(t.dispatchState==="interrupted"&&(t.interruptedAt===void 0||!Number.isFinite(new Date(t.interruptedAt).getTime())))throw new o("invalid_activation_interrupted_time","Invalid Agent activation interruption time.");if(t.dispatchState!=="interrupted"&&t.interruptedAt!==void 0)throw new o("invalid_activation_interrupted_time","Unexpected Agent activation interruption time.")}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const e=t,i=s=>s===null||typeof s=="string"&&/^[a-f0-9]{64}$/.test(s);return typeof e.managedRoomPolicyVersion=="string"&&e.managedRoomPolicyVersion.length<=64&&i(e.sessionModeInstructionsHash)&&i(e.agentSystemPromptHash)&&typeof e.trustedRoomContextHash=="string"&&/^[a-f0-9]{64}$/.test(e.trustedRoomContextHash)}function x(t){return t.dispatchState==="interrupted"}function P(t){if(!t||["canIdentifySession","canBindAutomatically","canOpenEmbeddedRoomPage","canResumeIdleSession","canActivateWhileHostOpen"].some(i=>typeof t[i]!="boolean"))throw new o("invalid_session_capabilities","Invalid Agent session capabilities.")}function f(t){if(!C(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function _(t){if(!t||t.length>1024||/[\0\r\n]/.test(t))throw new o("invalid_source_session_key","Invalid native Agent session key.")}function c(t,e){if(!T.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function p(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new o("invalid_room_sequence","Invalid Room sequence.")}function d(t){return structuredClone(t)}function k(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{j as RoomSessionRegistry,o as RoomSessionRegistryError,v as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ie(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const r={runId:e.runId,sourceSeq:e.seq},o=se(e.args);return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:r,...e.name?{name:e.name}:{},...o!==void 0?{argsSummary:o}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const le=3e4;function ue(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ce(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function L(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function H(e,t,a){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ge(e,t){F({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function U(e){return ae(e)}function j(e,t,a){return{}}function B(e,t,a,r,o,l){e.configure?.({sessionId:t,externalChannel:r??null,env:{...U(a),...j(t,r,o),...l?{SHENNIAN_MANAGED_ROOM_CONTEXT:l}:{}}})}function fe(e,t,a){if(e!=="claude"||!a)return t;const r=t.trim();return!!r&&r.startsWith("-")&&!r.includes("/")?te(a)??t:t}function pe(e,t,a,r){let o=null;function l(n,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ie(n,t,s),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function f(n){if(!n.currentRunId||!n.currentRunPhase)return;const s=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:s,runPhase:n.currentRunPhase},seq:s,id:`agent-heartbeat-${n.currentRunId}-${s}-${Date.now()}`})}function y(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{f(n)},le),n.heartbeatTimer.unref?.())}function i(n){const s=n?.pendingTextEvent;!n||!s||!s.text||(l({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),n.pendingTextEvent=null)}r.on("agentEvent",n=>{const s=e.sessions.get(t),T=ue(n),I=n.state==="final"||n.state==="error"||n.state==="aborted";if(s&&(s.nextEventSeq=n.seq+1,I||(s.currentRunId=n.runId),!I&&T&&(s.currentRunPhase=T,y(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&w({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?E(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?E(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?E(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:Y(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&E(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message});const h=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(h,(e.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,L(a,n.source),n.agentSessionId),s&&F({sessionId:t,agentType:a,workDir:s.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const S=n.text??"";if(!S)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==n.runId&&i(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=S,s.pendingTextEvent.seq=n.seq);return}let x={};if(n.state==="final"){i(s);const S=e.runTextAcc.get(h)??"";S&&(x={messageSummary:de(S)}),e.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(i(s),e.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&i(s);I&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),e.chatQueue?.noteTerminal(t)),l(n,x)}),r.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function me(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function C(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function J(e,t,a,r,o,l,g){return g?e.send(t,a,r,o,l,g):l?.trim()?e.send(t,a,r,o,l):o?.length?e.send(t,a,r,o):r?e.send(t,a,r):e.send(t,a)}async function he(e,t,a,r,o,l,g,f){e.evictIdleSessions();const y=X(a);if(!y)throw new Error(`Unsupported agent: ${a}`);const i=r==="manager"&&a!=="manager"?new ee(y,a==="claude"?"claude":"codex"):y;B(i,t,a,g,f),await i.start(t,o,l);const n={adapter:i,workDir:o,agentType:a,sessionMode:r,agentSessionId:l??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:g??null,externalReplyTarget:f??null,externalChannelEnv:{...U(a),...j(t,g,f)}};return e.sessions.set(t,n),pe(e,t,a,i),n}function ye(e,t){const a=e.sessions.get(t),r=a?.currentRunId;if(!a||!r)return;const o=a.nextEventSeq;e.runTextAcc.delete(`${t}:${r}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:r,seq:o},seq:o,id:`agent-evt-${r}-${o}`}),e.chatQueue?.noteTerminal(t)}async function Me(e,t,a){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}me(e,t.id);const{sessionId:r,text:o,agentType:l,sessionMode:g,managerConfig:f,workDir:y,agentSessionId:i,modelId:n,systemPrompt:s,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:I,reasoningEffort:h,clientMessageId:x,sessionListProjection:S,waitForDispatch:K,responseId:Q,roomContextRegistryKey:Ie,managedRoomContextToken:$,suppressExternalOwnerEcho:V}=t.params,k=Q||t.id;ne(S);const b=null,P="";if(!r||!o){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:"sessionId and text are required"});return}const c=l,R=c==="manager"||g==="manager"?"manager":"standard";R==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,I??null);const q=(c==="claude"||c==="codex"||c==="workbuddy")&&typeof h=="string"&&h.trim()?h.trim():void 0,A=e.resolvePath(fe(c,y||v.homedir(),i)||v.homedir()),M=A,_=oe(t.params.attachments),p=_?.length?await re({text:o,attachments:_,workDir:A}):{text:o,attachments:_,localized:!1};let d=e.sessions.get(r);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==c||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==R||d.workDir!==A||JSON.stringify(d.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}else if(i&&d.agentSessionId!==i)try{await d.adapter.resume(i),d.agentSessionId=i}catch{e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}}if(!d)try{d=await he(e,r,c,R,A,i,b,P)}catch(u){const m=u instanceof Error&&u.message.startsWith("Unsupported agent:")?u.message:`Failed to start ${l}: ${u instanceof Error?u.message:String(u)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${l} workDir=${A} agentSessionId=${i??""}: ${m}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:m,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:m});return}$&&/^mrc_[A-Za-z0-9_-]{43}$/.test($)&&B(d.adapter,r,c,b,P,$);const W={id:x??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:Z(p.text,p.attachments)};w({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:c,sessionMode:R,managerConfig:f??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null,reasoningEffort:q}});const z=u=>{ge(e,{sessionId:r,agentType:c,sessionMode:R,managerConfig:f??null,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null}),d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:r,agentType:c,sourceAgentType:L(c,n),canonicalMessageId:x??null,sourceSessionKey:d.agentSessionId??i??null,text:p.text,managedEchoPolicy:u?.deliveryMode==="external_owner_queue"&&!V?"import_following":"suppress_following"}),E(r,W),H(e,W,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})},N=async(u,m)=>{const D=ce(c,u);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${l} workDir=${A} agentSessionId=${d.agentSessionId??i??""}: ${D}`),e.sessions.delete(r);try{await C(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:D,runId:"",seq:0}}),!m){const O={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:D};E(r,O),H(e,O,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})}m&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:D}))};if(K){let u;try{const m=p.attachments;u=await J(d.adapter,p.text,n,q,m,s,a),w({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(m){await N(m,!0);return}z(u??void 0),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized||u?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...u?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:u.deliveryMode,queuedSubmissionId:u.queuedSubmissionId}:{}}}:{}}),w({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}z(),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),w({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const G=p.attachments;J(d.adapter,p.text,n,q,G,s,a).then(()=>{w({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(u=>{N(u,!1)})}async function _e(e,t){const{sessionId:a}=t.params,r=e.sessions.get(a);if(r){try{await r.adapter.stop()}catch{}ye(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{_e as handleChatAbort,Me as handleChatSend,ge as sendSessionUpdateEvent};
|
|
1
|
+
import O from"node:os";import{createAgent as X}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Y,buildUserMessagePayload as Z}from"@shennian/wire";import{ManagerModeAdapter as ee}from"../../agents/manager.js";import{reportLog as E}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as te}from"../../native-fusion/parsers.js";import{appendMessage as x,recordSession as F}from"../store.js";import{mergeProjectedSessions as ne}from"../projection.js";import{buildManagedAgentEnv as ae}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as re}from"../remote-attachments.js";import{buildToolArgsSummary as se}from"../tool-summary.js";function oe(e){if(!Array.isArray(e))return;const t=e.map(a=>{if(!a||typeof a!="object")return null;const r=a,o=typeof r.path=="string"?r.path:"",u=typeof r.name=="string"?r.name:"",g=typeof r.mimeType=="string"?r.mimeType:"";if(!o||!u||!g)return null;const f=typeof r.previewData=="string"&&r.previewData.trim()?r.previewData.trim():void 0;return{path:o,name:u,mimeType:g,kind:g.startsWith("image/")?"image":"file",...f?{previewData:f}:{}}}).filter(a=>a!=null);return t.length?t:void 0}function de(e){const t=e.indexOf(`
|
|
2
|
+
`),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ie(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const r={runId:e.runId,sourceSeq:e.seq},o=se(e.args);return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:r,...e.name?{name:e.name}:{},...o!==void 0?{argsSummary:o}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const le=3e4;function ue(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ce(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function L(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function H(e,t,a){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ge(e,t){F({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function U(e){return ae(e)}function j(e,t,a){return{}}function B(e,t,a,r,o,u){e.configure?.({sessionId:t,externalChannel:r??null,env:{...U(a),...j(t,r,o),...u?{SHENNIAN_MANAGED_ROOM_CONTEXT:u}:{}}})}function fe(e,t,a){if(e!=="claude"||!a)return t;const r=t.trim();return!!r&&r.startsWith("-")&&!r.includes("/")?te(a)??t:t}function pe(e,t,a,r){let o=null;function u(n,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ie(n,t,s),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function f(n){if(!n.currentRunId||!n.currentRunPhase)return;const s=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:s,runPhase:n.currentRunPhase},seq:s,id:`agent-heartbeat-${n.currentRunId}-${s}-${Date.now()}`})}function y(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{f(n)},le),n.heartbeatTimer.unref?.())}function i(n){const s=n?.pendingTextEvent;!n||!s||!s.text||(u({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),n.pendingTextEvent=null)}r.on("agentEvent",n=>{const s=e.sessions.get(t),T=ue(n),I=n.state==="final"||n.state==="error"||n.state==="aborted";if(s&&(s.nextEventSeq=n.seq+1,I||(s.currentRunId=n.runId),!I&&T&&(s.currentRunPhase=T,y(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&E({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:Y(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message});const h=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(h,(e.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,L(a,n.source),n.agentSessionId),s&&F({sessionId:t,agentType:a,workDir:s.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const S=n.text??"";if(!S)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==n.runId&&i(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=S,s.pendingTextEvent.seq=n.seq);return}let w={};if(n.state==="final"){i(s);const S=e.runTextAcc.get(h)??"";S&&(w={messageSummary:de(S)}),e.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(i(s),e.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&i(s);I&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),e.chatQueue?.noteTerminal(t)),u(n,w)}),r.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function me(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function C(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function J(e,t,a,r,o,u,g){return g?e.send(t,a,r,o,u,g):u?.trim()?e.send(t,a,r,o,u):o?.length?e.send(t,a,r,o):r?e.send(t,a,r):e.send(t,a)}async function he(e,t,a,r,o,u,g,f){e.evictIdleSessions();const y=X(a);if(!y)throw new Error(`Unsupported agent: ${a}`);const i=r==="manager"&&a!=="manager"?new ee(y,a==="claude"?"claude":"codex"):y;B(i,t,a,g,f),await i.start(t,o,u);const n={adapter:i,workDir:o,agentType:a,sessionMode:r,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:g??null,externalReplyTarget:f??null,externalChannelEnv:{...U(a),...j(t,g,f)}};return e.sessions.set(t,n),pe(e,t,a,i),n}function ye(e,t){const a=e.sessions.get(t),r=a?.currentRunId;if(!a||!r)return;const o=a.nextEventSeq;e.runTextAcc.delete(`${t}:${r}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:r,seq:o},seq:o,id:`agent-evt-${r}-${o}`}),e.chatQueue?.noteTerminal(t)}async function qe(e,t,a){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}me(e,t.id);const{sessionId:r,text:o,agentType:u,sessionMode:g,managerConfig:f,workDir:y,agentSessionId:i,modelId:n,systemPrompt:s,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:I,reasoningEffort:h,clientMessageId:w,sessionListProjection:S,waitForDispatch:Q,responseId:K,managedRoomContextToken:$,suppressExternalOwnerEcho:V}=t.params,k=K||t.id;ne(S);const b=null,P="";if(!r||!o){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:"sessionId and text are required"});return}const c=u,D=c==="manager"||g==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,I??null);const q=(c==="claude"||c==="codex"||c==="workbuddy")&&typeof h=="string"&&h.trim()?h.trim():void 0,A=e.resolvePath(fe(c,y||O.homedir(),i)||O.homedir()),M=A,_=oe(t.params.attachments),p=_?.length?await re({text:o,attachments:_,workDir:A}):{text:o,attachments:_,localized:!1};let d=e.sessions.get(r);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==c||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==A||JSON.stringify(d.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}else if(i&&d.agentSessionId!==i)try{await d.adapter.resume(i),d.agentSessionId=i}catch{e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}}if(!d)try{d=await he(e,r,c,D,A,i,b,P)}catch(l){const m=l instanceof Error&&l.message.startsWith("Unsupported agent:")?l.message:`Failed to start ${u}: ${l instanceof Error?l.message:String(l)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${i??""}: ${m}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:m,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:m});return}$&&/^mrc_[A-Za-z0-9_-]{43}$/.test($)&&B(d.adapter,r,c,b,P,$);const W={id:w??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:Z(p.text,p.attachments)};E({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:c,sessionMode:D,managerConfig:f??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null,reasoningEffort:q}});const z=l=>{ge(e,{sessionId:r,agentType:c,sessionMode:D,managerConfig:f??null,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null}),d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:r,agentType:c,sourceAgentType:L(c,n),canonicalMessageId:w??null,sourceSessionKey:d.agentSessionId??i??null,text:p.text,managedEchoPolicy:l?.deliveryMode==="external_owner_queue"&&!V?"import_following":"suppress_following",externalRunId:l?.deliveryMode==="external_owner_queue"?`codex-external-${l.queuedSubmissionId??w??t.id}`:null}),x(r,W),H(e,W,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})},N=async(l,m)=>{const R=ce(c,l);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${d.agentSessionId??i??""}: ${R}`),e.sessions.delete(r);try{await C(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:R,runId:"",seq:0}}),!m){const v={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:R};x(r,v),H(e,v,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})}m&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:R}))};if(Q){let l;try{const m=p.attachments;l=await J(d.adapter,p.text,n,q,m,s,a),E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(m){await N(m,!0);return}z(l??void 0),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized||l?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...l?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:l.deliveryMode,queuedSubmissionId:l.queuedSubmissionId}:{}}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}z(),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const G=p.attachments;J(d.adapter,p.text,n,q,G,s,a).then(()=>{E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(l=>{N(l,!1)})}async function Me(e,t){const{sessionId:a}=t.params,r=e.sessions.get(a);if(r){try{await r.adapter.stop()}catch{}ye(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{Me as handleChatAbort,qe as handleChatSend,ge as sendSessionUpdateEvent};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType } from './session.ts';
|
|
1
|
+
import type { AgentType, ManagerConfig, SessionMode } from './session.ts';
|
|
2
2
|
import type { RoomActivationMode } from './room.ts';
|
|
3
3
|
export type AgentWorkspaceRoomLink = {
|
|
4
4
|
bindingId: string;
|
|
@@ -37,6 +37,8 @@ export type AgentWorkspace = {
|
|
|
37
37
|
runtimeAdapter: AgentType | null;
|
|
38
38
|
workDir: string;
|
|
39
39
|
modelId: string | null;
|
|
40
|
+
sessionMode: SessionMode;
|
|
41
|
+
managerConfig: ManagerConfig | null;
|
|
40
42
|
createdAt: string;
|
|
41
43
|
updatedAt: string;
|
|
42
44
|
sessions: AgentWorkspaceSession[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AgentType } from './session.ts';
|
|
1
2
|
export type RoomVisibility = 'private' | 'public_read';
|
|
2
3
|
export type RoomJoinPolicy = 'open' | 'approval' | 'closed';
|
|
3
4
|
export type RoomStatus = 'active' | 'archived' | 'deleted';
|
|
@@ -9,11 +10,15 @@ export type RoomActivationMode = 'auto_follow' | 'mention_only' | 'paused';
|
|
|
9
10
|
export type RoomActivationCapability = 'managed' | 'idle_resume' | 'notify_only';
|
|
10
11
|
export type RoomBindingMode = 'managed' | 'hook_native';
|
|
11
12
|
export type RoomBindingStatus = 'pending_approval' | 'active' | 'paused' | 'detached';
|
|
12
|
-
|
|
13
|
+
/** `claude_code` is accepted only as a legacy wire value. New writers use the real AgentType. */
|
|
14
|
+
export type RoomRuntimeAdapter = Exclude<AgentType, 'manager' | 'pi' | 'openclaw'> | 'claude_code';
|
|
13
15
|
export type RoomAttachmentKind = 'image' | 'audio' | 'video' | 'file';
|
|
14
16
|
export type RoomAttachmentStatus = 'pending' | 'uploaded' | 'scanning' | 'ready' | 'rejected' | 'expired' | 'deleted';
|
|
15
17
|
/** JSON-safe representation of the database BIGINT sequence. */
|
|
16
18
|
export type RoomSequence = string;
|
|
19
|
+
export declare function roomRuntimeAgentType(value: string): AgentType | null;
|
|
20
|
+
export declare function isRoomRuntimeAdapter(value: string): value is RoomRuntimeAdapter;
|
|
21
|
+
export declare function roomRuntimeAdapterForAgent(agentType: AgentType): RoomRuntimeAdapter | null;
|
|
17
22
|
export type RoomSummary = {
|
|
18
23
|
id: string;
|
|
19
24
|
name: string;
|
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
// @arch docs/features/network-rooms/implementation-plan.md
|
|
2
2
|
// @test src/__tests__/room.test.ts
|
|
3
|
-
|
|
3
|
+
import { isAvailableAgentType } from "./session.js";
|
|
4
|
+
export function roomRuntimeAgentType(value) {
|
|
5
|
+
if (value === 'claude_code')
|
|
6
|
+
return 'claude';
|
|
7
|
+
if (!isAvailableAgentType(value) ||
|
|
8
|
+
value === 'manager' ||
|
|
9
|
+
value === 'pi' ||
|
|
10
|
+
value === 'openclaw') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function isRoomRuntimeAdapter(value) {
|
|
16
|
+
return roomRuntimeAgentType(value) !== null;
|
|
17
|
+
}
|
|
18
|
+
export function roomRuntimeAdapterForAgent(agentType) {
|
|
19
|
+
return roomRuntimeAgentType(agentType) ? agentType : null;
|
|
20
|
+
}
|
|
@@ -45,6 +45,10 @@ export type AgentRuntimeSummary = {
|
|
|
45
45
|
export type AgentInfo = {
|
|
46
46
|
type: AgentType;
|
|
47
47
|
models: ModelInfo[];
|
|
48
|
+
/** True when this provider can back a stable Shennian Agent and managed Room session. */
|
|
49
|
+
supportsManagedAgent?: boolean;
|
|
50
|
+
/** True when Manager mode can create this provider as a worker. */
|
|
51
|
+
supportsWorker?: boolean;
|
|
48
52
|
/** True only after this real provider passed Manager-mode runtime validation. */
|
|
49
53
|
supportsManagerMode?: boolean;
|
|
50
54
|
/** Per-machine Agent provider config summary (additive, non-secret). */
|
|
@@ -80,6 +84,12 @@ export type SessionActivitySnapshot = {
|
|
|
80
84
|
toolCallCount?: number;
|
|
81
85
|
/** True when the daemon owns a live control channel that can stop the active run. */
|
|
82
86
|
canStop?: boolean;
|
|
87
|
+
/** Optional owner projection for runs controlled by a native desktop host. */
|
|
88
|
+
owner?: 'shennian' | 'external';
|
|
89
|
+
/** Human-readable native owner. Older clients safely ignore this field. */
|
|
90
|
+
ownerLabel?: string;
|
|
91
|
+
/** External-owner lifecycle. `runPhase` remains the backwards-compatible projection. */
|
|
92
|
+
lifecycle?: 'queued' | 'running';
|
|
83
93
|
};
|
|
84
94
|
export type ExternalChannelSessionStatus = {
|
|
85
95
|
configured?: boolean;
|