shennian 0.3.10 → 0.3.11
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 +45 -40
- package/dist/src/commands/daemon-windows.d.ts +7 -0
- package/dist/src/commands/daemon-windows.js +11 -7
- package/dist/src/commands/daemon.d.ts +1 -1
- package/dist/src/commands/daemon.js +7 -7
- package/dist/src/commands/room.d.ts +9 -0
- package/dist/src/commands/room.js +8 -4
- package/dist/src/index.js +2 -2
- package/dist/src/integrations/room-plugin-shared.js +2 -2
- package/dist/src/integrations/workbuddy-plugin-lifecycle.d.ts +10 -0
- package/dist/src/integrations/workbuddy-plugin-lifecycle.js +2 -2
- package/dist/src/room/activation-service.d.ts +3 -0
- package/dist/src/room/activation-service.js +9 -5
- package/dist/src/room/agent-tool.d.ts +1 -0
- package/dist/src/room/agent-tool.js +1 -1
- package/dist/src/room/managed-room-binding.d.ts +23 -1
- package/dist/src/room/managed-room-binding.js +1 -1
- package/dist/src/room/managed-room-session-runtime.d.ts +18 -0
- package/dist/src/room/managed-room-session-runtime.js +1 -0
- package/dist/src/room/mirror-sync-service.d.ts +3 -1
- package/dist/src/room/mirror-sync-service.js +1 -1
- package/dist/src/room/operator-ipc.d.ts +2 -0
- package/dist/src/room/operator-ipc.js +1 -1
- package/dist/src/room/room-action-client.d.ts +2 -0
- package/dist/src/room/room-action-ipc.d.ts +1 -0
- package/dist/src/room/room-action-ipc.js +1 -1
- package/dist/src/room/room-binding-client.d.ts +9 -0
- package/dist/src/room/room-binding-client.js +1 -1
- package/dist/src/room/room-binding-ipc.d.ts +3 -1
- package/dist/src/room/session-registry.d.ts +7 -1
- package/dist/src/room/session-registry.js +2 -2
- package/dist/src/room/tool-schema.d.ts +1 -1
- package/dist/src/room/tool-schema.js +1 -1
- package/dist/src/session/handlers/chat.d.ts +8 -1
- package/dist/src/session/handlers/chat.js +1 -1
- package/dist/src/session/handlers/room-managed.d.ts +1 -0
- package/dist/src/session/handlers/room-managed.js +1 -1
- package/dist/src/session/manager.d.ts +20 -0
- package/dist/src/session/manager.js +1 -1
- package/dist/src/upgrade/engine.d.ts +5 -0
- package/dist/src/upgrade/engine.js +2 -2
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/room.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/room.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform==="win32"&&l.existsSync(this.receiptPath)&&l.unlinkSync(this.receiptPath),l.renameSync(d,this.receiptPath),process.platform!=="win32"&&(l.chmodSync(
|
|
1
|
+
import b from"node:crypto";import l from"node:fs";import D from"node:os";import h from"node:path";import{resolveBuiltinCommand as C,spawnResolvedCommand as B}from"../agents/command-spec.js";import{resolveShennianPath as S}from"../config/index.js";import{ensureWorkBuddyRoomPluginBundle as N,WORKBUDDY_MARKETPLACE_NAME as c,WORKBUDDY_PLUGIN_NAME as w}from"./workbuddy-plugin-bundle.js";import{hashDirectoryContent as v,hashPath as m}from"./room-plugin-lifecycle-common.js";const u=`${w}@${c}`,A=3e4,E=1024*1024;class g extends Error{code;constructor(t,e){super(e),this.code=t,this.name="WorkBuddyPluginLifecycleError"}}class Y{receiptPath;configDir;resolveCommand;ensureBundle;runCommand;constructor(t={}){this.receiptPath=t.receiptPath??S("integrations","workbuddy","installation.json"),this.configDir=t.configDir??O(),this.resolveCommand=t.resolveCommand??(()=>C("workbuddy")),this.ensureBundle=t.ensureBundle??(()=>N()),this.runCommand=t.runCommand??((e,i)=>I(e,i,this.configDir))}async status(t={}){const e=this.resolveCommand();if(!e)return W();const i=t.bundle??this.ensureBundle(),r=this.readReceipt(i),a=(await this.listPlugins(e)).find(d=>d.id===u);return a?M({installPath:a.installPath,sourcePluginRoot:i.pluginRoot,configDir:this.configDir,marketplaceName:c,pluginName:w,installedVersion:a.version,platform:process.platform,allowCacheCopy:r!=null})?{available:!0,state:a.enabled?"enabled":"disabled",installedVersion:a.version,availableVersion:i.pluginVersion,needsUpdate:a.version!==i.pluginVersion||r!=null&&(r.state==="installing"||r.bundleContentHash!==v(i.pluginRoot)),consentRequired:r==null,restartRequired:!!t.restartRequired}:{available:!0,state:"conflict",installedVersion:a.version,availableVersion:i.pluginVersion,needsUpdate:!1,consentRequired:!0,restartRequired:!!t.restartRequired,errorCode:"foreign_plugin_installation"}:{available:!0,state:"not_installed",installedVersion:null,availableVersion:i.pluginVersion,needsUpdate:!1,consentRequired:!0,restartRequired:!!t.restartRequired}}async apply(t){const e=this.resolveCommand();if(!e)throw s("workbuddy_unavailable");const i=this.ensureBundle();if(t==="install")await this.install(e,i);else if(t==="uninstall")await this.uninstall(e,i);else{const r=await this.status({bundle:i});if(r.state==="conflict")throw s("foreign_plugin_installation");if(r.state==="not_installed")throw s("plugin_not_installed");t==="enable"&&await this.run(e,["plugin","enable",u,"--scope","user"]),t==="disable"&&await this.run(e,["plugin","disable",u,"--scope","user"]),t==="update"&&(await this.run(e,["plugin","marketplace","update",c]),await this.run(e,["plugin","update",u,"--scope","user"])),this.writeReceipt(i,"installed")}return this.status({bundle:i,restartRequired:!0})}async install(t,e){const i=this.readReceipt(e),r=await this.status({bundle:e});if(r.state==="conflict")throw s("foreign_plugin_installation");if(r.state==="enabled"||r.state==="disabled"){this.writeReceipt(e,"installing"),r.state==="disabled"&&await this.run(t,["plugin","enable",u,"--scope","user"]),r.needsUpdate&&(await this.run(t,["plugin","marketplace","update",c]),await this.run(t,["plugin","update",u,"--scope","user"])),this.writeReceipt(e,"installed");return}const a=(await this.listMarketplaces(t)).some(f=>f.name===c);if(a&&!i)throw s("foreign_marketplace");if(this.writeReceipt(e,"installing"),a&&await this.run(t,["plugin","marketplace","remove",c]),await this.run(t,["plugin","marketplace","add",e.marketplaceRoot,"--name",c]),await this.run(t,["plugin","install",u,"--scope","user"]),(await this.status({bundle:e})).state!=="enabled")throw s("install_verification_failed");this.writeReceipt(e,"installed")}async uninstall(t,e){const i=this.readReceipt(e),r=await this.status({bundle:e});if(r.state==="conflict")throw s("foreign_plugin_installation");(r.state==="enabled"||r.state==="disabled")&&await this.run(t,["plugin","uninstall",u,"--scope","user","--prune","--yes"]),i&&(await this.listMarketplaces(t)).some(a=>a.name===c)&&await this.run(t,["plugin","marketplace","remove",c]),this.removeReceipt()}async listPlugins(t){const e=await this.run(t,["plugin","list","--json"]);return U(e.stdout)}async listMarketplaces(t){const e=await this.run(t,["plugin","marketplace","list"]);return x(e.stdout)}async run(t,e){const i=await this.runCommand(t,e);if(i.code!==0)throw s("workbuddy_command_failed");return i}readReceipt(t){try{const e=l.lstatSync(this.receiptPath);if(!e.isFile()||e.isSymbolicLink())throw s("unsafe_installation_receipt");const i=JSON.parse(l.readFileSync(this.receiptPath,"utf8"));return i.schemaVersion!==1||i.pluginId!==u||i.marketplaceName!==c||i.consentVersion!==1||i.pluginRootHash!==m(t.pluginRoot)||i.configDirHash!==m(this.configDir)||i.state!=="installing"&&i.state!=="installed"?null:i}catch(e){if(e.code==="ENOENT")return null;if(e instanceof g)throw e;return null}}writeReceipt(t,e){const i=this.readReceipt(t),r=new Date().toISOString(),o={schemaVersion:1,pluginId:u,marketplaceName:c,pluginRootHash:m(t.pluginRoot),configDirHash:m(this.configDir),bundleContentHash:e==="installing"?i?.bundleContentHash:v(t.pluginRoot),consentVersion:1,state:e,consentedAt:i?.consentedAt??r,lastActionAt:r},a=h.dirname(this.receiptPath);l.mkdirSync(a,{recursive:!0,mode:448});const d=`${this.receiptPath}.tmp-${process.pid}-${b.randomBytes(6).toString("hex")}`;try{l.writeFileSync(d,`${JSON.stringify(o,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform==="win32"&&l.existsSync(this.receiptPath)&&l.unlinkSync(this.receiptPath),l.renameSync(d,this.receiptPath),process.platform!=="win32"&&(l.chmodSync(a,448),l.chmodSync(this.receiptPath,384))}finally{try{l.unlinkSync(d)}catch{}}}removeReceipt(){try{const t=l.lstatSync(this.receiptPath);if(!t.isFile()||t.isSymbolicLink())throw s("unsafe_installation_receipt");l.unlinkSync(this.receiptPath)}catch(t){if(t.code!=="ENOENT")throw t}}}function O(n=process.env){const t=n.WORKBUDDY_CONFIG_DIR?.trim();return h.resolve(t||h.join(D.homedir(),".workbuddy"))}function V(n,t=process.env){return{...t,WORKBUDDY_CONFIG_DIR:n,CODEBUDDY_CONFIG_DIR:n}}async function I(n,t,e){return await new Promise(i=>{const r=B(n,t,{env:V(e),cwd:process.cwd(),stdio:["ignore","pipe","pipe"]});let o="",a="",d=!1;const f=p=>{d||(d=!0,clearTimeout(R),i(p))},R=setTimeout(()=>{r.kill("SIGTERM"),f({code:null,stdout:o,stderr:""})},A),y=(p,P)=>`${p}${P.toString("utf8")}`.slice(-E);r.stdout?.on("data",p=>{o=y(o,p)}),r.stderr?.on("data",p=>{a=y(a,p)}),r.once("error",()=>f({code:null,stdout:o,stderr:""})),r.once("close",p=>f({code:p,stdout:o,stderr:a}))})}function U(n){return _(n,"invalid_plugin_list").map(e=>{if(!e||typeof e!="object"||Array.isArray(e))throw s("invalid_plugin_list");const i=e;if(typeof i.id!="string"||typeof i.version!="string"||typeof i.scope!="string"||typeof i.enabled!="boolean"||typeof i.installPath!="string")throw s("invalid_plugin_list");return{id:i.id,version:i.version,scope:i.scope,enabled:i.enabled,installPath:i.installPath}})}function x(n){return _(n,"invalid_marketplace_list").map(e=>{if(!e||typeof e!="object"||Array.isArray(e))throw s("invalid_marketplace_list");const i=e;if(typeof i.name!="string")throw s("invalid_marketplace_list");return{name:i.name,type:typeof i.type=="string"?i.type:void 0}})}function _(n,t){try{const e=JSON.parse(n);if(!Array.isArray(e))throw s(t);return e}catch(e){throw e instanceof g?e:s(t)}}function M(n){if(k(n.installPath,n.sourcePluginRoot,n.platform))return!0;if(!n.allowCacheCopy)return!1;const e=(n.platform==="win32"?h.win32:h.posix).join(n.configDir,"plugins","cache",n.marketplaceName,n.pluginName,n.installedVersion);return k(n.installPath,e,n.platform)}function k(n,t,e){const i=e==="win32"?h.win32:h.posix,r=o=>{let a=i.resolve(o);if(e===process.platform)try{a=l.realpathSync(o)}catch{}return e==="win32"?a.toLowerCase():a};return r(n)===r(t)}function W(){return{available:!1,state:"unavailable",installedVersion:null,availableVersion:null,needsUpdate:!1,consentRequired:!0,restartRequired:!1}}function s(n){return new g(n,`WorkBuddy integration failed (${n}).`)}export{Y as WorkBuddyPluginLifecycle,g as WorkBuddyPluginLifecycleError,V as buildWorkBuddyDesktopCommandEnv,M as isOwnedWorkBuddyPluginInstallation,O as resolveWorkBuddyDesktopConfigDir};
|
|
@@ -18,11 +18,14 @@ export type RoomActivationContext = {
|
|
|
18
18
|
members?: Array<{
|
|
19
19
|
displayName: string;
|
|
20
20
|
principalType: 'human' | 'agent';
|
|
21
|
+
description?: string;
|
|
22
|
+
ownerDisplayName?: string | null;
|
|
21
23
|
}>;
|
|
22
24
|
agentsByIdentityId?: Record<string, {
|
|
23
25
|
displayName: string;
|
|
24
26
|
description: string;
|
|
25
27
|
}>;
|
|
28
|
+
localHistoryPath?: string;
|
|
26
29
|
};
|
|
27
30
|
export declare class RoomActivationService {
|
|
28
31
|
private readonly deps;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import{selectRoomActivationTargets as
|
|
2
|
-
\u9644\u4EF6\uFF1A${
|
|
3
|
-
${
|
|
1
|
+
import{selectRoomActivationTargets as b}from"./activation-policy.js";const v=4,_=6e4,$=6,y=5*6e4;class R{deps;constructor(t){this.deps=t}async processMessages(t,e,i,s={roomName:"\u5F53\u524D\u7FA4\u804A"}){const a=[...e].sort((o,c)=>D(o.sequence,c.sequence));for(const o of a){const c=new Set(b(o,i).map(d=>d.registration.registryKey));for(const d of i){const r=this.deps.registry.getByRegistryKey(d.registryKey);if(!(!r?.binding||r.binding.roomId!==t)&&!(BigInt(o.sequence)<=BigInt(r.binding.lastActivatedSequence))){if(c.has(r.registryKey)){const u=(o.activationCause?.hopCount??0)+(o.author.principalType==="agent"?1:0),m=o.activationCause?.rootCauseMessageId??o.id;if(u>v)this.audit(r,o.id,m,u,"suppressed","max_hops");else{const p=g([o]),I=r.binding.activationMode==="mention_only"?M([...(r.binding.mentionContext??[]).map(f=>f.deliveryText),p]):p;this.deps.registry.enqueueActivation(r.registryKey,{causeMessageId:o.id,rootCauseMessageId:m,sequence:o.sequence,hopCount:u,enqueuedAt:(this.deps.now?.()??new Date).toISOString(),attempts:0,deliveryText:I,systemPrompt:h(s,r)},this.deps.now?.()??new Date,r.binding.activationMode==="mention_only"),this.audit(r,o.id,m,u,"queued","policy_match")}}else r.binding.activationMode==="mention_only"&&o.kind!=="system"&&o.author.membershipId!==r.binding.agentMembershipId&&this.deps.registry.appendMentionContext(r.registryKey,{sequence:o.sequence,deliveryText:g([o])},this.deps.now?.()??new Date);this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"activated",sequence:o.sequence})}}}for(const o of i)await this.drain(o.registryKey)}async drain(t){await this.deps.registry.runExclusive(t,async()=>{const e=this.deps.registry.getByRegistryKey(t),i=e?.binding,s=i?.activationQueue[0];if(!e||!i||!s||i.status!=="active"||i.activationMode==="paused"||!e.capabilities.canResumeIdleSession||e.lifecycleState!=="inactive"&&!e.capabilities.canActivateWhileHostOpen)return;const a=this.deps.observations.getBySource(e);if(!a?.workspacePath||a.lifecycleState==="closed")return;const o=this.deps.registry.claimActivation({registryKey:t,windowMs:_,maxActivations:$,pauseMs:y,now:this.deps.now?.()});if(!o.allowed){o.newlyPaused&&this.audit(e,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"suppressed","rate_limited");return}this.deps.registry.markActivationDispatching(t,s.causeMessageId);try{const c=await this.deps.activate({sessionId:e.nianSessionId,agentType:N(e.runtimeAdapter),agentSessionId:e.sourceSessionKey,workDir:a.workspacePath,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u65B0\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",systemPrompt:s.systemPrompt??h({roomName:"\u5F53\u524D\u7FA4\u804A"},e),roomContextRegistryKey:e.registryKey,clientMessageId:`room-activation-${i.bindingId}-${s.causeMessageId}`});this.deps.registry.updateLifecycle(t,c?.externalOwnerQueued?"inactive":"active"),this.audit(e,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"activated","idle_resume")}catch(c){throw this.deps.registry.retryActiveActivation({registryKey:t,maxAttempts:3,pauseMs:y,now:this.deps.now?.()}),this.audit(e,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"failed",c instanceof Error?c.name:"activation_error"),c}})}audit(t,e,i,s,a,o){t.binding&&this.deps.audit?.record({roomId:t.binding.roomId,bindingId:t.binding.bindingId,causeMessageId:e,rootCauseMessageId:i,hopCount:s,outcome:a,reason:o,at:this.deps.now?.()})}}function g(n){const t=n.map(e=>{const i=e.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",s=e.attachments.length>0?`
|
|
2
|
+
\u9644\u4EF6\uFF1A${e.attachments.map(a=>a.name).join("\u3001")}`:"";return{speaker:e.author.displayName,body:`${i}${s}`}});return l(t)}function M(n){return l(n.map(w))}function w(n){const t=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const e=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const s=n.match(/^([^\n]+)\n([\s\S]*)$/);return s?{speaker:s[1],body:s[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function l(n){if(n.length===1){const[t]=n;return`[\u7FA4\u6D88\u606F] <${t.speaker}>
|
|
3
|
+
${t.body}`}return`[\u7FA4\u6D88\u606F]
|
|
4
|
+
${n.map(t=>`<${t.speaker}>
|
|
5
|
+
${t.body}`).join(`
|
|
4
6
|
|
|
5
|
-
`)}function
|
|
6
|
-
`)}function
|
|
7
|
+
`)}`}function h(n,t){const e=t.binding?n.agentsByIdentityId?.[t.binding.agentIdentityId]:void 0,i=e?`\u4F60\u662F\u667A\u80FD\u4F53\u201C${e.displayName}\u201D${e.description.trim()?`\uFF0C\u4F60\u7684\u804C\u8D23\u662F\uFF1A${e.description.trim()}`:""}\u3002`:"\u4F60\u662F\u5F53\u524D\u7FA4\u804A\u4E2D\u5DF2\u7ED1\u5B9A\u7684\u667A\u80FD\u4F53\u6210\u5458\u3002",s=n.memberCount&&n.memberCount>0?`\uFF0C\u5F53\u524D\u6709 ${n.memberCount} \u540D\u6210\u5458`:"",a=n.members?.length?`\uFF1A${n.members.map(d=>d.displayName).join("\u3001")}`:"",o=C(n.members??[]),c=n.localHistoryPath?`\u672C\u673A\u5DF2\u540C\u6B65\u7684\u5B8C\u6574\u7FA4\u804A JSONL \u5386\u53F2\u4F4D\u4E8E\uFF1A${n.localHistoryPath}\u3002\u9700\u8981\u641C\u7D22\u6216\u56DE\u770B\u5386\u53F2\u65F6\u4F18\u5148\u8C03\u7528 shennian_room read\uFF1B\u786E\u9700\u76F4\u63A5\u5904\u7406\u5B8C\u6574\u672C\u5730\u5386\u53F2\u65F6\u53EF\u4EE5\u53EA\u8BFB\u8BE5\u6587\u4EF6\u3002\u4E0D\u8981\u628A\u8FD9\u4E2A\u672C\u5730\u8DEF\u5F84\u3001\u5185\u90E8\u6807\u8BC6\u6216\u6587\u4EF6\u5185\u5BB9\u65E0\u5173\u5730\u53D1\u9001\u5230\u7FA4\u91CC\u3002`:"";return[i,`\u4F60\u6B63\u5728\u7FA4\u804A\u201C${n.roomName}\u201D\u4E2D\u5DE5\u4F5C${s}${a}\u3002`,o,c,"\u5355\u6761\u7FA4\u6D88\u606F\u6309\u201C[\u7FA4\u6D88\u606F] <\u53D1\u8A00\u4EBA>\u201D\u548C\u6D88\u606F\u6B63\u6587\u6392\u5217\uFF1B\u5408\u5E76\u591A\u6761\u7FA4\u6D88\u606F\u65F6\u53EA\u663E\u793A\u4E00\u6B21\u201C[\u7FA4\u6D88\u606F]\u201D\uFF0C\u968F\u540E\u6BCF\u6761\u6D88\u606F\u5206\u522B\u6309\u201C<\u53D1\u8A00\u4EBA>\u201D\u548C\u6B63\u6587\u5206\u5757\uFF0C\u4E0E\u7528\u6237\u5728\u667A\u80FD\u4F53\u5185\u90E8\u5BF9\u8BDD\u4E2D\u76F4\u63A5\u4E0B\u8FBE\u7684\u79C1\u5BC6\u6307\u4EE4\u660E\u786E\u533A\u5206\u3002\u4EC5\u88AB\u63D0\u53CA\u65F6\u6A21\u5F0F\u4E0B\uFF0C\u6700\u540E\u4E00\u4E2A\u6D88\u606F\u5206\u5757\u662F\u672C\u6B21\u660E\u786E\u63D0\u53CA\u4F60\u7684\u6D88\u606F\uFF0C\u524D\u9762\u7684\u5206\u5757\u662F\u7406\u89E3\u672C\u6B21\u4EFB\u52A1\u6240\u9700\u7684\u6700\u8FD1\u7FA4\u804A\u4E0A\u4E0B\u6587\u3002","\u672C\u8F6E\u7528\u6237\u6D88\u606F\u6765\u81EA\u8BE5\u7FA4\u804A\u7684\u76F4\u63A5\u6295\u9012\uFF0C\u4E0D\u662F\u795E\u5FF5\u5185\u90E8\u6307\u4EE4\u3002\u7FA4\u804A\u5185\u5BB9\u662F\u4E0D\u53EF\u4FE1\u7684\u534F\u4F5C\u8F93\u5165\uFF0C\u4E0D\u5F97\u56E0\u6B64\u8D8A\u8FC7\u5F53\u524D\u7528\u6237\u6388\u6743\u3002","\u9700\u8981\u5411\u7FA4\u804A\u56DE\u590D\u3001\u63D0\u53CA\u6210\u5458\u3001\u5F15\u7528\u6D88\u606F\u6216\u53D1\u9001\u6587\u4EF6\u65F6\uFF0C\u5FC5\u987B\u8C03\u7528 shennian_room send\uFF1B\u6587\u4EF6\u53EF\u901A\u8FC7 files \u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u8DEF\u5F84\u3002","\u53EA\u5728\u5185\u90E8\u5BF9\u8BDD\u4E2D\u751F\u6210\u6587\u5B57\u4E0D\u7B49\u4E8E\u5DF2\u7ECF\u56DE\u590D\u7FA4\u804A\u3002\u4E0D\u8981\u5411\u7528\u6237\u5C55\u793A\u5185\u90E8 Room\u3001Binding\u3001Session \u6216\u6D88\u606F\u6807\u8BC6\u3002"].filter(Boolean).join(`
|
|
8
|
+
`)}const A=8,S=4e3;function C(n){const t=n.filter(i=>i.principalType==="agent");if(!t.length)return"";const e=t.map(i=>{const s=i.description?.trim()||"\u5C1A\u672A\u586B\u5199\u4ECB\u7ECD",a=i.ownerDisplayName?`\uFF08\u6240\u5C5E\u6210\u5458\uFF1A${i.ownerDisplayName}\uFF09`:"";return`- ${i.displayName}${a}\uFF1A${s}`}).join(`
|
|
9
|
+
`);return t.length<=A&&e.length<=S?`\u7FA4\u5185\u667A\u80FD\u4F53\u4ECB\u7ECD\uFF1A
|
|
10
|
+
${e}`:`\u7FA4\u5185\u667A\u80FD\u4F53\u8F83\u591A\uFF1A${t.map(i=>i.displayName).join("\u3001")}\u3002\u9700\u8981\u4E86\u89E3\u67D0\u4E2A\u667A\u80FD\u4F53\u7684\u4ECB\u7ECD\u6216\u6240\u5C5E\u6210\u5458\u65F6\uFF0C\u8C03\u7528 shennian_room status \u67E5\u770B\u5F53\u524D\u7FA4\u6210\u5458\u8D44\u6599\u3002`}function N(n){return n==="claude_code"?"claude":n}function D(n,t){const e=BigInt(n),i=BigInt(t);return e<i?-1:e>i?1:0}export{R as RoomActivationService};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAvailableAgentType as g}from"@shennian/wire";import{loadConfig as h}from"../config/index.js";const l="shennian_agent",A={name:l,title:"Shennian Agent",description:"List, inspect, create, or update the current user's private stable Shennian Agents.",inputSchema:{type:"object",additionalProperties:!1,required:["action"],properties:{action:{type:"string",enum:["list","get","create","update"]},agentId:{type:"string",minLength:1,maxLength:191},displayName:{type:"string",minLength:1,maxLength:191},description:{type:"string",maxLength:1e3},systemPrompt:{type:"string",maxLength:2e4},runtimeAdapter:{type:"string",minLength:1,maxLength:64},workDir:{type:"string",minLength:1,maxLength:1024},modelId:{type:["string","null"],maxLength:191}}}};function u(r){(!r||typeof r!="object"||Array.isArray(r))&&s();const e=r,t=e.action;t!=="list"&&t!=="get"&&t!=="create"&&t!=="update"&&s();const i=new Set(["action","agentId","displayName","description","systemPrompt","runtimeAdapter","workDir","modelId"]);Object.keys(e).some(o=>!i.has(o))&&s();const a="agentId"in e?m(e.agentId,191):"";if((t==="get"||t==="update")&&!a&&s(),t==="list"||t==="get")return{action:t,...a?{agentId:a}:{}};const n={};for(const[o,c]of[["displayName",191],["description",1e3],["systemPrompt",2e4],["runtimeAdapter",64],["workDir",1024]])o in e&&(n[o]=m(e[o],c,o==="displayName"||o==="runtimeAdapter"||o==="workDir"));return"modelId"in e&&(n.modelId=e.modelId===null?null:m(e.modelId,191)),typeof n.runtimeAdapter=="string"&&(!g(n.runtimeAdapter)||n.runtimeAdapter==="pi")&&s(),t==="create"&&(!n.displayName||!n.runtimeAdapter||!n.workDir)&&s(),{action:t,...a?{agentId:a}:{},body:n}}async function I(r,e=fetch){const t=u(r),i=h(),a=i.machineToken??i.accessToken;if(!a)throw new d("not_paired","Pair this machine with Shennian first.");const n={...t.body??{}};if(t.action==="create"&&!n.machineId){if(!i.machineId)throw new d("not_paired","Pair this machine with Shennian first.");n.machineId=i.machineId}const o=t.agentId?`/${encodeURIComponent(t.agentId)}`:"",c=await e(`${(i.serverUrl??"https://shennian.net").replace(/\/+$/,"")}/api/agents${o}`,{method:t.action==="create"?"POST":t.action==="update"?"PATCH":"GET",headers:{Authorization:`Bearer ${a}`,...t.action==="create"||t.action==="update"?{"Content-Type":"application/json"}:{}},...t.action==="create"||t.action==="update"?{body:JSON.stringify(n)}:{}}),p=await c.json().catch(()=>null);if(!c.ok)throw new d(typeof p?.code=="string"?p.code:`http_${c.status}`,typeof p?.error=="string"?p.error:"Shennian Agent request failed.");return p}class d extends Error{code;constructor(e,t){super(t),this.code=e,this.name="AgentToolError"}}function m(r,e,t=!1){if(typeof r!="string")return s();const i=r.trim();return t&&!i||i.length>e?s():i}function s(){throw new d("invalid_tool_input","Invalid Shennian Agent tool input.")}export{d as AgentToolError,A as SHENNIAN_AGENT_TOOL,l as SHENNIAN_AGENT_TOOL_NAME,I as executeAgentTool,u as normalizeAgentToolInput};
|
|
1
|
+
import{isAvailableAgentType as g}from"@shennian/wire";import{loadConfig as h}from"../config/index.js";const l="shennian_agent",A={name:l,title:"Shennian Agent",description:"List, inspect, create, or update the current user's private stable Shennian Agents.",inputSchema:{type:"object",additionalProperties:!1,required:["action"],properties:{action:{type:"string",enum:["list","get","create","update"]},agentId:{type:"string",minLength:1,maxLength:191},displayName:{type:"string",minLength:1,maxLength:191},description:{type:"string",minLength:1,maxLength:1e3},systemPrompt:{type:"string",maxLength:2e4},runtimeAdapter:{type:"string",minLength:1,maxLength:64},workDir:{type:"string",minLength:1,maxLength:1024},modelId:{type:["string","null"],maxLength:191}}}};function u(r){(!r||typeof r!="object"||Array.isArray(r))&&s();const e=r,t=e.action;t!=="list"&&t!=="get"&&t!=="create"&&t!=="update"&&s();const i=new Set(["action","agentId","displayName","description","systemPrompt","runtimeAdapter","workDir","modelId"]);Object.keys(e).some(o=>!i.has(o))&&s();const a="agentId"in e?m(e.agentId,191):"";if((t==="get"||t==="update")&&!a&&s(),t==="list"||t==="get")return{action:t,...a?{agentId:a}:{}};const n={};for(const[o,c]of[["displayName",191],["description",1e3],["systemPrompt",2e4],["runtimeAdapter",64],["workDir",1024]])o in e&&(n[o]=m(e[o],c,o==="displayName"||o==="runtimeAdapter"||o==="workDir"));return"modelId"in e&&(n.modelId=e.modelId===null?null:m(e.modelId,191)),typeof n.runtimeAdapter=="string"&&(!g(n.runtimeAdapter)||n.runtimeAdapter==="pi")&&s(),t==="create"&&(!n.displayName||!n.description||!n.runtimeAdapter||!n.workDir)&&s(),{action:t,...a?{agentId:a}:{},body:n}}async function I(r,e=fetch){const t=u(r),i=h(),a=i.machineToken??i.accessToken;if(!a)throw new d("not_paired","Pair this machine with Shennian first.");const n={...t.body??{}};if(t.action==="create"&&!n.machineId){if(!i.machineId)throw new d("not_paired","Pair this machine with Shennian first.");n.machineId=i.machineId}const o=t.agentId?`/${encodeURIComponent(t.agentId)}`:"",c=await e(`${(i.serverUrl??"https://shennian.net").replace(/\/+$/,"")}/api/agents${o}`,{method:t.action==="create"?"POST":t.action==="update"?"PATCH":"GET",headers:{Authorization:`Bearer ${a}`,...t.action==="create"||t.action==="update"?{"Content-Type":"application/json"}:{}},...t.action==="create"||t.action==="update"?{body:JSON.stringify(n)}:{}}),p=await c.json().catch(()=>null);if(!c.ok)throw new d(typeof p?.code=="string"?p.code:`http_${c.status}`,typeof p?.error=="string"?p.error:"Shennian Agent request failed.");return p}class d extends Error{code;constructor(e,t){super(t),this.code=e,this.name="AgentToolError"}}function m(r,e,t=!1){if(typeof r!="string")return s();const i=r.trim();return t&&!i||i.length>e?s():i}function s(){throw new d("invalid_tool_input","Invalid Shennian Agent tool input.")}export{d as AgentToolError,A as SHENNIAN_AGENT_TOOL,l as SHENNIAN_AGENT_TOOL_NAME,I as executeAgentTool,u as normalizeAgentToolInput};
|
|
@@ -11,6 +11,10 @@ export type ManagedRoomBindInput = {
|
|
|
11
11
|
workDir: string;
|
|
12
12
|
agentType: AgentType;
|
|
13
13
|
};
|
|
14
|
+
export type ManagedRoomRestartInput = ManagedRoomBindInput & {
|
|
15
|
+
bindingId: string;
|
|
16
|
+
previousNianSessionId: string;
|
|
17
|
+
};
|
|
14
18
|
export type ManagedRoomBinder = {
|
|
15
19
|
bind(input: ManagedRoomBindInput): Promise<{
|
|
16
20
|
binding: {
|
|
@@ -24,13 +28,26 @@ export type ManagedRoomBinder = {
|
|
|
24
28
|
};
|
|
25
29
|
idempotent: boolean;
|
|
26
30
|
}>;
|
|
31
|
+
restart(input: ManagedRoomRestartInput): Promise<{
|
|
32
|
+
binding: {
|
|
33
|
+
id: string;
|
|
34
|
+
roomId: string;
|
|
35
|
+
agentIdentityId: string;
|
|
36
|
+
agentMembershipId: string;
|
|
37
|
+
nianSessionId: string;
|
|
38
|
+
status: 'pending_approval' | 'active' | 'paused' | 'detached';
|
|
39
|
+
activationMode: 'auto_follow' | 'mention_only' | 'paused';
|
|
40
|
+
};
|
|
41
|
+
resumeSequence: string;
|
|
42
|
+
idempotent: boolean;
|
|
43
|
+
}>;
|
|
27
44
|
};
|
|
28
45
|
export declare class TrustedManagedRoomBinder implements ManagedRoomBinder {
|
|
29
46
|
private readonly deps;
|
|
30
47
|
constructor(deps: {
|
|
31
48
|
registry: RoomSessionRegistry;
|
|
32
49
|
observations: RoomHostSessionObservationStore;
|
|
33
|
-
bindingClient: Pick<HttpRoomBindingClient, 'create'>;
|
|
50
|
+
bindingClient: Pick<HttpRoomBindingClient, 'create' | 'restart'>;
|
|
34
51
|
getMachineCredentials: () => {
|
|
35
52
|
machineId?: string;
|
|
36
53
|
machineToken?: string;
|
|
@@ -41,4 +58,9 @@ export declare class TrustedManagedRoomBinder implements ManagedRoomBinder {
|
|
|
41
58
|
binding: import("./room-binding-client.js").RemoteRoomBinding;
|
|
42
59
|
idempotent: boolean;
|
|
43
60
|
}>;
|
|
61
|
+
restart(input: ManagedRoomRestartInput): Promise<{
|
|
62
|
+
binding: import("./room-binding-client.js").RemoteRoomBinding;
|
|
63
|
+
resumeSequence: string;
|
|
64
|
+
idempotent: boolean;
|
|
65
|
+
}>;
|
|
44
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import y from"node:crypto";class h{deps;constructor(e){this.deps=e}async bind(e){const{machineId:t,machineToken:d}=this.deps.getMachineCredentials();if(!t||!d)throw new Error("Machine is not paired with Shennian.");const r=g(e.agentType),o=this.deps.hostProcessIdentity(),n=this.deps.registry.register({machineId:t,runtimeAdapter:r,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});this.deps.observations.observe({machineId:t,runtimeAdapter:r,sourceSessionKey:e.agentSessionId,hostContextHash:c(o),workspaceHash:c(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedBind"});const i=await this.deps.bindingClient.create({machineToken:d,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:r,sessionRegistryKey:n.registryKey,activationCapability:"managed",activationMode:"auto_follow"});return this.deps.registry.setBinding({registryKey:n.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:t,machineToken:d}=this.deps.getMachineCredentials();if(!t||!d)throw new Error("Machine is not paired with Shennian.");const r=g(e.agentType),o=this.deps.hostProcessIdentity(),n=this.deps.registry.register({machineId:t,runtimeAdapter:r,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});this.deps.observations.observe({machineId:t,runtimeAdapter:r,sourceSessionKey:e.agentSessionId,hostContextHash:c(o),workspaceHash:c(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedRestart"});const i=await this.deps.bindingClient.restart({machineToken:d,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:r,sessionRegistryKey:n.registryKey});this.deps.registry.setBinding({registryKey:n.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:n.registryKey,cursor:"delivered",sequence:i.resumeSequence}),this.deps.registry.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:i.resumeSequence});for(const a of this.deps.registry.list())a.registryKey!==n.registryKey&&a.nianSessionId===e.previousNianSessionId&&a.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(a.registryKey);return i}}function c(s){return y.createHash("sha256").update(s,"utf8").digest("hex")}function g(s){if(s==="codex")return"codex";if(s==="claude")return"claude_code";if(s==="workbuddy"||s==="custom:workbuddy")return"workbuddy";throw new Error(`Agent ${s} does not support managed Room binding.`)}export{h as TrustedManagedRoomBinder};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SessionManager } from '../session/manager.js';
|
|
2
|
+
import type { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
3
|
+
import { ManagedRoomSessionOrchestrator } from './managed-room-session.js';
|
|
4
|
+
import type { HttpRoomBindingClient } from './room-binding-client.js';
|
|
5
|
+
import type { RoomSessionRegistry } from './session-registry.js';
|
|
6
|
+
export declare function createManagedRoomSessionRuntime(input: {
|
|
7
|
+
sessionManager: Pick<SessionManager, 'createManagedRoomSession' | 'waitForManagedNativeSession' | 'publishManagedRoomSession'>;
|
|
8
|
+
registry: RoomSessionRegistry;
|
|
9
|
+
observations: RoomHostSessionObservationStore;
|
|
10
|
+
bindingClient: Pick<HttpRoomBindingClient, 'joinRoom'>;
|
|
11
|
+
serverUrl: string;
|
|
12
|
+
getMachineCredentials: () => {
|
|
13
|
+
machineId?: string;
|
|
14
|
+
machineToken?: string;
|
|
15
|
+
};
|
|
16
|
+
hostProcessIdentity: () => string;
|
|
17
|
+
operationsDirectory?: string;
|
|
18
|
+
}): ManagedRoomSessionOrchestrator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import h from"node:crypto";import{resolveShennianPath as y}from"../config/index.js";import{FileManagedRoomSessionOperationStore as l,ManagedRoomSessionOrchestrator as u}from"./managed-room-session.js";function w(n){return new u({operations:new l(n.operationsDirectory??y("runtime","managed-room-sessions")),createManagedSession:({creationKey:e,agent:o,workDir:t,modelId:a,initialMessage:i})=>n.sessionManager.createManagedRoomSession({creationKey:e,agentType:o,workDir:t,modelId:a,initialMessage:i}),waitForNativeSession:async({nianSessionId:e,runtimeAdapter:o,timeoutMs:t})=>({sourceSessionKey:(await n.sessionManager.waitForManagedNativeSession({nianSessionId:e,agentType:c(o),timeoutMs:t})).sourceSessionKey,hostProcessIdentity:n.hostProcessIdentity()}),registerManagedSession:async e=>{const o=n.getMachineCredentials();if(!o.machineId?.trim()||!o.machineToken?.trim())throw s("cli_not_signed_in","Sign in to the Shennian CLI first.");const t=n.registry.register({machineId:o.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,capabilities:e.capabilities});return n.observations.observe({machineId:o.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:m(e.hostProcessIdentity),workspaceHash:m(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedCreate"}),{registryKey:t.registryKey}},joinRegisteredSession:async e=>{const o=n.getMachineCredentials();if(!o.machineToken?.trim())throw s("cli_not_signed_in","Sign in to the Shennian CLI first.");const t=n.registry.getByRegistryKey(e.registryKey);if(!t)throw s("room_session_unavailable","Managed Agent Session is unavailable.");const a={machineToken:o.machineToken,bindingIntentId:e.bindingIntentId,nianSessionId:t.nianSessionId,runtimeAdapter:t.runtimeAdapter,sessionRegistryKey:t.registryKey,inviteToken:I(e.inviteUrl),agentAlias:e.agentAlias,activationCapability:"managed",activationMode:e.activationMode};let i=null;for(let r=0;r<20;r+=1)try{n.sessionManager.publishManagedRoomSession({nianSessionId:t.nianSessionId,agentType:c(t.runtimeAdapter)}),i=await n.bindingClient.joinRoom(a);break}catch(d){if(!S(d,"nian_session_mismatch")||r===19)throw d;await new Promise(g=>setTimeout(g,100))}if(!i)throw s("managed_room_join_failed","Managed Room join failed.");return n.registry.setBinding({registryKey:t.registryKey,bindingId:i.binding.id,roomId:i.room.id,agentIdentityId:i.binding.agentIdentityId,agentMembershipId:i.binding.agentMembershipId,status:i.binding.status,activationMode:i.binding.activationMode}),{outcome:i.outcome,room:i.room,binding:i.binding,idempotent:i.idempotent}},allowedInviteOrigins:[new URL(n.serverUrl).origin]})}function c(n){return n==="claude_code"?"claude":n}function I(n){const e=new URL(n).pathname.split("/").filter(Boolean).at(-1);if(!e||!/^[A-Za-z0-9_-]{43}$/.test(e))throw s("invalid_managed_room_invite_url","Room invite URL is invalid.");return e}function m(n){return h.createHash("sha256").update(n,"utf8").digest("hex")}function s(n,e){return Object.assign(new Error(e),{code:n})}function S(n,e){return!!(n&&typeof n=="object"&&"code"in n&&n.code===e)}export{w as createManagedRoomSessionRuntime};
|
|
@@ -4,7 +4,9 @@ import { RoomSessionRegistry, type RoomSessionRegistration } from './session-reg
|
|
|
4
4
|
import type { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
5
5
|
import type { RoomMessageView } from '@shennian/wire';
|
|
6
6
|
import type { RoomActivationContext } from './activation-service.js';
|
|
7
|
-
type Mirror = Pick<RoomJsonlMirror, 'append' | 'markAccessRevoked' | 'readState' | 'recordSyncError'
|
|
7
|
+
type Mirror = Pick<RoomJsonlMirror, 'append' | 'markAccessRevoked' | 'readState' | 'recordSyncError'> & {
|
|
8
|
+
messagesPath?: string;
|
|
9
|
+
};
|
|
8
10
|
type Timer = ReturnType<typeof setTimeout>;
|
|
9
11
|
export declare class RoomMirrorSyncService {
|
|
10
12
|
private readonly deps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RoomActionHttpError as h}from"./room-action-client.js";import{RoomJsonlMirror as p}from"./jsonl-mirror.js";import y from"node:crypto";class
|
|
1
|
+
import{RoomActionHttpError as h}from"./room-action-client.js";import{RoomJsonlMirror as p}from"./jsonl-mirror.js";import y from"node:crypto";class P{deps;timer=null;running=!1;started=!1;constructor(s){this.deps=s}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const s=this.deps.getMachineCredentials().machineToken?.trim();if(s){this.running=!0;try{const n=f(this.deps.registry.list());for(const[a,r]of n)await this.syncRoom(a,r,s)}finally{this.running=!1}}}async syncRoom(s,n,a){const r=(this.deps.mirrorFactory??(e=>new p(e)))(s),m=n.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(m.length===0){n.some(e=>e.binding?.status==="detached")&&!n.some(e=>e.binding?.status==="pending_approval")&&r.markAccessRevoked();return}let u=0,o=null,c=null;for(const e of m){const d=e.binding,g={machineToken:a,roomId:s,bindingId:d.bindingId};try{const t=await this.deps.actionClient.status(g);if(t.activationCapability==="managed"&&t.managedWorkspacePath&&!this.deps.observations?.getBySource(e)&&this.deps.observations?.observe({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:l(e.hostProcessIdentity),workspaceHash:l(t.managedWorkspacePath),workspacePath:t.managedWorkspacePath,lifecycleState:"active",eventName:"ManagedSync"}),this.deps.registry.setBinding({registryKey:e.registryKey,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode}),t.status==="detached"){u+=1;continue}if(t.status==="pending_approval")continue;o??=g,c??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},...r.messagesPath?{localHistoryPath:r.messagesPath}:{}},t.agent&&(c.agentsByIdentityId[t.agentIdentityId]=t.agent)}catch(t){if(b(t)){this.deps.registry.detachBinding(e.registryKey),u+=1;continue}r.recordSyncError(I(t));return}}if(o){const e=n.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(r,o,s,e,c);return}u===m.length&&r.markAccessRevoked()}async pullPages(s,n,a,r,m){const u=this.deps.maxPagesPerRun??100;let o=s.readState().lastSyncedSequence;for(let c=0;c<u;c+=1){const e=await this.deps.actionClient.read({...n,afterSequence:o,limit:100}),d=s.append({messages:e.messages,historyFromSequence:e.historyFromSequence,roomName:e.roomName});if(this.deps.onMessages&&await this.deps.onMessages(a,e.messages,r,m??{roomName:e.roomName}),!e.hasMore)return;if(d.lastSyncedSequence===o)throw new h("room_sync_no_progress","Room mirror synchronization made no progress.");o=d.lastSyncedSequence}}arm(s){if(!this.started||this.timer)return;const n=this.deps.setTimeout??setTimeout;this.timer=n(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},s),this.timer.unref?.()}}function l(i){return y.createHash("sha256").update(i,"utf8").digest("hex")}function f(i){const s=new Map;for(const n of i){if(!n.binding||n.lifecycleState==="closed")continue;const a=s.get(n.binding.roomId)??[];a.push(n),s.set(n.binding.roomId,a)}return s}function b(i){return i instanceof h&&(i.code==="room_binding_unavailable"||i.code==="room_action_http_403")}function I(i){return i instanceof h?i.code:"room_sync_failed"}export{P as RoomMirrorSyncService};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
|
|
2
2
|
import type { RoomSessionRegistry } from './session-registry.js';
|
|
3
3
|
import type { HttpRoomActionClient } from './room-action-client.js';
|
|
4
|
+
import type { ManagedRoomSessionOrchestrator } from './managed-room-session.js';
|
|
4
5
|
export declare function createRoomOperatorIpcHandlers(input: {
|
|
5
6
|
registry: Pick<RoomSessionRegistry, 'list'>;
|
|
6
7
|
actionClient: Pick<HttpRoomActionClient, 'createOperatorRoom'>;
|
|
@@ -8,4 +9,5 @@ export declare function createRoomOperatorIpcHandlers(input: {
|
|
|
8
9
|
machineId?: string;
|
|
9
10
|
machineToken?: string;
|
|
10
11
|
};
|
|
12
|
+
managedRoomSessions?: Pick<ManagedRoomSessionOrchestrator, 'execute'>;
|
|
11
13
|
}): Record<string, DaemonIpcHandler>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";function
|
|
1
|
+
import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";import h from"node:crypto";import f from"node:fs";import p from"node:path";function R(e){return{"room-operator.inspect":t=>{const o=y(t),n=e.getMachineCredentials(),i=n.machineId?.trim(),l=!!(i&&n.machineToken?.trim()),a=i?e.registry.list().filter(c=>c.machineId===i):[],d=a.filter(c=>c.binding!==null),s=b(d);return g(o==="status"?{source:"local_registry",signedIn:l,registeredSessions:a.length,boundSessions:d.length,rooms:s.length}:{source:"local_registry",complete:!1,rooms:s})},"room-operator.create":async t=>{const o=_(t),n=e.getMachineCredentials();if(!n.machineId?.trim()||!n.machineToken?.trim())throw new r("cli_not_signed_in","Sign in to the Shennian CLI first.");try{return g(await e.actionClient.createOperatorRoom({machineToken:n.machineToken,name:o}))}catch(i){throw u(i)}},"room-operator.agent-create":async(t,o)=>{if(!e.managedRoomSessions)throw new r("managed_room_unavailable","Managed Room Agent creation is unavailable.");const n=e.getMachineCredentials(),i=n.machineId?.trim();if(!i||!n.machineToken?.trim())throw new r("cli_not_signed_in","Sign in to the Shennian CLI first.");const l=v(t),d=I(t)??`room_agent_${h.createHash("sha256").update(`${i}\0${o.requestId}`,"utf8").digest("hex").slice(0,48)}`;try{return g(await e.managedRoomSessions.execute(d,l))}catch(s){throw u(s)}}}}function g(e){return JSON.parse(JSON.stringify(e))}function y(e){if(!e||Array.isArray(e)||typeof e!="object")throw new r("invalid_params","Room operator parameters are required.");const t=e;if(Object.keys(t).length!==1||t.action!=="status"&&t.action!=="list")throw new r("invalid_params","Room operator action is invalid.");return t.action}function _(e){if(!e||Array.isArray(e)||typeof e!="object")throw new r("invalid_params","Room operator parameters are required.");const t=e;if(Object.keys(t).length!==1||typeof t.name!="string")throw new r("invalid_params","Room operator create parameters are invalid.");const o=t.name.trim();if(!o||o.length>80)throw new r("invalid_params","Room name must contain 1 to 80 characters.");return o}function v(e){if(!e||Array.isArray(e)||typeof e!="object")throw new r("invalid_params","Managed Agent parameters are required.");const t=e,o=new Set(["room","agent","workDir","modelId","alias","activationMode","operationId"]);if(Object.keys(t).some(w=>!o.has(w)))throw new r("invalid_params","Managed Agent parameters are invalid.");const n=m(t.room,"room",2048),i=m(t.agent,"agent",32),l=m(t.alias,"alias",80),a=m(t.activationMode,"activationMode",32),d=m(t.workDir,"workDir",4096);if(!p.isAbsolute(d))throw new r("invalid_workdir","Managed Agent workDir must be absolute.");let s;try{if(s=f.realpathSync(d),!f.statSync(s).isDirectory())throw new Error("not-directory")}catch{throw new r("invalid_workdir","Managed Agent workDir must be an existing directory.")}if(i!=="codex"&&i!=="claude"&&i!=="workbuddy")throw new r("invalid_params","Managed Agent runtime is invalid.");if(a!=="auto_follow"&&a!=="mention_only"&&a!=="paused")throw new r("invalid_params","Managed Agent activation mode is invalid.");const c=t.modelId===void 0?void 0:m(t.modelId,"modelId",191);return{inviteUrl:n,agent:i,workDir:s,alias:l,mode:a,...c?{modelId:c}:{}}}function I(e){if(!e||Array.isArray(e)||typeof e!="object")return null;const t=e.operationId;if(t===void 0)return null;if(typeof t!="string"||!/^room_agent_[a-f0-9]{48}$/.test(t))throw new r("invalid_params","Managed Agent operationId is invalid.");return t}function m(e,t,o){if(typeof e!="string"||!e.trim()||e.length>o||/[\0\r\n]/.test(e))throw new r("invalid_params",`${t} is invalid.`);return e.trim()}function u(e){return e&&typeof e=="object"&&"code"in e&&typeof e.code=="string"&&"message"in e&&typeof e.message=="string"?new r(e.code,e.message):new r("room_operator_failed","Shennian Room operator action failed.")}function b(e){const t=new Map;for(const o of e){const n=o.binding;if(!n)continue;const i=t.get(n.roomId)??{sessions:0,runtimes:new Set,statuses:new Set,modes:new Set};i.sessions+=1,i.runtimes.add(o.runtimeAdapter),i.statuses.add(n.status),i.modes.add(n.activationMode),t.set(n.roomId,i)}return[...t.entries()].sort(([o],[n])=>o.localeCompare(n)).map(([o,n])=>({roomId:o,sessions:n.sessions,runtimes:[...n.runtimes].sort(),bindingStatuses:[...n.statuses].sort(),activationModes:[...n.modes].sort()}))}export{R as createRoomOperatorIpcHandlers};
|
|
@@ -6,6 +6,7 @@ import { RoomSessionRegistry } from './session-registry.js';
|
|
|
6
6
|
import type { RoomAttachmentUploader } from './attachment-upload.js';
|
|
7
7
|
import type { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
8
8
|
type RoomMirrorWriter = {
|
|
9
|
+
readonly messagesPath?: string;
|
|
9
10
|
append(input: {
|
|
10
11
|
messages: Awaited<ReturnType<HttpRoomActionClient['read']>>['messages'];
|
|
11
12
|
historyFromSequence: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DaemonIpcError as a}from"../daemon-ipc/protocol.js";import{normalizeRoomActionParameters as R}from"./room-action-schema.js";function B(e){return{"room-action.execute":async o=>{const h=T(o),
|
|
1
|
+
import{DaemonIpcError as a}from"../daemon-ipc/protocol.js";import{normalizeRoomActionParameters as R}from"./room-action-schema.js";function B(e){return{"room-action.execute":async o=>{const h=T(o),m=e.getMachineCredentials();if(!m.machineId?.trim()||!m.machineToken?.trim())throw new a("machine_not_authorized","This machine is not authorized yet.");const r=w(h.registryKey,"registryKey",64),A=w(h.invocationToken,"invocationToken",128),c=S(h.action),u=R(c,h.actionParameters);return e.registry.runExclusive(r,async()=>{let y;try{y=e.authority.consume({invocationToken:A,machineId:m.machineId,machineCredential:m.machineToken,registryKey:r,action:c,targetTool:"shennian_room",actionParameters:u})}catch(t){throw C(t)}const d=e.registry.getByRegistryKey(r),i=d?.binding;if(!d)throw new a("room_session_unavailable","The exact Agent session is unavailable.");if(c==="room.create"){if(i&&i.status!=="detached")throw new a("session_already_bound","This Agent session is already bound to a Room.");if(!y.bindingIntentId)throw new a("binding_intent_missing","Room create intent is missing.");const t=u,n=await e.actionClient.createRoom({machineToken:m.machineToken,bindingIntentId:y.bindingIntentId,nianSessionId:d.nianSessionId,runtimeAdapter:d.runtimeAdapter,sessionRegistryKey:r,name:t.name,agentAlias:t.agentAlias,agentDescription:t.agentDescription,activationCapability:_(d.capabilities),activationMode:t.activationMode});return e.registry.setBinding({registryKey:r,bindingId:n.binding.id,roomId:n.room.id,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),g(n)}if(c==="room.join"){if(i&&i.status!=="detached")throw new a("session_already_bound","This Agent session is already bound to a Room.");if(!y.bindingIntentId)throw new a("binding_intent_missing","Room join intent is missing.");const t=u,n=await e.actionClient.joinRoom({machineToken:m.machineToken,bindingIntentId:y.bindingIntentId,nianSessionId:d.nianSessionId,runtimeAdapter:d.runtimeAdapter,sessionRegistryKey:r,inviteToken:k(t.inviteUrl),message:t.message,agentAlias:t.agentAlias,agentDescription:t.agentDescription,activationCapability:_(d.capabilities),activationMode:t.activationMode});return e.registry.setBinding({registryKey:r,bindingId:n.binding.id,roomId:n.room.id,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),g(n)}if(!i)throw new a("room_not_bound","This Agent session is not bound to a Room.");if(c!=="room.status"&&(i.status==="detached"||i.status==="pending_approval"))throw new a(i.status==="pending_approval"?"room_binding_pending_approval":"room_binding_detached",i.status==="pending_approval"?"This Agent is waiting for Room approval.":"This Agent Room binding must be repaired before use.");const I={machineToken:m.machineToken,roomId:i.roomId,bindingId:i.bindingId};if(c==="room.status"){const t=await e.actionClient.status(I),n=e.mirrorFactory?.(t.roomId);e.registry.setBinding({registryKey:r,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode}),t.status==="detached"&&n?.markAccessRevoked();const l=e.registry.getByRegistryKey(r),b=l?.binding,s=e.observations?.getBySource(d);return g({...t,localHistoryPath:n?.messagesPath??null,hostSession:{state:l?.lifecycleState??"closed",lastReportedEvent:s?.lastEventName??null,lastActiveAt:s?.lastActiveAt??null,lastIdleAt:s?.lastIdleAt??null,closedAt:s?.closedAt??null,closedConfirmed:s?.lifecycleState==="closed",activationCapability:_(d.capabilities)},localActivation:{queued:b?.activationQueue.length??0,inFlight:!!b?.activeActivation,pausedUntil:b?.loopBreaker.pausedUntil??null}})}if(c==="room.read"){const t=u;if(t.query){const s=e.mirrorFactory?.(i.roomId);if(!s)throw new a("room_mirror_unavailable","Room mirror is unavailable.");return g({source:"local_mirror",matches:s.search({query:t.query,limit:t.limit})})}const n=await e.actionClient.read({...I,afterSequence:t.afterSequence,limit:t.limit});e.mirrorFactory?.(i.roomId).append({messages:n.messages,historyFromSequence:n.historyFromSequence,roomName:n.roomName});const l=BigInt(n.nextSequence)>BigInt(i.lastDeliveredSequence)?n.nextSequence:i.lastDeliveredSequence;e.registry.advanceCursor({registryKey:r,cursor:"delivered",sequence:l});const b=[];for(const s of t.attachmentIds??[]){const v=n.messages.flatMap(p=>p.attachments).find(p=>p.attachmentId===s);if(!v||v.status!=="ready")throw new a("attachment_not_in_read_result","Requested attachment is not available in this read result.");if(!e.attachmentCache)throw new a("attachment_cache_unavailable","Room attachment cache is unavailable.");b.push({attachmentId:s,...await e.attachmentCache.materialize({...I,attachmentId:s,expected:{downloadRef:v.downloadRef,byteSize:v.byteSize,sha256:v.sha256}})})}return g({...n,materializedAttachments:b})}if(c==="room.send"){const t=u;let n;if(t.files?.length){const l=e.observations?.getBySource(d);if(!l?.workspacePath||!e.attachmentUploader)throw new a("room_attachment_upload_unavailable","The exact Agent workspace is unavailable for attachment upload.");n=await e.attachmentUploader.upload({...I,workspacePath:l.workspacePath,clientMessageId:t.clientMessageId,files:t.files})}return g(await e.actionClient.send({...I,clientMessageId:t.clientMessageId,text:t.text,mentionMembershipIds:t.mentionMembershipIds,replyToMessageId:t.replyToMessageId,attachmentIds:n,activationCauseMessageId:i.activeActivation?.causeMessageId}))}const M=u,f=await e.actionClient.mode({...I,...M});return e.registry.setBinding({registryKey:r,bindingId:i.bindingId,roomId:i.roomId,agentIdentityId:i.agentIdentityId,agentMembershipId:i.agentMembershipId,status:f.status,activationMode:f.activationMode}),g(f)})}}}function T(e){if(!e||Array.isArray(e)||typeof e!="object")throw new a("invalid_params","Room action parameters are required.");return e}function w(e,o,h){if(typeof e!="string"||!e.trim()||e.length>h)throw new a("invalid_params",`${o} is invalid.`);return e.trim()}function S(e){const o=w(e,"action",64);if(o!=="room.read"&&o!=="room.create"&&o!=="room.join"&&o!=="room.send"&&o!=="room.mode"&&o!=="room.status")throw new a("room_action_not_implemented","Room action is not available yet.");return o}function k(e){const o=new URL(e).pathname.split("/").at(-1);if(!o||!/^[A-Za-z0-9_-]{43}$/.test(o))throw new a("invalid_params","inviteUrl is invalid.");return o}function _(e){return e.canResumeIdleSession||e.canActivateWhileHostOpen?"idle_resume":"notify_only"}function C(e){return e&&typeof e=="object"&&"code"in e&&typeof e.code=="string"&&"message"in e&&typeof e.message=="string"?new a(e.code,e.message):new a("room_invocation_failed","Room invocation failed.")}function g(e){return JSON.parse(JSON.stringify(e))}export{B as createRoomActionIpcHandlers};
|
|
@@ -12,6 +12,10 @@ export type CreateRemoteRoomBindingInput = {
|
|
|
12
12
|
activationCapability: RoomActivationCapability;
|
|
13
13
|
activationMode: RoomActivationMode;
|
|
14
14
|
};
|
|
15
|
+
export type RestartRemoteRoomBindingInput = Omit<CreateRemoteRoomBindingInput, 'bindingIntentId' | 'activationCapability' | 'activationMode'> & {
|
|
16
|
+
bindingId: string;
|
|
17
|
+
previousNianSessionId: string;
|
|
18
|
+
};
|
|
15
19
|
export type RemoteRoomBinding = {
|
|
16
20
|
id: string;
|
|
17
21
|
roomId: string;
|
|
@@ -47,5 +51,10 @@ export declare class HttpRoomBindingClient {
|
|
|
47
51
|
binding: RemoteRoomBinding;
|
|
48
52
|
idempotent: boolean;
|
|
49
53
|
}>;
|
|
54
|
+
restart(input: RestartRemoteRoomBindingInput): Promise<{
|
|
55
|
+
binding: RemoteRoomBinding;
|
|
56
|
+
resumeSequence: string;
|
|
57
|
+
idempotent: boolean;
|
|
58
|
+
}>;
|
|
50
59
|
}
|
|
51
60
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HttpRoomActionClient as r}from"./room-action-client.js";class o extends Error{code;constructor(
|
|
1
|
+
import{HttpRoomActionClient as r}from"./room-action-client.js";class o extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomBindingHttpError"}}class m{fetchImpl;baseUrl;actionClient;constructor(e,n=fetch){this.fetchImpl=n,this.baseUrl=e.replace(/\/+$/,""),this.actionClient=new r(e,n)}status=e=>this.actionClient.status(e);getAttachmentCapability=e=>this.actionClient.getAttachmentCapability(e);initializeAttachment=e=>this.actionClient.initializeAttachment(e);completeAttachment=e=>this.actionClient.completeAttachment(e);createRoom=e=>this.actionClient.createRoom(e);createOperatorRoom=e=>this.actionClient.createOperatorRoom(e);resolveNativeSession=e=>this.actionClient.resolveNativeSession(e);joinRoom=e=>this.actionClient.joinRoom(e);read=e=>this.actionClient.read(e);send=e=>this.actionClient.send(e);mode=e=>this.actionClient.mode(e);async create(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let i=`room_binding_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(i=s.error)}catch{}throw new o(i,"Shennian Room binding request failed.")}let t;try{t=await n.json()}catch{throw new o("invalid_room_binding_response","Invalid Room binding response.")}return d(t,e)}async restart(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/session`,{method:"PATCH",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({agentIdentityId:e.agentIdentityId,previousNianSessionId:e.previousNianSessionId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}const t=await n.json().catch(()=>null);if(!n.ok){const s=t&&typeof t.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(t.error)?t.error:`room_binding_http_${n.status}`;throw new o(s,"Shennian Room restart request failed.")}const i=t;if(!i?.binding||i.binding.id!==e.bindingId||i.binding.roomId!==e.roomId||i.binding.agentIdentityId!==e.agentIdentityId||i.binding.nianSessionId!==e.nianSessionId||i.binding.runtimeAdapter!==e.runtimeAdapter||typeof i.binding.agentMembershipId!="string"||typeof i.resumeSequence!="string"||!/^(0|[1-9]\d{0,19})$/.test(i.resumeSequence)||typeof i.idempotent!="boolean")throw new o("invalid_room_binding_response","Invalid Room restart response.");return{binding:i.binding,resumeSequence:i.resumeSequence,idempotent:i.idempotent}}}function d(a,e){if(!a||typeof a!="object")throw new o("invalid_room_binding_response","Invalid Room binding response.");const n=a,t=n.binding;if(!t||typeof t.id!="string"||typeof t.agentMembershipId!="string"||t.roomId!==e.roomId||t.agentIdentityId!==e.agentIdentityId||t.nianSessionId!==e.nianSessionId||t.runtimeAdapter!==e.runtimeAdapter||t.activationCapability!==e.activationCapability||t.activationMode!==e.activationMode||!["pending_approval","active","paused","detached"].includes(t.status??"")||typeof n.idempotent!="boolean")throw new o("invalid_room_binding_response","Room binding response does not match the request.");return{binding:t,idempotent:n.idempotent}}export{m as HttpRoomBindingClient,o as RoomBindingHttpError};
|
|
@@ -16,7 +16,9 @@ export declare function createRoomBindingIpcHandlers(input: {
|
|
|
16
16
|
machineId?: string;
|
|
17
17
|
machineToken?: string;
|
|
18
18
|
};
|
|
19
|
-
mirrorFactory?: (roomId: string) => Pick<RoomJsonlMirror, 'append' | 'markAccessRevoked' | 'search'
|
|
19
|
+
mirrorFactory?: (roomId: string) => Pick<RoomJsonlMirror, 'append' | 'markAccessRevoked' | 'search'> & {
|
|
20
|
+
readonly messagesPath?: string;
|
|
21
|
+
};
|
|
20
22
|
attachmentCache?: Pick<RoomAttachmentCache, 'materialize'>;
|
|
21
23
|
attachmentUploader?: Pick<RoomAttachmentUploader, 'upload'>;
|
|
22
24
|
observations?: RoomHostSessionObservationStore;
|
|
@@ -17,12 +17,17 @@ export type RoomSessionBindingState = {
|
|
|
17
17
|
lastActivatedSequence: string;
|
|
18
18
|
activationQueue: RoomActivationQueueItem[];
|
|
19
19
|
activeActivation: RoomActivationQueueItem | null;
|
|
20
|
+
mentionContext?: RoomMentionContextItem[];
|
|
20
21
|
loopBreaker: {
|
|
21
22
|
windowStartedAt: string | null;
|
|
22
23
|
activationCount: number;
|
|
23
24
|
pausedUntil: string | null;
|
|
24
25
|
};
|
|
25
26
|
};
|
|
27
|
+
export type RoomMentionContextItem = {
|
|
28
|
+
sequence: string;
|
|
29
|
+
deliveryText: string;
|
|
30
|
+
};
|
|
26
31
|
export type RoomActivationQueueItem = {
|
|
27
32
|
causeMessageId: string;
|
|
28
33
|
rootCauseMessageId: string;
|
|
@@ -90,7 +95,8 @@ export declare class RoomSessionRegistry {
|
|
|
90
95
|
sequence: string;
|
|
91
96
|
now?: Date;
|
|
92
97
|
}): RoomSessionRegistration;
|
|
93
|
-
enqueueActivation(registryKey: string, item: RoomActivationQueueItem, now?: Date): RoomSessionRegistration;
|
|
98
|
+
enqueueActivation(registryKey: string, item: RoomActivationQueueItem, now?: Date, clearMentionContext?: boolean): RoomSessionRegistration;
|
|
99
|
+
appendMentionContext(registryKey: string, item: RoomMentionContextItem, now?: Date): RoomSessionRegistration;
|
|
94
100
|
dequeueActivation(registryKey: string, causeMessageId: string, now?: Date): RoomSessionRegistration;
|
|
95
101
|
markActivationDispatching(registryKey: string, causeMessageId: string, now?: Date): RoomSessionRegistration;
|
|
96
102
|
clearActiveActivation(registryKey: string, now?: Date): RoomSessionRegistration;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&
|
|
1
|
+
import w from"node:crypto";import u from"node:fs";import x from"node:path";import{resolveShennianPath as P}from"../config/index.js";const _=500,p=20,q=/^[A-Za-z0-9_.:@/-]{1,512}$/,R=new Set(["codex","claude_code","workbuddy"]),I=new Set(["pending_approval","active","paused","detached"]),A=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class N{filePath;state;mutexTails=new Map;constructor(e=P("runtime","room-session-registry.json")){this.filePath=e,this.state=this.load()}register(e){c(e.machineId,"machine_id"),f(e.runtimeAdapter),y(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),S(e.capabilities);const i=m(e),s=(e.now??new Date).toISOString(),n=this.state.records[i];if(Object.values(this.state.records).find(g=>g.registryKey!==i&&g.nianSessionId===e.nianSessionId&&g.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[m(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(!A.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);h(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);v(i);const r=a.binding.activationQueue;if(!r.some(l=>l.causeMessageId===i.causeMessageId)){if(r.length>=_)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(-p)),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),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};const n=e.now??new Date,a={...s,attempts:s.attempts+1};return i.binding.activeActivation=null,a.attempts<e.maxAttempts?(i.binding.activationQueue.some(r=>r.causeMessageId===a.causeMessageId)||i.binding.activationQueue.unshift(a),this.touchAndPersist(i,n),{queued:!0,attempts:a.attempts}):(i.binding.loopBreaker.pausedUntil=new Date(n.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,n),{queued:!1,attempts:a.attempts})}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 C(i)}catch(e){if(B(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=x.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 m(t){return c(t.machineId,"machine_id"),f(t.runtimeAdapter),y(t.sourceSessionKey),w.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function C(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(M(n),s!==n.registryKey||s!==m(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 M(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),f(t.runtimeAdapter),y(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.");S(t.capabilities),t.binding&&T(t.binding)}function T(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"),!A.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(h(t.lastDeliveredSequence),h(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>_)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(v),t.activeActivation!=null&&v(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>p))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(b)}function b(t){if(h(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function v(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),h(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.")}function S(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(!R.has(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function y(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(!q.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function h(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 B(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{N as RoomSessionRegistry,o as RoomSessionRegistryError,m as buildRoomSessionRegistryKey};
|
|
@@ -4,7 +4,7 @@ export declare const SHENNIAN_ROOM_TOOL_NAME = "shennian_room";
|
|
|
4
4
|
export declare const SHENNIAN_ROOM_TOOL: {
|
|
5
5
|
readonly name: "shennian_room";
|
|
6
6
|
readonly title: "Shennian Room";
|
|
7
|
-
readonly description: "Create, join, read, search, send to, configure, or inspect the single Shennian Room bound to this Agent session.";
|
|
7
|
+
readonly description: "Create, join, read, search, send to, configure, or inspect the single Shennian Room bound to this Agent session. Status includes member and Agent descriptions plus the canonical local JSONL mirror path.";
|
|
8
8
|
readonly inputSchema: {
|
|
9
9
|
readonly type: "object";
|
|
10
10
|
readonly additionalProperties: false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeRoomActionParameters as c}from"./room-action-schema.js";const d="shennian_room",l={name:d,title:"Shennian Room",description:"Create, join, read, search, send to, configure, or inspect the single Shennian Room bound to this Agent session.",inputSchema:{type:"object",additionalProperties:!1,required:["action"],properties:{action:{type:"string",enum:["create","join","read","send","mode","status"],description:"The Room action to perform."},name:{type:"string",minLength:1,maxLength:80},agentAlias:{type:"string",minLength:1,maxLength:80},agentDescription:{type:"string",maxLength:1e3},inviteUrl:{type:"string",format:"uri",maxLength:2048},message:{type:"string",maxLength:500},activationMode:{type:"string",enum:["auto","auto_follow","mention","mention_only","paused"]},afterSequence:{type:"string",pattern:"^(0|[1-9]\\d{0,19})$"},limit:{type:"integer",minimum:1,maximum:100},query:{type:"string",minLength:1,maxLength:200},attachmentIds:{type:"array",maxItems:10,uniqueItems:!0,items:{type:"string",minLength:1,maxLength:191}},clientMessageId:{type:"string",minLength:1,maxLength:191},text:{type:"string",minLength:1,maxLength:2e4},files:{type:"array",description:"Up to 10 files inside the current Agent workspace to upload privately with this message.",minItems:1,maxItems:10,items:{type:"string",minLength:1,maxLength:4096}},mentionMembershipIds:{type:"array",maxItems:20,uniqueItems:!0,items:{type:"string",minLength:1,maxLength:191}},replyToMessageId:{type:"string",minLength:1,maxLength:191}}}},u=new Map([["create","room.create"],["join","room.join"],["read","room.read"],["send","room.send"],["mode","room.mode"],["status","room.status"]]);function f(i){(!i||typeof i!="object"||Array.isArray(i))&&g();const t=i,n=typeof t.action=="string"?u.get(t.action):void 0;n||g();const o=t._shennianRegistryKey,r=t._shennianInvocationToken,s=t._shennianAuthorizedActionId,a=Object.keys(t).filter(e=>e.startsWith("_shennian"));if(typeof s=="string"&&/^[A-Za-z0-9_-]{8,128}$/.test(s)&&a.length===1){const e=Object.fromEntries(Object.entries(t).filter(([m])=>m!=="action"&&m!=="_shennianAuthorizedActionId"));return{action:n,actionParameters:c(n,e),authorizedActionId:s}}if(typeof o!="string"||!/^[a-f0-9]{64}$/.test(o)||typeof r!="string"||!/^sni_v1_[A-Za-z0-9_-]{40,80}$/.test(r)||a.some(e=>e!=="_shennianRegistryKey"&&e!=="_shennianInvocationToken"))throw new p("session_context_required","The current Agent session is not connected to Shennian Room yet.");const h=Object.fromEntries(Object.entries(t).filter(([e])=>e!=="action"&&e!=="_shennianRegistryKey"&&e!=="_shennianInvocationToken"));return{action:n,actionParameters:c(n,h),registryKey:o,invocationToken:r}}class p extends Error{code;constructor(t,n){super(n),this.code=t,this.name="RoomToolInputError"}}function g(){throw new p("invalid_tool_input","Invalid Shennian Room tool input.")}export{p as RoomToolInputError,l as SHENNIAN_ROOM_TOOL,d as SHENNIAN_ROOM_TOOL_NAME,f as normalizeRoomToolInput};
|
|
1
|
+
import{normalizeRoomActionParameters as c}from"./room-action-schema.js";const d="shennian_room",l={name:d,title:"Shennian Room",description:"Create, join, read, search, send to, configure, or inspect the single Shennian Room bound to this Agent session. Status includes member and Agent descriptions plus the canonical local JSONL mirror path.",inputSchema:{type:"object",additionalProperties:!1,required:["action"],properties:{action:{type:"string",enum:["create","join","read","send","mode","status"],description:"The Room action to perform."},name:{type:"string",minLength:1,maxLength:80},agentAlias:{type:"string",minLength:1,maxLength:80},agentDescription:{type:"string",maxLength:1e3},inviteUrl:{type:"string",format:"uri",maxLength:2048},message:{type:"string",maxLength:500},activationMode:{type:"string",enum:["auto","auto_follow","mention","mention_only","paused"]},afterSequence:{type:"string",pattern:"^(0|[1-9]\\d{0,19})$"},limit:{type:"integer",minimum:1,maximum:100},query:{type:"string",minLength:1,maxLength:200},attachmentIds:{type:"array",maxItems:10,uniqueItems:!0,items:{type:"string",minLength:1,maxLength:191}},clientMessageId:{type:"string",minLength:1,maxLength:191},text:{type:"string",minLength:1,maxLength:2e4},files:{type:"array",description:"Up to 10 files inside the current Agent workspace to upload privately with this message.",minItems:1,maxItems:10,items:{type:"string",minLength:1,maxLength:4096}},mentionMembershipIds:{type:"array",maxItems:20,uniqueItems:!0,items:{type:"string",minLength:1,maxLength:191}},replyToMessageId:{type:"string",minLength:1,maxLength:191}}}},u=new Map([["create","room.create"],["join","room.join"],["read","room.read"],["send","room.send"],["mode","room.mode"],["status","room.status"]]);function f(i){(!i||typeof i!="object"||Array.isArray(i))&&g();const t=i,n=typeof t.action=="string"?u.get(t.action):void 0;n||g();const o=t._shennianRegistryKey,r=t._shennianInvocationToken,s=t._shennianAuthorizedActionId,a=Object.keys(t).filter(e=>e.startsWith("_shennian"));if(typeof s=="string"&&/^[A-Za-z0-9_-]{8,128}$/.test(s)&&a.length===1){const e=Object.fromEntries(Object.entries(t).filter(([m])=>m!=="action"&&m!=="_shennianAuthorizedActionId"));return{action:n,actionParameters:c(n,e),authorizedActionId:s}}if(typeof o!="string"||!/^[a-f0-9]{64}$/.test(o)||typeof r!="string"||!/^sni_v1_[A-Za-z0-9_-]{40,80}$/.test(r)||a.some(e=>e!=="_shennianRegistryKey"&&e!=="_shennianInvocationToken"))throw new p("session_context_required","The current Agent session is not connected to Shennian Room yet.");const h=Object.fromEntries(Object.entries(t).filter(([e])=>e!=="action"&&e!=="_shennianRegistryKey"&&e!=="_shennianInvocationToken"));return{action:n,actionParameters:c(n,h),registryKey:o,invocationToken:r}}class p extends Error{code;constructor(t,n){super(n),this.code=t,this.name="RoomToolInputError"}}function g(){throw new p("invalid_tool_input","Invalid Shennian Room tool input.")}export{p as RoomToolInputError,l as SHENNIAN_ROOM_TOOL,d as SHENNIAN_ROOM_TOOL_NAME,f as normalizeRoomToolInput};
|