shennian 0.3.37 → 0.3.39
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/README.md +1 -0
- package/dist/publish-build-manifest.json +73 -168
- package/dist/src/agents/detect.js +1 -1
- package/dist/src/agents/manager.d.ts +36 -0
- package/dist/src/agents/manager.js +11 -6
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/agents/workbuddy.js +2 -2
- package/dist/src/commands/agent.d.ts +6 -1
- package/dist/src/commands/agent.js +3 -6
- package/dist/src/commands/integration.d.ts +1 -0
- package/dist/src/commands/integration.js +2 -1
- package/dist/src/commands/manager.js +1 -1
- package/dist/src/commands/pair-authorization.d.ts +3 -0
- package/dist/src/commands/pair-authorization.js +1 -0
- package/dist/src/commands/pair-browser.js +1 -1
- package/dist/src/commands/room.d.ts +9 -17
- package/dist/src/commands/room.js +3 -8
- package/dist/src/commands/runtime.d.ts +2 -0
- package/dist/src/commands/runtime.js +6 -0
- package/dist/src/daemon-ipc/client.d.ts +1 -1
- package/dist/src/daemon-ipc/json.d.ts +3 -0
- package/dist/src/daemon-ipc/json.js +1 -0
- package/dist/src/daemon-ipc/protocol.d.ts +1 -1
- package/dist/src/daemon-ipc/server.d.ts +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/manager/registry.d.ts +21 -0
- package/dist/src/manager/registry.js +2 -2
- package/dist/src/manager/runtime.d.ts +5 -1
- package/dist/src/manager/runtime.js +14 -13
- package/dist/src/room/activation-service.d.ts +6 -0
- package/dist/src/room/activation-service.js +5 -8
- package/dist/src/room/attachment-cache.d.ts +2 -1
- package/dist/src/room/attachment-cache.js +2 -2
- package/dist/src/room/device-authorization-client.d.ts +39 -1
- package/dist/src/room/managed-context.d.ts +45 -0
- package/dist/src/room/managed-context.js +1 -0
- package/dist/src/room/managed-prompt-composer.d.ts +32 -0
- package/dist/src/room/managed-prompt-composer.js +24 -0
- package/dist/src/room/managed-room-ipc.d.ts +16 -0
- package/dist/src/room/managed-room-ipc.js +1 -0
- package/dist/src/room/mirror-sync-service.d.ts +2 -5
- package/dist/src/room/mirror-sync-service.js +1 -1
- package/dist/src/room/public-attachment-cache.d.ts +34 -0
- package/dist/src/room/public-attachment-cache.js +2 -0
- package/dist/src/room/public-room-client.d.ts +36 -0
- package/dist/src/room/public-room-client.js +1 -0
- package/dist/src/room/room-action-client.d.ts +3 -1
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/room/session-registry.d.ts +7 -0
- package/dist/src/room/session-registry.js +2 -2
- package/dist/src/session/handlers/chat.d.ts +5 -1
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/manager.d.ts +1 -20
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue.js +3 -3
- package/dist/src/session/store.d.ts +9 -1
- package/dist/src/session/store.js +3 -3
- package/dist/src/session/types.d.ts +2 -1
- package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/agent-capabilities.js +0 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.js +0 -1
- package/node_modules/@shennian/wire/dist/envelope.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/envelope.js +0 -1
- package/node_modules/@shennian/wire/dist/events.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/events.js +0 -1
- package/node_modules/@shennian/wire/dist/external-message-format.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/external-message-format.js +0 -1
- package/node_modules/@shennian/wire/dist/frames.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/frames.js +0 -1
- package/node_modules/@shennian/wire/dist/fs.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/fs.js +0 -1
- package/node_modules/@shennian/wire/dist/index.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/index.js +0 -1
- package/node_modules/@shennian/wire/dist/machine.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/machine.js +0 -1
- package/node_modules/@shennian/wire/dist/message-payload.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/message-payload.js +0 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/methods.js +0 -1
- package/node_modules/@shennian/wire/dist/native-session.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/native-session.js +0 -1
- package/node_modules/@shennian/wire/dist/project.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/project.js +0 -1
- package/node_modules/@shennian/wire/dist/room.d.ts +44 -1
- package/node_modules/@shennian/wire/dist/room.js +0 -1
- package/node_modules/@shennian/wire/dist/session.d.ts +18 -3
- package/node_modules/@shennian/wire/dist/session.js +0 -2
- package/node_modules/@shennian/wire/dist/tool-detail.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/tool-detail.js +0 -1
- package/node_modules/@shennian/wire/dist/voice-chief.d.ts +0 -1
- package/node_modules/@shennian/wire/dist/voice-chief.js +0 -1
- package/node_modules/@shennian/wire/package.json +1 -0
- package/package.json +2 -2
- package/dist/src/integrations/claude-room-plugin-lifecycle.d.ts +0 -47
- package/dist/src/integrations/claude-room-plugin-lifecycle.js +0 -1
- package/dist/src/integrations/codex-room-plugin-lifecycle.d.ts +0 -49
- package/dist/src/integrations/codex-room-plugin-lifecycle.js +0 -1
- package/dist/src/integrations/room-plugin-bundles.d.ts +0 -23
- package/dist/src/integrations/room-plugin-bundles.js +0 -1
- package/dist/src/integrations/room-plugin-lifecycle-common.d.ts +0 -17
- package/dist/src/integrations/room-plugin-lifecycle-common.js +0 -1
- package/dist/src/integrations/room-plugin-shared.d.ts +0 -24
- package/dist/src/integrations/room-plugin-shared.js +0 -38
- package/dist/src/integrations/room-session-hook.d.ts +0 -30
- package/dist/src/integrations/room-session-hook.js +0 -2
- package/dist/src/integrations/workbuddy-plugin-bundle.d.ts +0 -16
- package/dist/src/integrations/workbuddy-plugin-bundle.js +0 -1
- package/dist/src/integrations/workbuddy-plugin-lifecycle.d.ts +0 -62
- package/dist/src/integrations/workbuddy-plugin-lifecycle.js +0 -2
- package/dist/src/integrations/workbuddy-session-hook.d.ts +0 -22
- package/dist/src/integrations/workbuddy-session-hook.js +0 -1
- package/dist/src/room/agent-tool.d.ts +0 -63
- package/dist/src/room/agent-tool.js +0 -1
- package/dist/src/room/authorized-action-coordinator.d.ts +0 -40
- package/dist/src/room/authorized-action-coordinator.js +0 -1
- package/dist/src/room/authorized-action-ipc.d.ts +0 -11
- package/dist/src/room/authorized-action-ipc.js +0 -1
- package/dist/src/room/host-session-observation-ipc.d.ts +0 -9
- package/dist/src/room/host-session-observation-ipc.js +0 -1
- package/dist/src/room/invocation-token-ipc.d.ts +0 -9
- package/dist/src/room/invocation-token-ipc.js +0 -1
- package/dist/src/room/invocation-token.d.ts +0 -53
- package/dist/src/room/invocation-token.js +0 -1
- package/dist/src/room/jsonl-mirror.d.ts +0 -73
- package/dist/src/room/jsonl-mirror.js +0 -6
- package/dist/src/room/managed-room-session-runtime.d.ts +0 -18
- package/dist/src/room/managed-room-session-runtime.js +0 -1
- package/dist/src/room/managed-room-session.d.ts +0 -144
- package/dist/src/room/managed-room-session.js +0 -2
- package/dist/src/room/operator-ipc.d.ts +0 -13
- package/dist/src/room/operator-ipc.js +0 -1
- package/dist/src/room/pending-authorized-actions.d.ts +0 -69
- package/dist/src/room/pending-authorized-actions.js +0 -3
- package/dist/src/room/room-action-ipc.d.ts +0 -34
- package/dist/src/room/room-action-ipc.js +0 -1
- package/dist/src/room/room-action-schema.d.ts +0 -3
- package/dist/src/room/room-action-schema.js +0 -1
- package/dist/src/room/room-authorized-action-executor.d.ts +0 -23
- package/dist/src/room/room-authorized-action-executor.js +0 -1
- package/dist/src/room/room-binding-ipc.d.ts +0 -31
- package/dist/src/room/room-binding-ipc.js +0 -1
- package/dist/src/room/session-registry-ipc.d.ts +0 -14
- package/dist/src/room/session-registry-ipc.js +0 -1
- package/dist/src/room/tool-schema.d.ts +0 -120
- package/dist/src/room/tool-schema.js +0 -1
- package/dist/src/room/tool-server.d.ts +0 -11
- package/dist/src/room/tool-server.js +0 -3
- package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.d.ts +0 -2
- package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.js +0 -23
- package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.js.map +0 -1
- package/node_modules/@shennian/wire/dist/__tests__/mock.test.d.ts +0 -2
- package/node_modules/@shennian/wire/dist/__tests__/mock.test.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/__tests__/mock.test.js +0 -66
- package/node_modules/@shennian/wire/dist/__tests__/mock.test.js.map +0 -1
- package/node_modules/@shennian/wire/dist/__tests__/types.test.d.ts +0 -2
- package/node_modules/@shennian/wire/dist/__tests__/types.test.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/__tests__/types.test.js +0 -159
- package/node_modules/@shennian/wire/dist/__tests__/types.test.js.map +0 -1
- package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/agent-capabilities.js.map +0 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.js.map +0 -1
- package/node_modules/@shennian/wire/dist/envelope.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/envelope.js.map +0 -1
- package/node_modules/@shennian/wire/dist/events.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/events.js.map +0 -1
- package/node_modules/@shennian/wire/dist/external-message-format.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/external-message-format.js.map +0 -1
- package/node_modules/@shennian/wire/dist/frames.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/frames.js.map +0 -1
- package/node_modules/@shennian/wire/dist/fs.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/fs.js.map +0 -1
- package/node_modules/@shennian/wire/dist/index.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/index.js.map +0 -1
- package/node_modules/@shennian/wire/dist/machine.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/machine.js.map +0 -1
- package/node_modules/@shennian/wire/dist/message-payload.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/message-payload.js.map +0 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/methods.js.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/fixtures.d.ts +0 -39
- package/node_modules/@shennian/wire/dist/mock/fixtures.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/fixtures.js +0 -99
- package/node_modules/@shennian/wire/dist/mock/fixtures.js.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/index.d.ts +0 -3
- package/node_modules/@shennian/wire/dist/mock/index.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/index.js +0 -4
- package/node_modules/@shennian/wire/dist/mock/index.js.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/routes.d.ts +0 -96
- package/node_modules/@shennian/wire/dist/mock/routes.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/mock/routes.js +0 -40
- package/node_modules/@shennian/wire/dist/mock/routes.js.map +0 -1
- package/node_modules/@shennian/wire/dist/native-session.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/native-session.js.map +0 -1
- package/node_modules/@shennian/wire/dist/project.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/project.js.map +0 -1
- package/node_modules/@shennian/wire/dist/queue.d.ts +0 -15
- package/node_modules/@shennian/wire/dist/queue.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/queue.js +0 -2
- package/node_modules/@shennian/wire/dist/queue.js.map +0 -1
- package/node_modules/@shennian/wire/dist/room.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/room.js.map +0 -1
- package/node_modules/@shennian/wire/dist/session.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/session.js.map +0 -1
- package/node_modules/@shennian/wire/dist/skills.d.ts +0 -61
- package/node_modules/@shennian/wire/dist/skills.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/skills.js +0 -2
- package/node_modules/@shennian/wire/dist/skills.js.map +0 -1
- package/node_modules/@shennian/wire/dist/tool-detail.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/tool-detail.js.map +0 -1
- package/node_modules/@shennian/wire/dist/voice-chief.d.ts.map +0 -1
- package/node_modules/@shennian/wire/dist/voice-chief.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RoomActionHttpError as
|
|
1
|
+
import{RoomActionHttpError as m}from"./room-action-client.js";import y from"node:crypto";class b{deps;timer=null;running=!1;started=!1;constructor(i){this.deps=i}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 i=this.deps.getMachineCredentials().machineToken?.trim();if(i){this.running=!0;try{const n=p(this.deps.registry.list());for(const[r,g]of n)await this.syncRoom(r,g,i)}finally{this.running=!1}}}async syncRoom(i,n,r){const g=n.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(g.length===0)return;let u=0,o=null,s=null;for(const e of g){const d=e.binding,c={machineToken:r,roomId:i,bindingId:d.bindingId};try{const t=await this.deps.actionClient.status(c);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:h(e.hostProcessIdentity),workspaceHash:h(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??=c,s??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},agentSystemPromptsByIdentityId:{}},t.agent&&(s.agentsByIdentityId[t.agentIdentityId]=t.agent,s.agentSystemPromptsByIdentityId[t.agentIdentityId]=t.agent.systemPrompt??"")}catch(t){if(l(t)){this.deps.registry.detachBinding(e.registryKey),u+=1;continue}return}}if(o){const e=n.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(o,i,e,s);return}}async pullPages(i,n,r,g){const u=this.deps.maxPagesPerRun??100;let o=r.map(s=>s.binding?.lastDeliveredSequence??"0").reduce((s,e)=>BigInt(s)<BigInt(e)?s:e,"0");for(let s=0;s<u;s+=1){const e=await this.deps.actionClient.read({...i,afterSequence:o,limit:100});for(const d of r){const c=this.deps.registry.getByRegistryKey(d.registryKey);c?.binding&&(c.binding.status==="active"||c.binding.status==="paused")&&BigInt(e.nextSequence)>BigInt(c.binding.lastDeliveredSequence)&&this.deps.registry.advanceCursor({registryKey:d.registryKey,cursor:"delivered",sequence:e.nextSequence})}if(this.deps.onMessages&&await this.deps.onMessages(n,e.messages,r,g??{roomName:e.roomName}),!e.hasMore)return;if(e.nextSequence===o)throw new m("room_sync_no_progress","Room mirror synchronization made no progress.");o=e.nextSequence}}arm(i){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)})},i),this.timer.unref?.()}}function h(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function p(a){const i=new Map;for(const n of a){if(!n.binding||n.lifecycleState==="closed")continue;const r=i.get(n.binding.roomId)??[];r.push(n),i.set(n.binding.roomId,r)}return i}function l(a){return a instanceof m&&(a.code==="room_binding_unavailable"||a.code==="room_action_http_403")}export{b as RoomMirrorSyncService};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PublicRoomClient } from './public-room-client.js';
|
|
2
|
+
type AttachmentMetadata = {
|
|
3
|
+
attachmentId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
byteSize: string;
|
|
7
|
+
sha256: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class PublicAttachmentCache {
|
|
10
|
+
private readonly options;
|
|
11
|
+
constructor(options: {
|
|
12
|
+
client: PublicRoomClient;
|
|
13
|
+
rootDirectory?: string;
|
|
14
|
+
maxCacheBytes?: number;
|
|
15
|
+
maxCacheAgeMs?: number;
|
|
16
|
+
});
|
|
17
|
+
lookup(roomId: string, attachment: AttachmentMetadata): {
|
|
18
|
+
downloaded: boolean;
|
|
19
|
+
localPath: string | null;
|
|
20
|
+
};
|
|
21
|
+
materialize(roomId: string, attachmentId: string): Promise<{
|
|
22
|
+
localPath: string;
|
|
23
|
+
downloaded: true;
|
|
24
|
+
cached: boolean;
|
|
25
|
+
name: string;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
byteSize: string;
|
|
28
|
+
sha256: string;
|
|
29
|
+
}>;
|
|
30
|
+
prune(preservePath?: string): void;
|
|
31
|
+
private root;
|
|
32
|
+
private entry;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import h from"node:crypto";import i from"node:fs";import l from"node:path";import{resolveShennianPath as $}from"../config/index.js";import{PublicRoomClientError as x}from"./public-room-client.js";const z=100*1024*1024,j=2*1024*1024*1024,k=720*60*6e4;class X{options;constructor(e){this.options=e}lookup(e,n){try{const o=this.entry(e,n.attachmentId),s=T(o.directory);if(!C(s,n))return{downloaded:!1,localPath:null};const a=l.join(o.directory,s.safeName);return H(a,Number(n.byteSize),n.sha256)?(P(o.directory,{...s,lastAccessedAt:new Date().toISOString()}),{downloaded:!0,localPath:a}):{downloaded:!1,localPath:null}}catch{return{downloaded:!1,localPath:null}}}async materialize(e,n){const o=await this.options.client.getAttachmentDownload(e,n),s=E(o,n),a=this.lookup(e,s);if(a.downloaded&&a.localPath)return this.prune(a.localPath),{...s,localPath:a.localPath,downloaded:!0,cached:!0};const y=this.entry(e,n);D(y.directory);const r=F(s.name),c=l.join(y.directory,r),f=l.join(y.directory,`.download-${process.pid}-${h.randomBytes(8).toString("hex")}`),p=Number(s.byteSize);try{const w=await this.options.client.download(String(o.downloadUrl)),b=w.headers.get("content-length");b&&Number(b)!==p&&d("attachment_size_mismatch");const u=i.openSync(f,"wx",384),A=h.createHash("sha256");let m=0;try{const g=w.body.getReader();for(;;){const S=await g.read();if(S.done)break;m+=S.value.byteLength,(m>p||m>z)&&(await g.cancel(),d("attachment_size_mismatch"));const _=Buffer.from(S.value);i.writeSync(u,_),A.update(_)}i.fsyncSync(u)}finally{i.closeSync(u)}return m!==p&&d("attachment_size_mismatch"),A.digest("hex")!==s.sha256&&d("attachment_hash_mismatch"),i.renameSync(f,c),process.platform!=="win32"&&i.chmodSync(c,384),P(y.directory,{schemaVersion:1,attachmentId:n,safeName:r,mimeType:s.mimeType,byteSize:s.byteSize,sha256:s.sha256,lastAccessedAt:new Date().toISOString()}),this.prune(c),{...s,localPath:c,downloaded:!0,cached:!1}}finally{try{i.unlinkSync(f)}catch{}}}prune(e){const n=this.root(),o=M(n),s=Date.now()-(this.options.maxCacheAgeMs??k),a=this.options.maxCacheBytes??j;let y=o.reduce((r,c)=>r+c.byteSize,0);for(const r of o.sort((c,f)=>c.lastAccessedAt-f.lastAccessedAt))e&&l.resolve(r.filePath)===l.resolve(e)||r.lastAccessedAt>=s&&y<=a||(i.rmSync(r.directory,{recursive:!0,force:!0}),y-=r.byteSize)}root(){return this.options.rootDirectory??$("room-attachments")}entry(e,n){const o=h.createHash("sha256").update(`room-context-v1\0${e}`,"utf8").digest("hex"),s=h.createHash("sha256").update(`attachment-v1\0${n}`,"utf8").digest("hex");return{contextHash:o,attachmentHash:s,directory:l.join(this.root(),o,s)}}}function E(t,e){if(t.attachmentId!==e||typeof t.name!="string"||typeof t.mimeType!="string"||typeof t.byteSize!="string"||!/^(0|[1-9]\d{0,15})$/.test(t.byteSize)||Number(t.byteSize)>z||typeof t.sha256!="string"||!/^[a-f0-9]{64}$/.test(t.sha256)||typeof t.downloadUrl!="string"||typeof t.expiresAt!="string"||Date.parse(t.expiresAt)<=Date.now())throw new x("invalid_attachment_capability","Attachment capability is invalid.");let n;try{n=new URL(t.downloadUrl)}catch{d("invalid_attachment_capability")}return(n.protocol!=="https:"||n.username||n.password||n.hash)&&d("invalid_attachment_capability"),{attachmentId:e,name:t.name,mimeType:t.mimeType,byteSize:t.byteSize,sha256:t.sha256}}function F(t){const e=t.replace(/[\\/\0-\x1f\x7f:]/g,"_").slice(0,255).trim();return(!e||e==="."||e==="..")&&d("unsafe_attachment_name"),e}function D(t){i.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&i.chmodSync(t,448)}function H(t,e,n){try{const o=i.lstatSync(t);return o.isFile()&&!o.isSymbolicLink()&&o.size===e&&h.createHash("sha256").update(i.readFileSync(t)).digest("hex")===n}catch{return!1}}function T(t){const e=l.join(t,"metadata.json"),n=i.lstatSync(e);if(!n.isFile()||n.isSymbolicLink())throw new Error("unsafe metadata");return JSON.parse(i.readFileSync(e,"utf8"))}function P(t,e){D(t);const n=l.join(t,"metadata.json"),o=`${n}.tmp-${process.pid}-${h.randomBytes(6).toString("hex")}`;try{i.writeFileSync(o,`${JSON.stringify(e)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),i.renameSync(o,n),process.platform!=="win32"&&i.chmodSync(n,384)}finally{try{i.unlinkSync(o)}catch{}}}function C(t,e){return t.schemaVersion===1&&t.attachmentId===e.attachmentId&&t.byteSize===e.byteSize&&t.sha256===e.sha256&&t.mimeType===e.mimeType&&Number.isFinite(Date.parse(t.lastAccessedAt))}function M(t){if(!N(t))return[];const e=[];for(const n of i.readdirSync(t)){if(!/^[a-f0-9]{64}$/.test(n))continue;const o=l.join(t,n);if(N(o))for(const s of i.readdirSync(o)){if(!/^[a-f0-9]{64}$/.test(s))continue;const a=l.join(o,s);try{const y=T(a),r=l.join(a,y.safeName),c=i.lstatSync(r);if(!c.isFile()||c.isSymbolicLink())continue;e.push({directory:a,filePath:r,byteSize:c.size,lastAccessedAt:Date.parse(y.lastAccessedAt)})}catch{}}}return e}function N(t){try{const e=i.lstatSync(t);return e.isDirectory()&&!e.isSymbolicLink()}catch{return!1}}function d(t){throw new x(t,"Attachment download validation failed.")}export{X as PublicAttachmentCache};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare class PublicRoomClientError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly nextAction: Record<string, string> | null;
|
|
4
|
+
constructor(code: string, message: string, nextAction?: Record<string, string> | null);
|
|
5
|
+
}
|
|
6
|
+
export declare class PublicRoomClient {
|
|
7
|
+
private readonly fetchImpl;
|
|
8
|
+
constructor(fetchImpl?: typeof fetch);
|
|
9
|
+
create(name: string): Promise<Record<string, unknown>>;
|
|
10
|
+
join(inviteToken: string): Promise<Record<string, unknown>>;
|
|
11
|
+
status(roomId: string): Promise<Record<string, unknown>>;
|
|
12
|
+
read(roomId: string, input: {
|
|
13
|
+
before?: string;
|
|
14
|
+
after?: string;
|
|
15
|
+
limit: number;
|
|
16
|
+
}): Promise<Record<string, unknown>>;
|
|
17
|
+
send(roomId: string, input: {
|
|
18
|
+
clientMessageId: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
attachmentIds?: string[];
|
|
21
|
+
}): Promise<Record<string, unknown>>;
|
|
22
|
+
initializeAttachment(roomId: string, input: {
|
|
23
|
+
clientUploadId: string;
|
|
24
|
+
name: string;
|
|
25
|
+
mediaKind: 'image' | 'audio' | 'file';
|
|
26
|
+
mimeType: string;
|
|
27
|
+
byteSize: number;
|
|
28
|
+
sha256: string;
|
|
29
|
+
}): Promise<Record<string, unknown>>;
|
|
30
|
+
completeAttachment(roomId: string, attachmentId: string): Promise<Record<string, unknown>>;
|
|
31
|
+
getAttachmentDownload(roomId: string, attachmentId: string): Promise<Record<string, unknown>>;
|
|
32
|
+
leave(roomId: string): Promise<Record<string, unknown>>;
|
|
33
|
+
upload(url: string, headers: Record<string, string>, data: Buffer): Promise<void>;
|
|
34
|
+
download(url: string): Promise<Response>;
|
|
35
|
+
private request;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadConfig as w}from"../config/index.js";import{SERVERS as y}from"../region.js";class r extends Error{code;nextAction;constructor(e,t,n=null){super(t),this.code=e,this.nextAction=n}}class b{fetchImpl;constructor(e=fetch){this.fetchImpl=e}create(e){return this.request("POST","/api/room-cli/create",{name:e})}join(e){return this.request("POST","/api/room-cli/join",{inviteToken:e})}status(e){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/status`)}read(e,t){const n=new URLSearchParams({limit:String(t.limit)});return t.before&&n.set("before",t.before),t.after&&n.set("after",t.after),this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/messages?${n}`)}send(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/messages`,t,{retryNetworkOnce:!0})}initializeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/init`,t)}completeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/complete`,{})}getAttachmentDownload(e,t){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/download`)}leave(e){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/leave`,{})}async upload(e,t,n){let s;try{s=await this.fetchImpl(e,{method:"PUT",headers:t,body:n,redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new r("attachment_upload_failed","Attachment upload failed.")}if(!s.ok)throw new r("attachment_upload_failed","Attachment upload failed.")}async download(e){try{const t=await this.fetchImpl(e,{redirect:"error",signal:AbortSignal.timeout(6e4)});if(!t.ok||!t.body)throw new r("attachment_download_failed","Attachment download failed.");return t}catch(t){throw t instanceof r?t:new r("attachment_download_failed","Attachment download failed.")}}async request(e,t,n,s={}){const l=w();if(!l.machineToken)throw new r("not_paired","Shennian CLI is not paired.",{type:"run_command",command:"shennian pair --browser"});const d=(l.serverUrl??y.cn.url).replace(/\/+$/,""),h=()=>this.fetchImpl(`${d}${t}`,{method:e,headers:{authorization:`Bearer ${l.machineToken}`,...n==null?{}:{"content-type":"application/json"}},...n==null?{}:{body:JSON.stringify(n)},signal:AbortSignal.timeout(2e4)});let i;try{i=await h()}catch{if(!s.retryNetworkOnce)throw new r("server_unavailable","Shennian service is unavailable.");try{i=await h()}catch{throw new r("server_unavailable","Shennian service is unavailable.")}}const c=await S(i);if(!i.ok){const a=m(c)?c:{},u=typeof a.error=="string"?a.error:`http_${i.status}`,p=typeof a.message=="string"?a.message:"Room request failed.",f=g(a.nextAction)?a.nextAction:null;throw new r(u,p,f)}if(!m(c))throw new r("invalid_server_response","Shennian returned an invalid Room response.");return c}}function g(o){return m(o)&&Object.values(o).every(e=>typeof e=="string")}async function S(o){try{return await o.json()}catch{return null}}function m(o){return!!o&&typeof o=="object"&&!Array.isArray(o)}export{b as PublicRoomClient,r as PublicRoomClientError};
|
|
@@ -19,6 +19,7 @@ export type RemoteRoomAgentStatus = {
|
|
|
19
19
|
agent?: {
|
|
20
20
|
displayName: string;
|
|
21
21
|
description: string;
|
|
22
|
+
systemPrompt?: string;
|
|
22
23
|
};
|
|
23
24
|
room: {
|
|
24
25
|
name: string;
|
|
@@ -189,7 +190,8 @@ export declare class HttpRoomActionClient {
|
|
|
189
190
|
idempotent: boolean;
|
|
190
191
|
}>;
|
|
191
192
|
read(target: BindingTarget & {
|
|
192
|
-
|
|
193
|
+
beforeSequence?: string;
|
|
194
|
+
afterSequence?: string;
|
|
193
195
|
limit: number;
|
|
194
196
|
}): Promise<{
|
|
195
197
|
messages: RoomMessageView[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import S from"node:path";class c extends Error{code;constructor(e,o){super(o),this.code=e,this.name="RoomActionHttpError"}}class T{fetchImpl;baseUrl;constructor(e,o=fetch){this.fetchImpl=o,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(o);if(!o.ok)throw y(o.status,t);return A(t,e,this.baseUrl)}async resolveNativeSession(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(o);if(!o.ok)throw y(o.status,t);(!t||typeof t!="object")&&r();const n=t;return(typeof n.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(n.nianSessionId)||typeof n.created!="boolean")&&r(),n}async createRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(o);if(!o.ok)throw y(o.status,t);return j(t,e,this.baseUrl)}async joinRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(o);if(!o.ok)throw y(o.status,t);return _(t,e,this.baseUrl)}async status(e){const o=await this.request(e,"status");return w(o,e)}async getAttachmentCapability(e){const o=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!o||typeof o!="object")&&r();const t=o;return(t.attachmentId!==e.attachmentId||t.roomId!==e.roomId||typeof t.safeName!="string"||!b(t.safeName)||typeof t.mimeType!="string"||t.mimeType.length<1||t.mimeType.length>191||typeof t.byteSize!="string"||!d(t.byteSize)||typeof t.sha256!="string"||!/^[a-f0-9]{64}$/.test(t.sha256)||!g(t.downloadUrl)||typeof t.expiresAt!="string"||!Number.isFinite(Date.parse(t.expiresAt)))&&r(),t}async initializeAttachment(e){const o=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!o||typeof o!="object")&&r();const t=o,n=t.attachment,a=t.upload;return(!n||typeof n.id!="string"||n.roomId!==e.roomId||typeof n.name!="string"||!b(n.name)||n.mimeType!==e.mimeType||n.byteSize!==String(e.byteSize)||n.sha256!==e.sha256||n.status!=="pending"&&n.status!=="uploaded"||!a||a.method!=="PUT"||!g(a.url)||!a.headers||typeof a.headers!="object"||a.headers["content-type"]!==e.mimeType||a.headers["x-oss-meta-sha256"]!==e.sha256||typeof a.expiresAt!="string"||!Number.isFinite(Date.parse(a.expiresAt))||Date.parse(a.expiresAt)<=Date.now()||typeof t.idempotent!="boolean")&&r(),t}async completeAttachment(e){const o=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!o||typeof o!="object")&&r();const t=o;typeof t.idempotent!="boolean"&&r();const n=t.attachment;return(!n||n.attachmentId!==e.attachmentId||n.mediaKind!=="image"&&n.mediaKind!=="audio"&&n.mediaKind!=="file"||typeof n.name!="string"||typeof n.mimeType!="string"||typeof n.byteSize!="string"||!d(n.byteSize)||typeof n.sha256!="string"||!/^[a-f0-9]{64}$/.test(n.sha256)||n.status!=="ready")&&r(),{attachment:n,idempotent:t.idempotent}}async read(e){const o=new URLSearchParams({after:e.afterSequence,limit:String(e.limit)}),t=await this.request(e,`messages?${o}`);(!t||typeof t!="object")&&r();const n=t;return(!Array.isArray(n.messages)||typeof n.nextSequence!="string"||!d(n.nextSequence)||typeof n.hasMore!="boolean"||typeof n.historyFromSequence!="string"||!d(n.historyFromSequence)||typeof n.roomName!="string")&&r(),{messages:n.messages.map(s=>u(s,e.roomId)),nextSequence:n.nextSequence,hasMore:n.hasMore,historyFromSequence:n.historyFromSequence,roomName:n.roomName}}async send(e){const o=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!o||typeof o!="object")&&r();const t=o;return typeof t.idempotent!="boolean"&&r(),{message:u(t.message,e.roomId),idempotent:t.idempotent}}async mode(e){const o=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!o||typeof o!="object")&&r();const t=o;return(t.bindingId!==e.bindingId||t.roomId!==e.roomId||t.status!=="active"&&t.status!=="paused"||!I(t.activationMode)||t.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}async request(e,o,t={}){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${o}`,{...t,headers:{authorization:`Bearer ${e.machineToken}`,...t.body?{"content-type":"application/json"}:{}},signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let a=`room_action_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(a=s.error)}catch{}throw new c(a,"Shennian Room action failed.")}try{return await n.json()}catch{r()}}}function w(i,e){(!i||typeof i!="object")&&r();const o=i,t=o.room,n=o.agent;(o.bindingId!==e.bindingId||o.roomId!==e.roomId||typeof o.agentIdentityId!="string"||o.agentMembershipId!==null&&typeof o.agentMembershipId!="string"||o.status!=="pending_approval"&&o.status!=="active"&&o.status!=="paused"&&o.status!=="detached"||!I(o.activationMode)||o.activationCapability!==void 0&&o.activationCapability!=="managed"&&o.activationCapability!=="idle_resume"&&o.activationCapability!=="notify_only"||o.managedWorkspacePath!==void 0&&o.managedWorkspacePath!==null&&(typeof o.managedWorkspacePath!="string"||o.managedWorkspacePath.length>4096||!S.isAbsolute(o.managedWorkspacePath))||typeof o.lastDeliveredSequence!="string"||!d(o.lastDeliveredSequence)||typeof o.lastActivatedSequence!="string"||!d(o.lastActivatedSequence)||!t||typeof t!="object"||n!==void 0&&(!n||typeof n!="object"))&&r(),(o.status==="pending_approval"&&o.agentMembershipId!==null||(o.status==="active"||o.status==="paused")&&typeof o.agentMembershipId!="string")&&r();const a=t,s=n,m=a.members;return(s!==void 0&&(typeof s.displayName!="string"||!s.displayName.trim()||typeof s.description!="string")||typeof a.name!="string"||typeof a.status!="string"||a.memberCount!==void 0&&(typeof a.memberCount!="number"||!Number.isInteger(a.memberCount)||a.memberCount<0)||typeof a.lastSequence!="string"||!d(a.lastSequence)||m!==void 0&&(!Array.isArray(m)||m.some(l=>{if(!l||typeof l!="object")return!0;const f=l;return typeof f.displayName!="string"||f.principalType!=="human"&&f.principalType!=="agent"})))&&r(),o}function u(i,e){(!i||typeof i!="object")&&r();const o=i;if((typeof o.id!="string"||o.roomId!==e||typeof o.sequence!="string"||!d(o.sequence)||!o.author||typeof o.author.membershipId!="string"||!Array.isArray(o.attachments)||typeof o.createdAt!="string")&&r(),o.activationCause!=null){const t=o.activationCause;(typeof t.causeMessageId!="string"||typeof t.rootCauseMessageId!="string"||!Number.isInteger(t.hopCount)||t.hopCount<1||t.hopCount>32)&&r()}return o}function j(i,e,o){(!i||typeof i!="object")&&r();const t=i,n=t.room,a=t.agentIdentity,s=t.binding;return(t.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!h(n.webUrl,o,`/spaces/${n.id}`)||!M(n.inviteUrl,o)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}function A(i,e,o){(!i||typeof i!="object")&&r();const t=i,n=t.room;return(t.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!h(n.webUrl,o,`/spaces/${n.id}`))&&r(),t}function _(i,e,o){(!i||typeof i!="object")&&r();const t=i,n=t.room,a=t.agentIdentity,s=t.binding;return(t.outcome!=="joined"&&t.outcome!=="pending_approval"||!n||typeof n.id!="string"||typeof n.name!="string"||!h(n.webUrl,o,`/spaces/${n.id}`)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),(t.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof t.joinRequestId!="string")||t.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||t.joinRequestId!==void 0))&&r(),t}function d(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function b(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function g(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function I(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function y(i,e){let o=`room_action_http_${i}`;if(e&&typeof e=="object"){const t=e.error;typeof t=="string"&&/^[a-z0-9_-]{1,80}$/.test(t)&&(o=t)}return new c(o,"Shennian Room action failed.")}function h(i,e,o){return v(i,e,t=>t.pathname===o)}function M(i,e){return v(i,e,o=>/^\/spaces\/join\/[A-Za-z0-9_-]{43}$/.test(o.pathname))}function v(i,e,o){if(typeof i!="string")return!1;try{const t=new URL(i),n=new URL(e),a=t.origin===n.origin,s=n.protocol==="http:"&&t.protocol==="http:"&&t.hostname===n.hostname,m=t.protocol==="https:"&&t.port===""&&(t.hostname==="shennian.net"||t.hostname==="app.shennian.net");return(a||s||m)&&!t.username&&!t.password&&!t.search&&!t.hash&&o(t)}catch{return!1}}export{T as HttpRoomActionClient,c as RoomActionHttpError};
|
|
1
|
+
import S from"node:path";class c extends Error{code;constructor(e,t){super(t),this.code=e,this.name="RoomActionHttpError"}}class R{fetchImpl;baseUrl;constructor(e,t=fetch){this.fetchImpl=t,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return _(o,e,this.baseUrl)}async resolveNativeSession(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);(!o||typeof o!="object")&&r();const n=o;return(typeof n.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(n.nianSessionId)||typeof n.created!="boolean")&&r(),n}async createRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return j(o,e,this.baseUrl)}async joinRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return A(o,e,this.baseUrl)}async status(e){const t=await this.request(e,"status");return w(t,e)}async getAttachmentCapability(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!t||typeof t!="object")&&r();const o=t;return(o.attachmentId!==e.attachmentId||o.roomId!==e.roomId||typeof o.safeName!="string"||!b(o.safeName)||typeof o.mimeType!="string"||o.mimeType.length<1||o.mimeType.length>191||typeof o.byteSize!="string"||!d(o.byteSize)||typeof o.sha256!="string"||!/^[a-f0-9]{64}$/.test(o.sha256)||!g(o.downloadUrl)||typeof o.expiresAt!="string"||!Number.isFinite(Date.parse(o.expiresAt)))&&r(),o}async initializeAttachment(e){const t=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!t||typeof t!="object")&&r();const o=t,n=o.attachment,a=o.upload;return(!n||typeof n.id!="string"||n.roomId!==e.roomId||typeof n.name!="string"||!b(n.name)||n.mimeType!==e.mimeType||n.byteSize!==String(e.byteSize)||n.sha256!==e.sha256||n.status!=="pending"&&n.status!=="uploaded"||!a||a.method!=="PUT"||!g(a.url)||!a.headers||typeof a.headers!="object"||a.headers["content-type"]!==e.mimeType||a.headers["x-oss-meta-sha256"]!==e.sha256||typeof a.expiresAt!="string"||!Number.isFinite(Date.parse(a.expiresAt))||Date.parse(a.expiresAt)<=Date.now()||typeof o.idempotent!="boolean")&&r(),o}async completeAttachment(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!t||typeof t!="object")&&r();const o=t;typeof o.idempotent!="boolean"&&r();const n=o.attachment;return(!n||n.attachmentId!==e.attachmentId||n.mediaKind!=="image"&&n.mediaKind!=="audio"&&n.mediaKind!=="file"||typeof n.name!="string"||typeof n.mimeType!="string"||typeof n.byteSize!="string"||!d(n.byteSize)||typeof n.sha256!="string"||!/^[a-f0-9]{64}$/.test(n.sha256)||n.status!=="ready")&&r(),{attachment:n,idempotent:o.idempotent}}async read(e){if(e.beforeSequence&&e.afterSequence)throw new c("ambiguous_sequence_cursor","beforeSequence and afterSequence are mutually exclusive.");const t=new URLSearchParams({limit:String(e.limit)});e.beforeSequence&&t.set("before",e.beforeSequence),e.afterSequence&&t.set("after",e.afterSequence);const o=await this.request(e,`messages?${t}`);(!o||typeof o!="object")&&r();const n=o;return(!Array.isArray(n.messages)||typeof n.nextSequence!="string"||!d(n.nextSequence)||typeof n.hasMore!="boolean"||typeof n.historyFromSequence!="string"||!d(n.historyFromSequence)||typeof n.roomName!="string")&&r(),{messages:n.messages.map(s=>h(s,e.roomId)),nextSequence:n.nextSequence,hasMore:n.hasMore,historyFromSequence:n.historyFromSequence,roomName:n.roomName}}async send(e){const t=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!t||typeof t!="object")&&r();const o=t;return typeof o.idempotent!="boolean"&&r(),{message:h(o.message,e.roomId),idempotent:o.idempotent}}async mode(e){const t=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!t||typeof t!="object")&&r();const o=t;return(o.bindingId!==e.bindingId||o.roomId!==e.roomId||o.status!=="active"&&o.status!=="paused"||!I(o.activationMode)||o.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),o}async request(e,t,o={}){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${t}`,{...o,headers:{authorization:`Bearer ${e.machineToken}`,...o.body?{"content-type":"application/json"}:{}},signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let a=`room_action_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(a=s.error)}catch{}throw new c(a,"Shennian Room action failed.")}try{return await n.json()}catch{r()}}}function w(i,e){(!i||typeof i!="object")&&r();const t=i,o=t.room,n=t.agent;(t.bindingId!==e.bindingId||t.roomId!==e.roomId||typeof t.agentIdentityId!="string"||t.agentMembershipId!==null&&typeof t.agentMembershipId!="string"||t.status!=="pending_approval"&&t.status!=="active"&&t.status!=="paused"&&t.status!=="detached"||!I(t.activationMode)||t.activationCapability!==void 0&&t.activationCapability!=="managed"&&t.activationCapability!=="idle_resume"&&t.activationCapability!=="notify_only"||t.managedWorkspacePath!==void 0&&t.managedWorkspacePath!==null&&(typeof t.managedWorkspacePath!="string"||t.managedWorkspacePath.length>4096||!S.isAbsolute(t.managedWorkspacePath))||typeof t.lastDeliveredSequence!="string"||!d(t.lastDeliveredSequence)||typeof t.lastActivatedSequence!="string"||!d(t.lastActivatedSequence)||!o||typeof o!="object"||n!==void 0&&(!n||typeof n!="object"))&&r(),(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&typeof t.agentMembershipId!="string")&&r();const a=o,s=n,m=a.members;return(s!==void 0&&(typeof s.displayName!="string"||!s.displayName.trim()||typeof s.description!="string")||typeof a.name!="string"||typeof a.status!="string"||a.memberCount!==void 0&&(typeof a.memberCount!="number"||!Number.isInteger(a.memberCount)||a.memberCount<0)||typeof a.lastSequence!="string"||!d(a.lastSequence)||m!==void 0&&(!Array.isArray(m)||m.some(f=>{if(!f||typeof f!="object")return!0;const l=f;return typeof l.displayName!="string"||l.principalType!=="human"&&l.principalType!=="agent"})))&&r(),t}function h(i,e){(!i||typeof i!="object")&&r();const t=i;if((typeof t.id!="string"||t.roomId!==e||typeof t.sequence!="string"||!d(t.sequence)||!t.author||typeof t.author.membershipId!="string"||!Array.isArray(t.attachments)||typeof t.createdAt!="string")&&r(),t.activationCause!=null){const o=t.activationCause;(typeof o.causeMessageId!="string"||typeof o.rootCauseMessageId!="string"||!Number.isInteger(o.hopCount)||o.hopCount<1||o.hopCount>32)&&r()}return t}function j(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room,a=o.agentIdentity,s=o.binding;return(o.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!u(n.webUrl,t,`/spaces/${n.id}`)||!q(n.inviteUrl,t)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),o}function _(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room;return(o.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!u(n.webUrl,t,`/spaces/${n.id}`))&&r(),o}function A(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room,a=o.agentIdentity,s=o.binding;return(o.outcome!=="joined"&&o.outcome!=="pending_approval"||!n||typeof n.id!="string"||typeof n.name!="string"||!u(n.webUrl,t,`/spaces/${n.id}`)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||s.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),(o.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof o.joinRequestId!="string")||o.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||o.joinRequestId!==void 0))&&r(),o}function d(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function b(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function g(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function I(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function y(i,e){let t=`room_action_http_${i}`;if(e&&typeof e=="object"){const o=e.error;typeof o=="string"&&/^[a-z0-9_-]{1,80}$/.test(o)&&(t=o)}return new c(t,"Shennian Room action failed.")}function u(i,e,t){return v(i,e,o=>o.pathname===t)}function q(i,e){return v(i,e,t=>/^\/spaces\/join\/[A-Za-z0-9_-]{43}$/.test(t.pathname))}function v(i,e,t){if(typeof i!="string")return!1;try{const o=new URL(i),n=new URL(e),a=o.origin===n.origin,s=n.protocol==="http:"&&o.protocol==="http:"&&o.hostname===n.hostname,m=o.protocol==="https:"&&o.port===""&&(o.hostname==="shennian.net"||o.hostname==="app.shennian.net");return(a||s||m)&&!o.username&&!o.password&&!o.search&&!o.hash&&t(o)}catch{return!1}}export{R as HttpRoomActionClient,c as RoomActionHttpError};
|
|
@@ -36,7 +36,14 @@ export type RoomActivationQueueItem = {
|
|
|
36
36
|
enqueuedAt: string;
|
|
37
37
|
attempts: number;
|
|
38
38
|
deliveryText?: string;
|
|
39
|
+
/** @deprecated compatibility-read only; new activations keep prompt bodies in daemon memory. */
|
|
39
40
|
systemPrompt?: string;
|
|
41
|
+
promptSnapshot?: {
|
|
42
|
+
managedRoomPolicyVersion: string;
|
|
43
|
+
sessionModeInstructionsHash: string | null;
|
|
44
|
+
agentSystemPromptHash: string | null;
|
|
45
|
+
trustedRoomContextHash: string;
|
|
46
|
+
};
|
|
40
47
|
dispatchState?: 'dispatching' | 'interrupted';
|
|
41
48
|
dispatchedAt?: string;
|
|
42
49
|
interruptedAt?: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import _ from"node:crypto";import u from"node:fs";import R from"node:path";import{resolveShennianPath as C}from"../config/index.js";const y=500,A=20,M=/^[A-Za-z0-9_.:@/-]{1,512}$/,T=new Set(["codex","claude_code","workbuddy"]),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 K{filePath;state;mutexTails=new Map;constructor(e=C("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"),m(e.runtimeAdapter),w(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),
|
|
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 p(t){return c(t.machineId,"machine_id"),m(t.runtimeAdapter),w(t.sourceSessionKey),_.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!==p(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"),m(t.runtimeAdapter),w(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.");
|
|
1
|
+
import _ from"node:crypto";import u from"node:fs";import R from"node:path";import{resolveShennianPath as C}from"../config/index.js";const y=500,A=20,M=/^[A-Za-z0-9_.:@/-]{1,512}$/,T=new Set(["codex","claude_code","workbuddy"]),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 K{filePath;state;mutexTails=new Map;constructor(e=C("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"),m(e.runtimeAdapter),w(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),P(e.capabilities);const i=p(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[p(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);v(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);f(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(q=>q.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=R.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${_.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 p(t){return c(t.machineId,"machine_id"),m(t.runtimeAdapter),w(t.sourceSessionKey),_.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!==p(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"),m(t.runtimeAdapter),w(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&&E(t.binding)}function E(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(v(t.lastDeliveredSequence),v(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>y)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(f),t.activeActivation!=null&&f(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(v(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function f(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),v(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&&!O(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 O(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 m(t){if(!T.has(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function w(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(!M.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function v(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{K as RoomSessionRegistry,o as RoomSessionRegistryError,p as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { AgentSendContext } from '../../agents/adapter.js';
|
|
2
|
-
import { type ReqFrame, type AgentType } from '@shennian/wire';
|
|
2
|
+
import { type ReqFrame, type AgentType, type SessionMode } from '@shennian/wire';
|
|
3
3
|
import type { SessionManagerRuntime } from '../types.js';
|
|
4
4
|
export declare function sendSessionUpdateEvent(runtime: SessionManagerRuntime, input: {
|
|
5
5
|
sessionId: string;
|
|
6
6
|
agentType: AgentType;
|
|
7
|
+
sessionMode?: SessionMode;
|
|
8
|
+
managerConfig?: import('@shennian/wire').ManagerConfig | null;
|
|
7
9
|
workDir: string;
|
|
8
10
|
agentSessionId?: string | null;
|
|
9
11
|
modelId?: string;
|
|
12
|
+
managerDefaultWorkerAgentType?: AgentType | null;
|
|
13
|
+
managerDefaultWorkerModelId?: string | null;
|
|
10
14
|
}): void;
|
|
11
15
|
export declare function handleChatSend(runtime: SessionManagerRuntime, req: ReqFrame, context?: AgentSendContext): Promise<void>;
|
|
12
16
|
export declare function handleChatAbort(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),r=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,r)}function re(e,t,r={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq},o=te(e.args);return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:a,...e.name?{name:e.name}:{},...o!==void 0?{argsSummary:o}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...r}}return{...e,sessionId:t,...r}}const se=3e4;function oe(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 de(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function W(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function L(e,t,r){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:r.agentType,agentSessionId:r.agentSessionId??null,modelId:r.modelId??null,workDir:r.workDir,status:"active",externalChannel:null}}})}function ie(e,t){O({sessionId:t.sessionId,agentType:t.agentType,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function H(e){return Z(e)}function U(e,t,r){return{}}function j(e,t,r,a,o,l){e.configure?.({sessionId:t,externalChannel:a??null,env:{...H(r),...U(t,a,o),...l?{SHENNIAN_ROOM_REGISTRY_KEY:l}:{}}})}function le(e,t,r){if(e!=="claude"||!r)return t;const a=t.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?G(r)??t:t}function ce(e,t,r,a){let o=null;function l(n,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:re(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 i(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 f(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{i(n)},se),n.heartbeatTimer.unref?.())}function y(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)}a.on("agentEvent",n=>{const s=e.sessions.get(t),S=oe(n),w=n.state==="final"||n.state==="error"||n.state==="aborted";if(s&&(s.nextEventSeq=n.seq+1,w||(s.currentRunId=n.runId),!w&&S&&(s.currentRunPhase=S,f(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&T({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:r}}),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 m=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(m,(e.runTextAcc.get(m)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,W(r,n.source),n.agentSessionId),s&&O({sessionId:t,agentType:r,workDir:s.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:r,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const I=n.text??"";if(!I)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==n.runId&&y(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=I,s.pendingTextEvent.seq=n.seq);return}let R={};if(n.state==="final"){y(s);const I=e.runTextAcc.get(m)??"";I&&(R={messageSummary:ae(I)}),e.runTextAcc.delete(m)}else n.state==="error"||n.state==="aborted"?(y(s),e.runTextAcc.delete(m)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&y(s);w&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),e.chatQueue?.noteTerminal(t)),l(n,R)}),a.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${r}: ${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 ue(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const r=e.processedReqIds.values().next().value;e.processedReqIds.delete(r)}}async function P(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function B(e,t,r,a,o,l,g){return g?e.send(t,r,a,o,l,g):l?.trim()?e.send(t,r,a,o,l):o?.length?e.send(t,r,a,o):a?e.send(t,r,a):e.send(t,r)}async function ge(e,t,r,a,o,l,g){e.evictIdleSessions();const i=Q(r);if(!i)throw new Error(`Unsupported agent: ${r}`);j(i,t,r,l,g),await i.start(t,a,o);const f={adapter:i,workDir:a,agentType:r,agentSessionId:o??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:l??null,externalReplyTarget:g??null,externalChannelEnv:{...H(r),...U(t,l,g)}};return e.sessions.set(t,f),ce(e,t,r,i),f}function fe(e,t){const r=e.sessions.get(t),a=r?.currentRunId;if(!r||!a)return;const o=r.nextEventSeq;e.runTextAcc.delete(`${t}:${a}`),r.pendingTextEvent=null,r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,r.heartbeatTimer&&(clearInterval(r.heartbeatTimer),r.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:a,seq:o},seq:o,id:`agent-evt-${a}-${o}`}),e.chatQueue?.noteTerminal(t)}async function Re(e,t,r){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}ue(e,t.id);const{sessionId:a,text:o,agentType:l,workDir:g,agentSessionId:i,modelId:f,systemPrompt:y,managerDefaultWorkerAgentType:n,managerDefaultWorkerModelId:s,reasoningEffort:S,clientMessageId:w,sessionListProjection:m,waitForDispatch:R,responseId:I,roomContextRegistryKey:$,suppressExternalOwnerEcho:J}=t.params,A=I||t.id;X(m);const b=null,M="";if(!a||!o){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:A,ok:!1,error:"sessionId and text are required"});return}const u=l;u==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,n??null,s??null);const q=(u==="claude"||u==="codex"||u==="workbuddy")&&typeof S=="string"&&S.trim()?S.trim():void 0,E=e.resolvePath(le(u,g||F.homedir(),i)||F.homedir()),D=E,_=ne(t.params.attachments),p=_?.length?await ee({text:o,attachments:_,workDir:E}):{text:o,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==u||d.workDir!==E||JSON.stringify(d.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(a);try{await P(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(a);try{await P(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}}if(!d)try{d=await ge(e,a,u,E,i,b,M)}catch(c){const h=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${l}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${a} agentType=${l} workDir=${E} agentSessionId=${i??""}: ${h}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:h,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:A,ok:!1,error:h});return}$&&/^[a-f0-9]{64}$/.test($)&&j(d.adapter,a,u,b,M,$);const C={id:w??`user-${t.id}`,sessionId:a,role:"user",ts:Date.now(),payload:V(p.text,p.attachments)};T({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:u,modelId:f,reasoningEffort:q}});const z=c=>{ie(e,{sessionId:a,agentType:u,workDir:D,agentSessionId:d.agentSessionId??i??null,modelId:f}),d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:a,agentType:u,sourceAgentType:W(u,f),canonicalMessageId:w??null,sourceSessionKey:d.agentSessionId??i??null,text:p.text,managedEchoPolicy:c?.deliveryMode==="external_owner_queue"&&!J?"import_following":"suppress_following"}),x(a,C),L(e,C,{agentType:u,workDir:D,agentSessionId:d.agentSessionId??i??null,modelId:f})},N=async(c,h)=>{const k=de(u,c);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${a} agentType=${l} workDir=${E} agentSessionId=${d.agentSessionId??i??""}: ${k}`),e.sessions.delete(a);try{await P(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:k,runId:"",seq:0}}),!h){const v={id:`agent-error-${t.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:k};x(a,v),L(e,v,{agentType:u,workDir:D,agentSessionId:d.agentSessionId??i??null,modelId:f})}h&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:A,ok:!1,error:k}))};if(R){let c;try{const h=p.attachments;c=await B(d.adapter,p.text,f,q,h,y,r),T({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(h){await N(h,!0);return}z(c??void 0),e.client.sendRes({type:"res",id:A,ok:!0,...p.localized||c?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...c?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:c.deliveryMode,queuedSubmissionId:c.queuedSubmissionId}:{}}}:{}}),T({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}z(),e.client.sendRes({type:"res",id:A,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),T({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const K=p.attachments;B(d.adapter,p.text,f,q,K,y,r).then(()=>{T({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(c=>{N(c,!1)})}async function ke(e,t){const{sessionId:r}=t.params,a=e.sessions.get(r);if(a){try{await a.adapter.stop()}catch{}fe(e,r),e.activityPublisher?.publish(r,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{ke as handleChatAbort,Re as handleChatSend,ie as sendSessionUpdateEvent};
|
|
1
|
+
import v 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 w}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as te}from"../../native-fusion/parsers.js";import{appendMessage as E,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:"",l=typeof r.name=="string"?r.name:"",g=typeof r.mimeType=="string"?r.mimeType:"";if(!o||!l||!g)return null;const f=typeof r.previewData=="string"&&r.previewData.trim()?r.previewData.trim():void 0;return{path:o,name:l,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,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 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:l,sessionMode:g,managerConfig:f,workDir:y,agentSessionId:i,modelId:n,systemPrompt:s,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:I,reasoningEffort:h,clientMessageId:x,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=l,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||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"))!==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(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:D,managerConfig:f??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null,reasoningEffort:q}});const z=u=>{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: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 R=ce(c,u);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${l} 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 O={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:R};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:R}))};if(Q){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 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};
|
|
@@ -32,26 +32,6 @@ export declare class SessionManager {
|
|
|
32
32
|
constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined, upgradePolicyController?: UpgradePolicyController | null, managedRoomBinder?: ManagedRoomBinder | null);
|
|
33
33
|
private getRuntime;
|
|
34
34
|
getUpgradeIdleState(): UpgradeIdleState;
|
|
35
|
-
createManagedRoomSession(input: {
|
|
36
|
-
creationKey: string;
|
|
37
|
-
agentType: 'codex' | 'claude' | 'workbuddy';
|
|
38
|
-
workDir: string;
|
|
39
|
-
modelId: string | null;
|
|
40
|
-
initialMessage: string;
|
|
41
|
-
}): Promise<{
|
|
42
|
-
nianSessionId: string;
|
|
43
|
-
}>;
|
|
44
|
-
waitForManagedNativeSession(input: {
|
|
45
|
-
nianSessionId: string;
|
|
46
|
-
agentType: 'codex' | 'claude' | 'workbuddy';
|
|
47
|
-
timeoutMs: number;
|
|
48
|
-
}): Promise<{
|
|
49
|
-
sourceSessionKey: string;
|
|
50
|
-
}>;
|
|
51
|
-
publishManagedRoomSession(input: {
|
|
52
|
-
nianSessionId: string;
|
|
53
|
-
agentType: 'codex' | 'claude' | 'workbuddy';
|
|
54
|
-
}): void;
|
|
55
35
|
activateRoomSession(input: {
|
|
56
36
|
sessionId: string;
|
|
57
37
|
agentType: AgentType;
|
|
@@ -61,6 +41,7 @@ export declare class SessionManager {
|
|
|
61
41
|
clientMessageId: string;
|
|
62
42
|
systemPrompt: string;
|
|
63
43
|
roomContextRegistryKey: string;
|
|
44
|
+
managedRoomContextToken?: string;
|
|
64
45
|
}): Promise<{
|
|
65
46
|
externalOwnerQueued: boolean;
|
|
66
47
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import h from"node:crypto";import{getRegisteredAgents as f,unregisterAgent as p}from"../agents/adapter.js";import{loadConfig as w}from"../config/index.js";import{handleUpgradeStart as b,handleUpgradeStatus as v}from"../commands/upgrade.js";import{handleAgentsRefresh as R,handleModelsRefresh as y}from"./handlers/agents.js";import{handleAgentCapabilitiesList as k}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as S,handleAgentConfigGet as I,handleAgentConfigTest as A,handleAgentConfigUpsert as T}from"./handlers/agent-config.js";import{handleChatAbort as u,handleChatSend as l,sendSessionUpdateEvent as P}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as C}from"./handlers/title.js";import{cleanupPendingTransfers as F,handleFsLs as x,handleFsRead as D,handleFsRename as E,handleFsWrite as O,handleFsTransfer as Q,handleFsTransferAbort as j,handleFsTransferChunk as U,handleFsTransferFinish as z,handleFsTransferStart as $,handleFsExportMarkdownPdf as V,handleFsExportMarkdownPdfSetup as B,handleFsArchiveZip as W}from"./handlers/fs.js";import{handleRegionProbe as L,handleRegionSwitch as G,handleUpgradeSetPolicy as K}from"./handlers/control.js";import{ManagerRuntimeService as N,setManagerRuntimeService as m}from"../manager/runtime.js";import{ChatQueueManager as H}from"./queue.js";import{createAuthorizedFsRoot as X,resolveAuthorizedPath as Z,resolveSessionWorkDir as J}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as Y}from"../agents/custom.js";import{handleManagedRoomBind as q,handleManagedRoomRestart as ee}from"./handlers/room-managed.js";import{listSessionRecords as d}from"./store.js";const te=50;import{resolveSessionWorkDir as xe}from"../fs/boundary.js";class _e{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;activeRequests=0;constructor(e,t=null,s,a=null,n=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=n,this.managerRuntime=new N({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),this.chatQueue=new H({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),m(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(i=>{console.error("[session.activity] managed probe failed",i)})},15e3),this.activityProbeTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async createManagedRoomSession(e){const t=h.createHash("sha256").update(e.creationKey,"utf8").digest("hex"),s=`room-managed-${t.slice(0,48)}`,a=d().find(r=>r.sessionId===s);if(a&&(a.agentType!==e.agentType||a.workDir!==e.workDir))throw Object.assign(new Error("Managed Room Session identity conflicts with local state."),{code:"managed_room_session_conflict"});if(a?.agentSessionId)return{nianSessionId:s};const n={value:null},i=new Proxy(this.client,{get:(r,o)=>{if(o==="sendRes")return g=>{n.value=g};const c=Reflect.get(r,o,r);return typeof c=="function"?c.bind(r):c}});if(await l({...this.getRuntime(),client:i},{type:"req",id:`managed-create-${t.slice(0,32)}`,method:"chat.send",params:{sessionId:s,text:e.initialMessage,agentType:e.agentType,workDir:e.workDir,modelId:e.modelId??void 0,clientMessageId:`managed-bootstrap-${t.slice(0,32)}`,waitForDispatch:!0}}),n.value?.ok!==!0)throw Object.assign(new Error(n.value?.error||"Managed Agent failed to start."),{code:"managed_agent_start_failed"});return{nianSessionId:s}}async waitForManagedNativeSession(e){const t=Date.now();for(;Date.now()-t<e.timeoutMs;){const s=this.sessions.get(e.nianSessionId);if(s&&s.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent runtime does not match the created session."),{code:"managed_room_session_conflict"});const a=d().find(i=>i.sessionId===e.nianSessionId),n=s?.agentSessionId??a?.agentSessionId;if(n)return{sourceSessionKey:n};await new Promise(i=>setTimeout(i,100))}throw Object.assign(new Error("Timed out waiting for the exact native Agent Session."),{code:"native_session_timeout"})}publishManagedRoomSession(e){const t=d().find(s=>s.sessionId===e.nianSessionId);if(!t?.agentSessionId||t.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent Session is unavailable for publication."),{code:"managed_room_session_unavailable"});if(this.client.getState()!=="connected")throw Object.assign(new Error("Shennian Relay is disconnected."),{code:"managed_room_relay_disconnected"});P(this.getRuntime(),{sessionId:t.sessionId,agentType:t.agentType,workDir:t.workDir,agentSessionId:t.agentSessionId,modelId:t.modelId??void 0})}async activateRoomSession(e){const t=d().find(i=>i.sessionId===e.sessionId),s=t?.agentType===e.agentType?t.modelId??void 0:void 0,a={value:null},n=new Proxy(this.client,{get:(i,r)=>{if(r==="sendRes")return c=>{a.value=c};const o=Reflect.get(i,r,i);return typeof o=="function"?o.bind(i):o}});return await l({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:s,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:a.value?.ok===!0&&a.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(s,a)=>{if(a==="sendRes")return()=>{};const n=Reflect.get(s,a,s);return typeof n=="function"?n.bind(s):n}});await u({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const n=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:n,runPhase:s.runPhase,canStop:s.canStop??!0},seq:n,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of f())t.startsWith("custom:")&&p(t);const e=w();for(const[t,s]of Object.entries(e.customAgents??{}))Y(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await l(t,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await u(t,e);break;case"session.refresh":await M(t,e);break;case"session.tool.detail":await _(t,e);break;case"session.title.set":await C(t,e);break;case"room.agent.bind-managed":await q(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ee(t,this.managedRoomBinder,e);break;case"fs.ls":await x(t,e);break;case"fs.read":await D(t,e);break;case"fs.write":await O(t,e);break;case"fs.export.markdown-pdf":await V(t,e);break;case"fs.export.markdown-pdf.setup":await B(t,e);break;case"fs.archive.zip":await W(t,e);break;case"fs.rename":await E(t,e);break;case"fs.transfer":await Q(t,e);break;case"fs.transfer.start":await $(t,e);break;case"fs.transfer.chunk":await U(t,e);break;case"fs.transfer.finish":await z(t,e);break;case"fs.transfer.abort":await j(t,e);break;case"region.probe":await L(t,e);break;case"region.switch":await G(t,e);break;case"upgrade.start":await b(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await v(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await K(t,e);break;case"agents.refresh":await R(t,e);break;case"models.refresh":await y(t,e);break;case"agent.config.get":await I(t,e);break;case"agent.config.upsert":await T(t,e);break;case"agent.config.clear":await S(t,e);break;case"agent.config.test":await A(t,e);break;case"agent.capabilities.list":await k(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<te)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return J(e)}resolveAuthorizedPath(e,t){return Z(e,X(t))}async cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),F(this.getRuntime()),await this.managerRuntime.stop(),m(null)}}export{_e as SessionManager,xe as resolveSessionWorkDir};
|
|
1
|
+
import u from"node:crypto";import{getRegisteredAgents as m,unregisterAgent as p}from"../agents/adapter.js";import{loadConfig as f}from"../config/index.js";import{handleUpgradeStart as g,handleUpgradeStatus as b}from"../commands/upgrade.js";import{handleAgentsRefresh as w,handleModelsRefresh as v}from"./handlers/agents.js";import{handleAgentCapabilitiesList as k}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as R,handleAgentConfigGet as y,handleAgentConfigTest as S,handleAgentConfigUpsert as A}from"./handlers/agent-config.js";import{handleChatAbort as c,handleChatSend as l}from"./handlers/chat.js";import{handleSessionRefresh as I}from"./handlers/session-refresh.js";import{handleSessionToolDetail as T}from"./handlers/tool-detail.js";import{handleSessionTitleSet as P}from"./handlers/title.js";import{cleanupPendingTransfers as C,handleFsLs as M,handleFsRead as F,handleFsRename as x,handleFsWrite as D,handleFsTransfer as E,handleFsTransferAbort as Q,handleFsTransferChunk as z,handleFsTransferFinish as U,handleFsTransferStart as W,handleFsExportMarkdownPdf as _,handleFsExportMarkdownPdfSetup as O,handleFsArchiveZip as V}from"./handlers/fs.js";import{handleRegionProbe as B,handleRegionSwitch as $,handleUpgradeSetPolicy as j}from"./handlers/control.js";import{ManagerRuntimeService as L,setManagerRuntimeService as h}from"../manager/runtime.js";import{ChatQueueManager as G}from"./queue.js";import{createAuthorizedFsRoot as N,resolveAuthorizedPath as X,resolveSessionWorkDir as Z}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as H}from"../agents/custom.js";import{handleManagedRoomBind as J,handleManagedRoomRestart as K}from"./handlers/room-managed.js";import{listSessionRecords as Y}from"./store.js";const q=50;import{resolveSessionWorkDir as Fe}from"../fs/boundary.js";class Pe{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;activeRequests=0;constructor(e,t=null,a,i=null,n=null){this.client=e,this.nativeFusion=t,this.cliVersion=a,this.upgradePolicyController=i,this.managedRoomBinder=n,this.managerRuntime=new L({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),this.chatQueue=new G({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),h(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(s=>{console.error("[session.activity] managed probe failed",s)})},15e3),this.activityProbeTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async activateRoomSession(e){const t=Y().find(s=>s.sessionId===e.sessionId),a=t?.agentType===e.agentType?t.modelId??void 0:void 0,i={value:null},n=new Proxy(this.client,{get:(s,o)=>{if(o==="sendRes")return d=>{i.value=d};const r=Reflect.get(s,o,s);return typeof r=="function"?r.bind(s):r}});return await l({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:a,sessionMode:t?.sessionMode??(t?.agentType==="manager"?"manager":"standard"),managerConfig:t?.managerConfig??null,managerDefaultWorkerAgentType:t?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(a,i)=>{if(i==="sendRes")return()=>{};const n=Reflect.get(a,i,a);return typeof n=="function"?n.bind(a):n}});await c({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${u.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const a=await t.adapter.getStatus().catch(()=>null);if(!a?.active||!a.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:a.runId||t.currentRunId,runPhase:a.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:i,canStop:a.canStop??!0});const n=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:a.runId||t.currentRunId,seq:n,runPhase:a.runPhase,canStop:a.canStop??!0},seq:n,id:`agent-status-${a.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of m())t.startsWith("custom:")&&p(t);const e=f();for(const[t,a]of Object.entries(e.customAgents??{}))H(t,a)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await l(t,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await c(t,e);break;case"session.refresh":await I(t,e);break;case"session.tool.detail":await T(t,e);break;case"session.title.set":await P(t,e);break;case"room.agent.bind-managed":await J(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await K(t,this.managedRoomBinder,e);break;case"fs.ls":await M(t,e);break;case"fs.read":await F(t,e);break;case"fs.write":await D(t,e);break;case"fs.export.markdown-pdf":await _(t,e);break;case"fs.export.markdown-pdf.setup":await O(t,e);break;case"fs.archive.zip":await V(t,e);break;case"fs.rename":await x(t,e);break;case"fs.transfer":await E(t,e);break;case"fs.transfer.start":await W(t,e);break;case"fs.transfer.chunk":await z(t,e);break;case"fs.transfer.finish":await U(t,e);break;case"fs.transfer.abort":await Q(t,e);break;case"region.probe":await B(t,e);break;case"region.switch":await $(t,e);break;case"upgrade.start":await g(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await b(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await j(t,e);break;case"agents.refresh":await w(t,e);break;case"models.refresh":await v(t,e);break;case"agent.config.get":await y(t,e);break;case"agent.config.upsert":await A(t,e);break;case"agent.config.clear":await R(t,e);break;case"agent.config.test":await S(t,e);break;case"agent.capabilities.list":await k(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<q)return;let e=null;for(const[t,a]of this.sessions)(!e||a.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:a});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Z(e)}resolveAuthorizedPath(e,t){return X(e,N(t))}async cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),C(this.getRuntime()),await this.managerRuntime.stop(),h(null)}}export{Pe as SessionManager,Fe as resolveSessionWorkDir};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import h from"node:fs";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as I}from"../config/index.js";import{mergeProjectedSessions as
|
|
1
|
+
import h from"node:fs";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as I}from"../config/index.js";import{mergeProjectedSessions as M}from"./projection.js";import{materializeRemoteChatAttachments as k}from"./remote-attachments.js";const x=I("chat-queue.json");function w(){return{sessions:{}}}function f(){return new Date().toISOString()}function S(s){return`enqueue-send-${s}-${y()}`}function d(){try{const s=JSON.parse(h.readFileSync(x,"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch{return w()}}function u(s){h.mkdirSync(I(""),{recursive:!0}),h.writeFileSync(x,JSON.stringify(s,null,2))}function p(s){if(!Array.isArray(s))return;const e=s.map(n=>{if(!n||typeof n!="object")return null;const t=n,i={path:typeof t.path=="string"?t.path:"",name:typeof t.name=="string"?t.name:"",mimeType:typeof t.mimeType=="string"?t.mimeType:"",...typeof t.previewData=="string"&&t.previewData.trim()?{previewData:t.previewData.trim()}:{}};return i.path&&i.name&&i.mimeType?i:null}).filter(n=>n!=null);return e.length?e:void 0}function D(s){const e=f();return{id:s.queueMessageId||s.clientMessageId||`queue-${y()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:p(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}class C{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:d().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(M(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=p(t.attachments),r=i?.length?await k({text:t.text,attachments:i,workDir:t.workDir}):{text:t.text,attachments:i,localized:!1},a=await this.isSessionBusy(t.sessionId),o=d(),c=o.sessions[t.sessionId]??[];if(!a&&!c.length){await this.opts.dispatchReq({...e,id:S(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const l=D({...t,text:r.text,attachments:r.attachments}),m=t.origin==="external"?q(c,l):null;m||(o.sessions[t.sessionId]=[...c,l],u(o)),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=d(),r=i.sessions[t.sessionId]??[],a=r.findIndex(o=>o.id===t.queueMessageId);if(a<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r[a]={...r[a],text:t.text,attachments:p(t.attachments),updatedAt:f()},i.sessions[t.sessionId]=r,u(i),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=d(),r=i.sessions[t.sessionId]??[],a=r.filter(o=>o.id!==t.queueMessageId);if(a.length===r.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],u(i),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}drainIdleQueues(){const e=d();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const n=d(),t=n.sessions[e]??[],i=t.shift();if(!i){this.broadcast(e);return}const r=i.origin==="external"?this.mergeExternalMessages(i,t):i;t.length?n.sessions[e]=t:delete n.sessions[e],u(n),this.broadcast(e),this.draining.add(e);try{await this.dispatchQueuedMessage(r)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=R(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(r=>r.text).filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`),attachments:b(i.flatMap(r=>r.attachments??[])),updatedAt:
|
|
4
|
-
`)}function T(
|
|
3
|
+
`),attachments:b(i.flatMap(r=>r.attachments??[])),updatedAt:f()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function R(s){const e=new Set,n=[];for(const t of s){const i=g(t);e.has(i)||(e.add(i),n.push(t))}return n}function q(s,e){const n=g(e);return s.find(t=>g(t)===n)??null}function g(s){const e=T(s);return e?`id:${e}`:["body",A(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
|
|
4
|
+
`)}function T(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const n=typeof s.id=="string"?s.id.trim():"";return n&&!n.startsWith("queue-")?n:""}function b(s){const e=new Set,n=[];for(const t of s){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),n.push(t))}return n.length?n:void 0}function A(s){return String(s||"").replace(/\s+/g," ").trim()}export{C as ChatQueueManager};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { AgentType, Envelope } from '@shennian/wire';
|
|
1
|
+
import type { AgentType, Envelope, ManagerConfig, SessionMode } from '@shennian/wire';
|
|
2
2
|
export type LocalSessionRecord = {
|
|
3
3
|
sessionId: string;
|
|
4
4
|
agentType: AgentType;
|
|
5
|
+
sessionMode?: SessionMode;
|
|
6
|
+
managerConfig?: ManagerConfig | null;
|
|
5
7
|
workDir: string;
|
|
6
8
|
agentSessionId?: string | null;
|
|
7
9
|
modelId?: string | null;
|
|
10
|
+
managerDefaultWorkerAgentType?: AgentType | null;
|
|
11
|
+
managerDefaultWorkerModelId?: string | null;
|
|
8
12
|
status?: 'active' | 'completed' | 'failed';
|
|
9
13
|
createdAt: string;
|
|
10
14
|
updatedAt: string;
|
|
@@ -14,9 +18,13 @@ export type LocalSessionRecord = {
|
|
|
14
18
|
export declare function recordSession(input: {
|
|
15
19
|
sessionId: string;
|
|
16
20
|
agentType: AgentType;
|
|
21
|
+
sessionMode?: SessionMode;
|
|
22
|
+
managerConfig?: ManagerConfig | null;
|
|
17
23
|
workDir: string;
|
|
18
24
|
agentSessionId?: string | null;
|
|
19
25
|
modelId?: string | null;
|
|
26
|
+
managerDefaultWorkerAgentType?: AgentType | null;
|
|
27
|
+
managerDefaultWorkerModelId?: string | null;
|
|
20
28
|
status?: 'active' | 'completed' | 'failed';
|
|
21
29
|
lastActivityAt?: string;
|
|
22
30
|
lastMessagePreview?: string | null;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`;
|
|
3
|
-
`).filter(o=>o.trim().length>0);let
|
|
1
|
+
import i from"node:fs";import f from"node:path";import{resolveShennianPath as g}from"../config/index.js";const l=g("sessions"),c=g("sessions-index.json");function u(){i.mkdirSync(l,{recursive:!0})}function S(e){return f.join(l,`${e}.jsonl`)}function d(){try{const e=JSON.parse(i.readFileSync(c,"utf-8"));return e&&typeof e=="object"?e:{}}catch{return{}}}function m(e){i.mkdirSync(f.dirname(c),{recursive:!0}),i.writeFileSync(c,JSON.stringify(e,null,2))}function I(e){const t=e.payload.replace(/\s+/g," ").trim();return!t||t.startsWith('{"v":1,"type":"tool')?null:t.length>120?`${t.slice(0,120)}...`:t}function A(e){try{const t=d(),n=t[e.sessionId],r=new Date().toISOString();t[e.sessionId]={sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode??n?.sessionMode,managerConfig:e.managerConfig??n?.managerConfig??null,workDir:e.workDir,agentSessionId:e.agentSessionId??n?.agentSessionId??null,modelId:e.modelId??n?.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:e.status??n?.status??"active",createdAt:n?.createdAt??r,updatedAt:r,lastActivityAt:e.lastActivityAt??n?.lastActivityAt??r,lastMessagePreview:e.lastMessagePreview??n?.lastMessagePreview??null},m(t)}catch{}}function x(){return Object.values(d())}function D(e,t){try{u();const n=JSON.stringify(t)+`
|
|
2
|
+
`;i.appendFileSync(S(e),n,"utf-8");const r=d(),a=r[e];if(a){const s=I(t);r[e]={...a,updatedAt:new Date().toISOString(),lastActivityAt:new Date(t.ts).toISOString(),lastMessagePreview:s??a.lastMessagePreview??null},m(r)}}catch{}}function M(e,t){const n=S(e);if(!i.existsSync(n))return[];const a=i.readFileSync(n,"utf-8").split(`
|
|
3
|
+
`).filter(o=>o.trim().length>0);let s=[];for(const o of a)try{s.push(JSON.parse(o))}catch{}return s.sort((o,y)=>y.ts-o.ts),t?.before!==void 0&&(s=s.filter(o=>o.ts<t.before)),t?.limit!==void 0&&t.limit>0&&(s=s.slice(0,t.limit)),s}function h(){return u(),i.readdirSync(l).filter(e=>e.endsWith(".jsonl")).map(e=>e.replace(/\.jsonl$/,""))}export{D as appendMessage,x as listSessionRecords,h as listSessions,M as readMessages,A as recordSession};
|