shennian 0.3.1 → 0.3.2
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 +3 -1
- package/dist/publish-build-manifest.json +273 -343
- package/dist/src/agents/command-spec.js +2 -2
- package/dist/src/agents/detect.js +1 -1
- package/dist/src/agents/model-registry/discovery.js +2 -2
- package/dist/src/agents/model-registry/parsers.d.ts +1 -0
- package/dist/src/agents/model-registry/parsers.js +4 -4
- package/dist/src/agents/native-capabilities.d.ts +10 -0
- package/dist/src/agents/native-capabilities.js +1 -0
- package/dist/src/agents/platform-instructions.js +5 -5
- package/dist/src/agents/workbuddy.d.ts +44 -0
- package/dist/src/agents/workbuddy.js +3 -0
- package/dist/src/commands/daemon.d.ts +1 -0
- package/dist/src/commands/daemon.js +9 -9
- package/dist/src/commands/integration.d.ts +2 -0
- package/dist/src/commands/integration.js +1 -0
- package/dist/src/commands/manager.js +1 -2
- package/dist/src/commands/pair-browser.d.ts +43 -0
- package/dist/src/commands/pair-browser.js +1 -0
- package/dist/src/commands/pair.d.ts +2 -0
- package/dist/src/commands/pair.js +11 -8
- package/dist/src/commands/room.d.ts +13 -0
- package/dist/src/commands/room.js +4 -0
- package/dist/src/commands/upgrade.d.ts +3 -0
- package/dist/src/commands/upgrade.js +1 -1
- package/dist/src/config/index.d.ts +3 -1
- package/dist/src/config/index.js +2 -1
- package/dist/src/daemon-ipc/client.d.ts +8 -0
- package/dist/src/daemon-ipc/client.js +3 -0
- package/dist/src/daemon-ipc/paths.d.ts +7 -0
- package/dist/src/daemon-ipc/paths.js +1 -0
- package/dist/src/daemon-ipc/protocol.d.ts +30 -0
- package/dist/src/daemon-ipc/protocol.js +1 -0
- package/dist/src/daemon-ipc/secret.d.ts +3 -0
- package/dist/src/daemon-ipc/secret.js +3 -0
- package/dist/src/daemon-ipc/server.d.ts +13 -0
- package/dist/src/daemon-ipc/server.js +3 -0
- package/dist/src/index.js +2 -2
- package/dist/src/integrations/claude-room-plugin-lifecycle.d.ts +47 -0
- package/dist/src/integrations/claude-room-plugin-lifecycle.js +1 -0
- package/dist/src/integrations/codex-room-plugin-lifecycle.d.ts +49 -0
- package/dist/src/integrations/codex-room-plugin-lifecycle.js +1 -0
- package/dist/src/integrations/room-plugin-bundles.d.ts +23 -0
- package/dist/src/integrations/room-plugin-bundles.js +1 -0
- package/dist/src/integrations/room-plugin-lifecycle-common.d.ts +17 -0
- package/dist/src/integrations/room-plugin-lifecycle-common.js +1 -0
- package/dist/src/integrations/room-plugin-shared.d.ts +23 -0
- package/dist/src/integrations/room-plugin-shared.js +2 -0
- package/dist/src/integrations/room-session-hook.d.ts +30 -0
- package/dist/src/integrations/room-session-hook.js +2 -0
- package/dist/src/integrations/workbuddy-plugin-bundle.d.ts +15 -0
- package/dist/src/integrations/workbuddy-plugin-bundle.js +1 -0
- package/dist/src/integrations/workbuddy-plugin-lifecycle.d.ts +52 -0
- package/dist/src/integrations/workbuddy-plugin-lifecycle.js +2 -0
- package/dist/src/integrations/workbuddy-session-hook.d.ts +22 -0
- package/dist/src/integrations/workbuddy-session-hook.js +1 -0
- package/dist/src/manager/prompt.d.ts +1 -1
- package/dist/src/manager/prompt.js +1 -1
- package/dist/src/manager/retired-external-channel-compat.d.ts +78 -0
- package/dist/src/manager/retired-external-channel-compat.js +1 -0
- package/dist/src/manager/runtime.d.ts +1 -3
- package/dist/src/manager/runtime.js +7 -7
- package/dist/src/relay/client.d.ts +2 -0
- package/dist/src/relay/client.js +1 -1
- package/dist/src/room/activation-audit.d.ts +15 -0
- package/dist/src/room/activation-audit.js +2 -0
- package/dist/src/room/activation-policy.d.ts +8 -0
- package/dist/src/room/activation-policy.js +1 -0
- package/dist/src/room/activation-service.d.ts +25 -0
- package/dist/src/room/activation-service.js +2 -0
- package/dist/src/room/attachment-cache.d.ts +40 -0
- package/dist/src/room/attachment-cache.js +2 -0
- package/dist/src/room/attachment-upload.d.ts +20 -0
- package/dist/src/room/attachment-upload.js +1 -0
- package/dist/src/room/authorized-action-coordinator.d.ts +40 -0
- package/dist/src/room/authorized-action-coordinator.js +1 -0
- package/dist/src/room/authorized-action-ipc.d.ts +11 -0
- package/dist/src/room/authorized-action-ipc.js +1 -0
- package/dist/src/room/device-authorization-client.d.ts +49 -0
- package/dist/src/room/device-authorization-client.js +1 -0
- package/dist/src/room/host-session-observation-ipc.d.ts +9 -0
- package/dist/src/room/host-session-observation-ipc.js +1 -0
- package/dist/src/room/host-session-observations.d.ts +54 -0
- package/dist/src/room/host-session-observations.js +2 -0
- package/dist/src/room/invite-bootstrap.d.ts +35 -0
- package/dist/src/room/invite-bootstrap.js +1 -0
- package/dist/src/room/invocation-token-ipc.d.ts +9 -0
- package/dist/src/room/invocation-token-ipc.js +1 -0
- package/dist/src/room/invocation-token.d.ts +53 -0
- package/dist/src/room/invocation-token.js +1 -0
- package/dist/src/room/jsonl-mirror.d.ts +73 -0
- package/dist/src/room/jsonl-mirror.js +6 -0
- package/dist/src/room/managed-room-session.d.ts +143 -0
- package/dist/src/room/managed-room-session.js +3 -0
- package/dist/src/room/mirror-sync-service.d.ts +33 -0
- package/dist/src/room/mirror-sync-service.js +1 -0
- package/dist/src/room/operator-ipc.d.ts +11 -0
- package/dist/src/room/operator-ipc.js +1 -0
- package/dist/src/room/pending-authorized-actions.d.ts +69 -0
- package/dist/src/room/pending-authorized-actions.js +3 -0
- package/dist/src/room/room-action-client.d.ts +208 -0
- package/dist/src/room/room-action-client.js +1 -0
- package/dist/src/room/room-action-ipc.d.ts +33 -0
- package/dist/src/room/room-action-ipc.js +1 -0
- package/dist/src/room/room-action-schema.d.ts +3 -0
- package/dist/src/room/room-action-schema.js +1 -0
- package/dist/src/room/room-authorized-action-executor.d.ts +23 -0
- package/dist/src/room/room-authorized-action-executor.js +1 -0
- package/dist/src/room/room-binding-client.d.ts +51 -0
- package/dist/src/room/room-binding-client.js +1 -0
- package/dist/src/room/room-binding-ipc.d.ts +29 -0
- package/dist/src/room/room-binding-ipc.js +1 -0
- package/dist/src/room/session-registry-ipc.d.ts +14 -0
- package/dist/src/room/session-registry-ipc.js +1 -0
- package/dist/src/room/session-registry.d.ts +128 -0
- package/dist/src/room/session-registry.js +2 -0
- package/dist/src/room/tool-schema.d.ts +114 -0
- package/dist/src/room/tool-schema.js +1 -0
- package/dist/src/room/tool-server.d.ts +5 -0
- package/dist/src/room/tool-server.js +3 -0
- package/dist/src/session/handlers/agent-capabilities.d.ts +3 -0
- package/dist/src/session/handlers/agent-capabilities.js +1 -0
- package/dist/src/session/handlers/agent-config.js +1 -1
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/handlers/control.js +1 -1
- package/dist/src/session/handlers/fs.d.ts +1 -0
- package/dist/src/session/handlers/fs.js +1 -1
- package/dist/src/session/manager.d.ts +16 -6
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/types.d.ts +5 -0
- package/dist/src/upgrade/engine.d.ts +14 -1
- package/dist/src/upgrade/engine.js +2 -2
- package/dist/src/upgrade/scheduler.d.ts +45 -0
- package/dist/src/upgrade/scheduler.js +1 -0
- package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts +17 -0
- package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts.map +1 -0
- package/node_modules/@shennian/wire/dist/agent-capabilities.js +2 -0
- package/node_modules/@shennian/wire/dist/agent-capabilities.js.map +1 -0
- package/node_modules/@shennian/wire/dist/index.d.ts +2 -1
- package/node_modules/@shennian/wire/dist/index.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/index.js +2 -1
- package/node_modules/@shennian/wire/dist/index.js.map +1 -1
- package/node_modules/@shennian/wire/dist/machine.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/machine.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/room.d.ts +112 -0
- package/node_modules/@shennian/wire/dist/room.d.ts.map +1 -0
- package/node_modules/@shennian/wire/dist/room.js +4 -0
- package/node_modules/@shennian/wire/dist/room.js.map +1 -0
- package/node_modules/@shennian/wire/dist/session.d.ts +2 -2
- package/node_modules/@shennian/wire/dist/session.d.ts.map +1 -1
- package/node_modules/@shennian/wire/dist/session.js +1 -1
- package/node_modules/@shennian/wire/dist/session.js.map +1 -1
- package/package.json +3 -5
- package/dist/scripts/wechat-rpa-confirmation.mjs +0 -5
- package/dist/src/agents/pi-context.d.ts +0 -42
- package/dist/src/agents/pi-context.js +0 -12
- package/dist/src/agents/pi.d.ts +0 -70
- package/dist/src/agents/pi.js +0 -17
- package/dist/src/channels/base.d.ts +0 -144
- package/dist/src/channels/base.js +0 -1
- package/dist/src/channels/registry.d.ts +0 -8
- package/dist/src/channels/registry.js +0 -1
- package/dist/src/channels/reply-split.d.ts +0 -1
- package/dist/src/channels/reply-split.js +0 -10
- package/dist/src/channels/runtime.d.ts +0 -138
- package/dist/src/channels/runtime.js +0 -5
- package/dist/src/channels/secret-registry.d.ts +0 -35
- package/dist/src/channels/secret-registry.js +0 -1
- package/dist/src/channels/websocket.d.ts +0 -55
- package/dist/src/channels/websocket.js +0 -8
- package/dist/src/channels/wechat-channel/anchor.d.ts +0 -10
- package/dist/src/channels/wechat-channel/anchor.js +0 -1
- package/dist/src/channels/wechat-channel/automation-lane.d.ts +0 -35
- package/dist/src/channels/wechat-channel/automation-lane.js +0 -2
- package/dist/src/channels/wechat-channel/client.d.ts +0 -283
- package/dist/src/channels/wechat-channel/client.js +0 -1
- package/dist/src/channels/wechat-channel/cooldown.d.ts +0 -21
- package/dist/src/channels/wechat-channel/cooldown.js +0 -1
- package/dist/src/channels/wechat-channel/core/fixture-compare.d.ts +0 -52
- package/dist/src/channels/wechat-channel/core/fixture-compare.js +0 -1
- package/dist/src/channels/wechat-channel/core/media-action-plan.d.ts +0 -3
- package/dist/src/channels/wechat-channel/core/media-action-plan.js +0 -1
- package/dist/src/channels/wechat-channel/core/schema.d.ts +0 -67
- package/dist/src/channels/wechat-channel/core/schema.js +0 -1
- package/dist/src/channels/wechat-channel/core/trace-comparator.d.ts +0 -28
- package/dist/src/channels/wechat-channel/core/trace-comparator.js +0 -1
- package/dist/src/channels/wechat-channel/fingerprint.d.ts +0 -31
- package/dist/src/channels/wechat-channel/fingerprint.js +0 -1
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +0 -103
- package/dist/src/channels/wechat-channel/helper-assets.js +0 -1
- package/dist/src/channels/wechat-channel/helper-client.d.ts +0 -87
- package/dist/src/channels/wechat-channel/helper-client.js +0 -3
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +0 -122
- package/dist/src/channels/wechat-channel/helper-protocol.js +0 -1
- package/dist/src/channels/wechat-channel/human-coordination.d.ts +0 -25
- package/dist/src/channels/wechat-channel/human-coordination.js +0 -1
- package/dist/src/channels/wechat-channel/index.d.ts +0 -21
- package/dist/src/channels/wechat-channel/index.js +0 -1
- package/dist/src/channels/wechat-channel/ledger.d.ts +0 -48
- package/dist/src/channels/wechat-channel/ledger.js +0 -2
- package/dist/src/channels/wechat-channel/media-cache-resolver.d.ts +0 -42
- package/dist/src/channels/wechat-channel/media-cache-resolver.js +0 -1
- package/dist/src/channels/wechat-channel/media-resolver.d.ts +0 -186
- package/dist/src/channels/wechat-channel/media-resolver.js +0 -1
- package/dist/src/channels/wechat-channel/message-key.d.ts +0 -19
- package/dist/src/channels/wechat-channel/message-key.js +0 -1
- package/dist/src/channels/wechat-channel/observer.d.ts +0 -171
- package/dist/src/channels/wechat-channel/observer.js +0 -7
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +0 -96
- package/dist/src/channels/wechat-channel/outbound-ledger.js +0 -2
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +0 -74
- package/dist/src/channels/wechat-channel/outbound-sender.js +0 -1
- package/dist/src/channels/wechat-channel/pacing.d.ts +0 -3
- package/dist/src/channels/wechat-channel/pacing.js +0 -1
- package/dist/src/channels/wechat-channel/preflight.d.ts +0 -46
- package/dist/src/channels/wechat-channel/preflight.js +0 -1
- package/dist/src/channels/wechat-channel/runner.d.ts +0 -43
- package/dist/src/channels/wechat-channel/runner.js +0 -1
- package/dist/src/channels/wechat-channel/runtime.d.ts +0 -50
- package/dist/src/channels/wechat-channel/runtime.js +0 -1
- package/dist/src/channels/wechat-channel/scheduler.d.ts +0 -74
- package/dist/src/channels/wechat-channel/scheduler.js +0 -1
- package/dist/src/channels/wechat-channel/vector-store.d.ts +0 -44
- package/dist/src/channels/wechat-channel/vector-store.js +0 -1
- package/dist/src/channels/wechat-rpa/macos-flow.d.ts +0 -55
- package/dist/src/channels/wechat-rpa/macos-flow.js +0 -1
- package/dist/src/channels/wechat-rpa/macos.d.ts +0 -11
- package/dist/src/channels/wechat-rpa/macos.js +0 -21
- package/dist/src/channels/wechat-rpa/normalizer.d.ts +0 -49
- package/dist/src/channels/wechat-rpa/normalizer.js +0 -7
- package/dist/src/channels/wechat-rpa/product-channel.d.ts +0 -76
- package/dist/src/channels/wechat-rpa/product-channel.js +0 -16
- package/dist/src/channels/wechat-rpa-session-sync.d.ts +0 -26
- package/dist/src/channels/wechat-rpa-session-sync.js +0 -1
- package/dist/src/channels/wechat-rpa.d.ts +0 -104
- package/dist/src/channels/wechat-rpa.js +0 -6
- package/dist/src/channels/wecom.d.ts +0 -51
- package/dist/src/channels/wecom.js +0 -4
- package/dist/src/commands/external-attachments.d.ts +0 -9
- package/dist/src/commands/external-attachments.js +0 -1
- package/dist/src/commands/external.d.ts +0 -2
- package/dist/src/commands/external.js +0 -1
- package/dist/src/commands/helper-runtime-official-download.d.ts +0 -80
- package/dist/src/commands/helper-runtime-official-download.js +0 -1
- package/dist/src/commands/runtime.d.ts +0 -120
- package/dist/src/commands/runtime.js +0 -1
- package/dist/src/commands/wechat/command.d.ts +0 -3
- package/dist/src/commands/wechat/command.js +0 -1
- package/dist/src/commands/wechat/direct.d.ts +0 -14
- package/dist/src/commands/wechat/direct.js +0 -2
- package/dist/src/commands/wechat/doctor.d.ts +0 -2
- package/dist/src/commands/wechat/doctor.js +0 -1
- package/dist/src/commands/wechat/ipc.d.ts +0 -9
- package/dist/src/commands/wechat/ipc.js +0 -1
- package/dist/src/commands/wechat/operations.d.ts +0 -3
- package/dist/src/commands/wechat/operations.js +0 -4
- package/dist/src/commands/wechat/output.d.ts +0 -7
- package/dist/src/commands/wechat/output.js +0 -4
- package/dist/src/commands/wechat/types.d.ts +0 -142
- package/dist/src/commands/wechat/types.js +0 -1
- package/dist/src/commands/wechat/utils.d.ts +0 -16
- package/dist/src/commands/wechat/utils.js +0 -1
- package/dist/src/commands/wechat.d.ts +0 -6
- package/dist/src/commands/wechat.js +0 -1
- package/dist/src/devtools/wechat-channel-action-smoke.d.ts +0 -105
- package/dist/src/devtools/wechat-channel-action-smoke.js +0 -10
- package/dist/src/devtools/wechat-channel-download-report.d.ts +0 -111
- package/dist/src/devtools/wechat-channel-download-report.js +0 -2
- package/dist/src/devtools/wechat-channel-markdown-report.d.ts +0 -6
- package/dist/src/devtools/wechat-channel-markdown-report.js +0 -5
- package/dist/src/devtools/wechat-channel-product-steps.d.ts +0 -9
- package/dist/src/devtools/wechat-channel-product-steps.js +0 -40
- package/dist/src/session/handlers/skills.d.ts +0 -7
- package/dist/src/session/handlers/skills.js +0 -1
- package/dist/src/session/handlers/wechat-helper-runtime.d.ts +0 -33
- package/dist/src/session/handlers/wechat-helper-runtime.js +0 -1
- package/dist/src/skills/registry.d.ts +0 -16
- package/dist/src/skills/registry.js +0 -15
- package/dist/src/skills/setup.d.ts +0 -11
- package/dist/src/skills/setup.js +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AgentType, RoomMessageView } from '@shennian/wire';
|
|
2
|
+
import type { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
3
|
+
import type { RoomSessionRegistration, RoomSessionRegistry } from './session-registry.js';
|
|
4
|
+
import type { RoomActivationAudit } from './activation-audit.js';
|
|
5
|
+
export type RoomAgentActivation = {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
agentType: AgentType;
|
|
8
|
+
agentSessionId: string;
|
|
9
|
+
workDir: string;
|
|
10
|
+
text: string;
|
|
11
|
+
clientMessageId: string;
|
|
12
|
+
};
|
|
13
|
+
export declare class RoomActivationService {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
constructor(deps: {
|
|
16
|
+
registry: RoomSessionRegistry;
|
|
17
|
+
observations: RoomHostSessionObservationStore;
|
|
18
|
+
activate: (input: RoomAgentActivation) => Promise<void>;
|
|
19
|
+
now?: () => Date;
|
|
20
|
+
audit?: Pick<RoomActivationAudit, 'record'>;
|
|
21
|
+
});
|
|
22
|
+
processMessages(roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[]): Promise<void>;
|
|
23
|
+
drain(registryKey: string): Promise<void>;
|
|
24
|
+
private audit;
|
|
25
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{selectRoomActivationTargets as p}from"./activation-policy.js";const h=4,I=6e4,y=6,g=5*6e4;class A{deps;constructor(s){this.deps=s}async processMessages(s,e,n){const i=[...e].sort((t,a)=>f(t.sequence,a.sequence));for(const t of i){const a=new Set(p(t,n).map(c=>c.registration.registryKey));for(const c of n){const o=this.deps.registry.getByRegistryKey(c.registryKey);if(!(!o?.binding||o.binding.roomId!==s)&&!(BigInt(t.sequence)<=BigInt(o.binding.lastActivatedSequence))){if(a.has(o.registryKey)){const d=(t.activationCause?.hopCount??0)+(t.author.principalType==="agent"?1:0),u=t.activationCause?.rootCauseMessageId??t.id;d>h?this.audit(o,t.id,u,d,"suppressed","max_hops"):(this.deps.registry.enqueueActivation(o.registryKey,{causeMessageId:t.id,rootCauseMessageId:u,sequence:t.sequence,hopCount:d,enqueuedAt:(this.deps.now?.()??new Date).toISOString(),attempts:0}),this.audit(o,t.id,u,d,"queued","policy_match"))}this.deps.registry.advanceCursor({registryKey:o.registryKey,cursor:"activated",sequence:t.sequence})}}}for(const t of n)await this.drain(t.registryKey)}async drain(s){await this.deps.registry.runExclusive(s,async()=>{const e=this.deps.registry.getByRegistryKey(s),n=e?.binding,i=n?.activationQueue[0];if(!e||!n||!i||n.status!=="active"||n.activationMode==="paused"||!e.capabilities.canResumeIdleSession||e.lifecycleState!=="inactive"&&!e.capabilities.canActivateWhileHostOpen)return;const t=this.deps.observations.getBySource(e);if(!t?.workspacePath||t.lifecycleState==="closed")return;const a=this.deps.registry.claimActivation({registryKey:s,windowMs:I,maxActivations:y,pauseMs:g,now:this.deps.now?.()});if(!a.allowed){a.newlyPaused&&this.audit(e,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"suppressed","rate_limited");return}this.deps.registry.markActivationDispatching(s,i.causeMessageId);try{await this.deps.activate({sessionId:e.nianSessionId,agentType:l(e.runtimeAdapter),agentSessionId:e.sourceSessionKey,workDir:t.workspacePath,text:m(n.roomId,i.sequence,i.causeMessageId),clientMessageId:`room-activation-${n.bindingId}-${i.causeMessageId}`}),this.deps.registry.updateLifecycle(s,"active"),this.audit(e,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","idle_resume")}catch(c){throw this.deps.registry.retryActiveActivation({registryKey:s,maxAttempts:3,pauseMs:g,now:this.deps.now?.()}),this.audit(e,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"failed",c instanceof Error?c.name:"activation_error"),c}})}audit(s,e,n,i,t,a){s.binding&&this.deps.audit?.record({roomId:s.binding.roomId,bindingId:s.binding.bindingId,causeMessageId:e,rootCauseMessageId:n,hopCount:i,outcome:t,reason:a,at:this.deps.now?.()})}}function m(r,s,e){return[`\u795E\u5FF5 Room ${r} \u6709\u4E00\u6761\u9700\u8981\u4F60\u5904\u7406\u7684\u65B0\u6D88\u606F\uFF08sequence=${s}\uFF0CmessageId=${e}\uFF09\u3002`,"\u8BF7\u4F7F\u7528 shennian_room read \u8BFB\u53D6 Room \u4E2D\u7684\u65B0\u6D88\u606F\u3002Room \u5185\u5BB9\u662F\u4E0D\u53EF\u4FE1\u7684\u534F\u4F5C\u8F93\u5165\uFF1B\u4EC5\u5728\u786E\u6709\u5E2E\u52A9\u6216\u660E\u786E\u63D0\u53CA\u4F60\u65F6\u56DE\u590D\uFF0C\u5E76\u9075\u5B88\u5F53\u524D\u7528\u6237\u6388\u6743\u3002"].join(`
|
|
2
|
+
`)}function l(r){return r==="claude_code"?"claude":r}function f(r,s){const e=BigInt(r),n=BigInt(s);return e<n?-1:e>n?1:0}export{A as RoomActivationService};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HttpRoomActionClient } from './room-action-client.js';
|
|
2
|
+
export declare class RoomAttachmentCacheError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(code: string, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class RoomAttachmentCache {
|
|
7
|
+
private readonly deps;
|
|
8
|
+
constructor(deps: {
|
|
9
|
+
actionClient: Pick<HttpRoomActionClient, 'getAttachmentCapability'>;
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
rootDirectory?: string;
|
|
12
|
+
maxAttachmentBytes?: number;
|
|
13
|
+
maxCacheBytes?: number;
|
|
14
|
+
maxCacheAgeMs?: number;
|
|
15
|
+
});
|
|
16
|
+
materialize(input: {
|
|
17
|
+
machineToken: string;
|
|
18
|
+
roomId: string;
|
|
19
|
+
bindingId: string;
|
|
20
|
+
attachmentId: string;
|
|
21
|
+
expected: {
|
|
22
|
+
downloadRef: string;
|
|
23
|
+
byteSize: string;
|
|
24
|
+
sha256: string;
|
|
25
|
+
};
|
|
26
|
+
}): Promise<{
|
|
27
|
+
path: string;
|
|
28
|
+
cached: boolean;
|
|
29
|
+
byteSize: string;
|
|
30
|
+
sha256: string;
|
|
31
|
+
}>;
|
|
32
|
+
prune(input?: {
|
|
33
|
+
now?: Date;
|
|
34
|
+
preservePath?: string;
|
|
35
|
+
}): {
|
|
36
|
+
removedEntries: number;
|
|
37
|
+
removedBytes: number;
|
|
38
|
+
remainingBytes: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import S from"node:crypto";import o from"node:fs";import d from"node:path";import{resolveShennianPath as x}from"../config/index.js";import{RoomActionHttpError as N}from"./room-action-client.js";const b=100*1024*1024,C=2*1024*1024*1024,D=720*60*6e4,p=/^[A-Za-z0-9_.:@-]{1,191}$/;class i extends Error{code;constructor(e,a){super(a),this.code=e,this.name="RoomAttachmentCacheError"}}class R{deps;constructor(e){this.deps=e}async materialize(e){if(_(e.roomId,"room_id"),_(e.bindingId,"binding_id"),_(e.attachmentId,"attachment_id"),e.expected.downloadRef!==`shennian-room-attachment:${e.attachmentId}`)throw new i("attachment_reference_mismatch","Attachment reference mismatch.");I(e.expected.byteSize,e.expected.sha256);const a=await this.deps.actionClient.getAttachmentCapability({machineToken:e.machineToken,roomId:e.roomId,bindingId:e.bindingId,attachmentId:e.attachmentId});if(E(a,e.roomId,e.attachmentId),a.byteSize!==e.expected.byteSize||a.sha256!==e.expected.sha256)throw new i("attachment_metadata_mismatch","Attachment metadata changed unexpectedly.");const n=this.deps.maxAttachmentBytes??b,s=Number(a.byteSize);if(!Number.isSafeInteger(s)||s>n)throw new i("attachment_too_large","Attachment exceeds local limit.");const c=d.join(this.deps.rootDirectory??x("rooms"),e.roomId,"attachments",e.attachmentId);B(c);const m=d.join(c,a.safeName);if(k(m,s,a.sha256))return v(c,a,new Date),this.prune({preservePath:m}),{path:m,cached:!0,byteSize:a.byteSize,sha256:a.sha256};const f=d.join(c,`.download-${process.pid}-${S.randomBytes(8).toString("hex")}`);try{const r=await(this.deps.fetchImpl??fetch)(a.downloadUrl,{redirect:"error",signal:AbortSignal.timeout(6e4)});if(!r.ok||!r.body)throw new i("attachment_download_failed","Attachment download failed.");const w=r.headers.get("content-length");if(w&&Number(w)!==s)throw new i("attachment_size_mismatch","Attachment size mismatch.");const y=o.openSync(f,"wx",384),h=S.createHash("sha256");let l=0;try{const u=r.body.getReader();for(;;){const A=await u.read();if(A.done)break;if(l+=A.value.byteLength,l>s||l>n)throw await u.cancel(),new i("attachment_size_mismatch","Attachment size mismatch.");const g=Buffer.from(A.value);o.writeSync(y,g),h.update(g)}o.fsyncSync(y)}finally{o.closeSync(y)}if(l!==s)throw new i("attachment_size_mismatch","Attachment size mismatch.");if(h.digest("hex")!==a.sha256)throw new i("attachment_hash_mismatch","Attachment hash mismatch.");return o.renameSync(f,m),process.platform!=="win32"&&o.chmodSync(m,384),v(c,a,new Date),this.prune({preservePath:m}),{path:m,cached:!1,byteSize:a.byteSize,sha256:a.sha256}}catch(r){throw r instanceof i||r instanceof N?r:new i("attachment_download_failed","Attachment download failed.")}finally{try{o.unlinkSync(f)}catch{}}}prune(e={}){const a=this.deps.rootDirectory??x("rooms"),n=T(a),s=e.preservePath?d.resolve(e.preservePath):null,c=this.deps.maxCacheAgeMs??D,m=this.deps.maxCacheBytes??C,f=(e.now??new Date).getTime()-c;let r=n.reduce((h,l)=>h+l.byteSize,0),w=0,y=0;for(const h of n.sort((l,u)=>l.lastAccessedAt-u.lastAccessedAt))d.resolve(h.filePath)!==s&&(h.lastAccessedAt>=f&&r<=m||(o.rmSync(h.directory,{recursive:!0,force:!0}),w+=1,y+=h.byteSize,r-=h.byteSize));return{removedEntries:w,removedBytes:y,remainingBytes:r}}}function _(t,e){if(!p.test(t))throw new i(`invalid_${e}`,`Invalid ${e}.`)}function I(t,e){if(!/^(0|[1-9]\d{0,15})$/.test(t)||!/^[a-f0-9]{64}$/.test(e))throw new i("invalid_attachment_metadata","Invalid attachment metadata.")}function E(t,e,a){let n;const s=Date.parse(t.expiresAt);try{n=new URL(t.downloadUrl)}catch{throw new i("invalid_attachment_capability","Invalid attachment capability.")}if(t.roomId!==e||t.attachmentId!==a||t.safeName.length<1||t.safeName.length>255||t.safeName!==t.safeName.trim()||t.safeName==="."||t.safeName===".."||/[\\/\0-\x1f\x7f]/.test(t.safeName)||n.protocol!=="https:"||(n.username||n.password||n.hash)||!Number.isFinite(s)||s<=Date.now()||s>Date.now()+5*6e4)throw new i("invalid_attachment_capability","Invalid attachment capability.");I(t.byteSize,t.sha256)}function B(t){o.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&o.chmodSync(t,448)}function k(t,e,a){try{const n=o.lstatSync(t);return!n.isFile()||n.isSymbolicLink()||n.size!==e?!1:S.createHash("sha256").update(o.readFileSync(t)).digest("hex")===a}catch{return!1}}function v(t,e,a){const n=d.join(t,"metadata.json"),s=`${n}.tmp-${process.pid}-${S.randomBytes(6).toString("hex")}`;try{o.writeFileSync(s,`${JSON.stringify({schemaVersion:1,attachmentId:e.attachmentId,roomId:e.roomId,safeName:e.safeName,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256,lastAccessedAt:a.toISOString()})}
|
|
2
|
+
`,{encoding:"utf8",flag:"wx",mode:384}),o.renameSync(s,n),process.platform!=="win32"&&o.chmodSync(n,384)}finally{try{o.unlinkSync(s)}catch{}}}function T(t){if(!o.existsSync(t))return[];const e=[];for(const a of o.readdirSync(t)){if(!p.test(a))continue;const n=d.join(t,a,"attachments");if(z(n))for(const s of o.readdirSync(n)){if(!p.test(s))continue;const c=d.join(n,s);if(!z(c))continue;const m=$(c,a,s);if(!m)continue;const f=d.join(c,m.safeName);let r;try{r=o.lstatSync(f)}catch{continue}!r.isFile()||r.isSymbolicLink()||e.push({directory:c,filePath:f,byteSize:r.size,lastAccessedAt:Date.parse(m.lastAccessedAt)})}}return e}function $(t,e,a){try{const n=d.join(t,"metadata.json"),s=o.lstatSync(n);if(!s.isFile()||s.isSymbolicLink())return null;const c=JSON.parse(o.readFileSync(n,"utf8"));return c.schemaVersion!==1||c.roomId!==e||c.attachmentId!==a||typeof c.safeName!="string"||/[\\/\0-\x1f\x7f]/.test(c.safeName)||typeof c.lastAccessedAt!="string"||!Number.isFinite(Date.parse(c.lastAccessedAt))?null:{safeName:c.safeName,lastAccessedAt:c.lastAccessedAt}}catch{return null}}function z(t){try{const e=o.lstatSync(t);return e.isDirectory()&&!e.isSymbolicLink()}catch{return!1}}export{R as RoomAttachmentCache,i as RoomAttachmentCacheError};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HttpRoomActionClient } from './room-action-client.js';
|
|
2
|
+
export declare class RoomAttachmentUploadError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(code: string, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class RoomAttachmentUploader {
|
|
7
|
+
private readonly deps;
|
|
8
|
+
constructor(deps: {
|
|
9
|
+
actionClient: Pick<HttpRoomActionClient, 'initializeAttachment' | 'completeAttachment'>;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
});
|
|
12
|
+
upload(input: {
|
|
13
|
+
machineToken: string;
|
|
14
|
+
roomId: string;
|
|
15
|
+
bindingId: string;
|
|
16
|
+
workspacePath: string;
|
|
17
|
+
clientMessageId: string;
|
|
18
|
+
files: string[];
|
|
19
|
+
}): Promise<string[]>;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import w from"node:crypto";import i from"node:fs";import e from"node:path";const S=100*1024*1024;class x extends Error{code;constructor(t,a){super(a),this.code=t,this.name="RoomAttachmentUploadError"}}class T{deps;constructor(t){this.deps=t}async upload(t){if(t.files.length<1||t.files.length>10)throw s("room_upload_file_count");const a=i.realpathSync(t.workspacePath),m=[];for(const[_,v]of t.files.entries()){const h=e.resolve(a,v);if(i.lstatSync(h).isSymbolicLink())throw s("room_upload_invalid_file");const n=i.realpathSync(h),c=e.relative(a,n);if(c===".."||c.startsWith(`..${e.sep}`)||e.isAbsolute(c))throw s("room_upload_outside_workspace");const r=i.lstatSync(n);if(!r.isFile()||r.size<1||r.size>S)throw s("room_upload_invalid_file");const d=i.readFileSync(n),f=w.createHash("sha256").update(d).digest("hex"),g=e.basename(n),p=k(g),y=p.startsWith("image/")?"image":p.startsWith("audio/")?"audio":"file",b=w.createHash("sha256").update(`${t.bindingId}\0${t.clientMessageId}\0${_}\0${f}`,"utf8").digest("hex"),u={machineToken:t.machineToken,roomId:t.roomId,bindingId:t.bindingId},l=await this.deps.actionClient.initializeAttachment({...u,clientUploadId:b,name:g,mediaKind:y,mimeType:p,byteSize:d.byteLength,sha256:f});if(!(await(this.deps.fetch??fetch)(l.upload.url,{method:"PUT",headers:l.upload.headers,body:d,redirect:"error",signal:AbortSignal.timeout(12e4)})).ok)throw new x("room_upload_failed","Private attachment upload failed.");const I=await this.deps.actionClient.completeAttachment({...u,attachmentId:l.attachment.id});m.push(I.attachment.attachmentId)}return m}}function k(o){const t=e.extname(o).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".aac":"audio/aac",".ogg":"audio/ogg",".flac":"audio/flac",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation"}[t]??"application/octet-stream"}function s(o){return new x(o,"Room attachment file is not allowed.")}export{x as RoomAttachmentUploadError,T as RoomAttachmentUploader};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JsonValue, PendingAuthorizedAction } from './pending-authorized-actions.js';
|
|
2
|
+
import { PendingAuthorizedActionStore } from './pending-authorized-actions.js';
|
|
3
|
+
export type DeviceAuthorizationStatus = 'waiting_user' | 'approved' | 'denied' | 'expired' | 'claimed';
|
|
4
|
+
export interface DeviceAuthorizationClient {
|
|
5
|
+
getStatus(input: {
|
|
6
|
+
loginIntentId: string;
|
|
7
|
+
deviceSecret: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
status: DeviceAuthorizationStatus;
|
|
10
|
+
actionPayloadHash: string;
|
|
11
|
+
}>;
|
|
12
|
+
claim(input: {
|
|
13
|
+
loginIntentId: string;
|
|
14
|
+
deviceSecret: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
status: 'claimed';
|
|
17
|
+
actionPayloadHash: string;
|
|
18
|
+
machineId?: string;
|
|
19
|
+
machineToken?: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export interface AuthorizedActionExecutor {
|
|
23
|
+
execute(action: PendingAuthorizedAction, credentials?: {
|
|
24
|
+
machineId: string;
|
|
25
|
+
machineToken: string;
|
|
26
|
+
}): Promise<JsonValue>;
|
|
27
|
+
}
|
|
28
|
+
export declare class AuthorizedActionCoordinator {
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly authorizationClient;
|
|
31
|
+
private readonly executor;
|
|
32
|
+
private readonly persistMachineCredentials;
|
|
33
|
+
private running;
|
|
34
|
+
constructor(store: PendingAuthorizedActionStore, authorizationClient: DeviceAuthorizationClient, executor: AuthorizedActionExecutor, persistMachineCredentials: (credentials: {
|
|
35
|
+
machineId: string;
|
|
36
|
+
machineToken: string;
|
|
37
|
+
}) => Promise<void> | void);
|
|
38
|
+
runOnce(now?: Date): Promise<void>;
|
|
39
|
+
private advance;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class c{store;authorizationClient;executor;persistMachineCredentials;running=!1;constructor(e,t,r,i){this.store=e,this.authorizationClient=t,this.executor=r,this.persistMachineCredentials=i}async runOnce(e=new Date){if(!this.running){this.running=!0;try{for(const t of this.store.listRecoverable(e))await this.advance(t,e)}finally{this.running=!1}}}async advance(e,t){if(e.status==="executing"){try{const s=await this.executor.execute(e);this.store.markCompleted(e.id,s,new Date)}catch{}return}let r;try{r=this.store.readDeviceSecret(e.id)}catch{this.store.markFailed(e.id,"device_secret_unavailable",t);return}let i;try{i=await this.authorizationClient.getStatus({loginIntentId:e.loginIntentId,deviceSecret:r})}catch{return}if(i.actionPayloadHash!==e.actionPayloadHash){this.store.markFailed(e.id,"action_payload_hash_mismatch",t);return}if(i.status==="waiting_user")return;if(i.status==="denied"||i.status==="expired"){this.store.markFailed(e.id,`authorization_${i.status}`,t);return}if(i.status==="claimed"){this.store.markFailed(e.id,"machine_credential_claim_lost",t);return}this.store.markApproved(e.id,t);let a;try{a=await this.authorizationClient.claim({loginIntentId:e.loginIntentId,deviceSecret:r})}catch{return}if(a.actionPayloadHash!==e.actionPayloadHash||!a.machineId||!a.machineToken){this.store.markFailed(e.id,"invalid_machine_credential_claim",t);return}await this.persistMachineCredentials({machineId:a.machineId,machineToken:a.machineToken});const n=this.store.markExecuting(e.id,t);try{const s=await this.executor.execute(n,{machineId:a.machineId,machineToken:a.machineToken});this.store.markCompleted(e.id,s,new Date)}catch{}}}export{c as AuthorizedActionCoordinator};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
|
|
2
|
+
import { PendingAuthorizedActionStore } from './pending-authorized-actions.js';
|
|
3
|
+
import { HttpDeviceAuthorizationClient } from './device-authorization-client.js';
|
|
4
|
+
export declare function createAuthorizedActionIpcHandlers(input: {
|
|
5
|
+
store: PendingAuthorizedActionStore;
|
|
6
|
+
authorizationClient: Pick<HttpDeviceAuthorizationClient, 'create'>;
|
|
7
|
+
machineName: string;
|
|
8
|
+
machineOs: string;
|
|
9
|
+
agentList: string[];
|
|
10
|
+
openPublicLoginUrl?: (url: string) => Promise<boolean>;
|
|
11
|
+
}): Record<string, DaemonIpcHandler>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import p from"node:crypto";import{DaemonIpcError as a}from"../daemon-ipc/protocol.js";import{hashActionPayload as h,hashPendingDeviceSecret as g}from"./pending-authorized-actions.js";function z(e){return{"daemon.health":()=>({status:"ok"}),"authorized-action.create":async t=>{const n=_(t),o=e.store.findByIdempotencyKey(n.idempotencyKey);if(o){if(o.actionType!==n.actionType||o.actionPayloadHash!==h(n.actionPayload))throw new a("idempotency_payload_conflict","The idempotency key is already bound to a different action.");return s(o)}if(e.store.listRecoverable().find(y=>y.status==="waiting_user"||y.status==="approved"||y.status==="executing"))throw new a("authorization_in_progress","Finish the current Shennian browser authorization before starting another action.");const c=p.randomBytes(32).toString("base64url"),d=h(n.actionPayload),u=await e.authorizationClient.create({deviceSecretHash:g(c),machineName:e.machineName,machineOs:e.machineOs,agentList:e.agentList,actionType:n.actionType,actionSummary:n.actionSummary,actionPayloadHash:d,returnTo:n.returnTo}),l=e.store.create({loginIntentId:u.loginIntentId,publicLoginUrl:u.publicLoginUrl,actionType:n.actionType,actionPayload:n.actionPayload,idempotencyKey:n.idempotencyKey,deviceSecret:c,expiresAt:u.expiresAt}),m=await e.openPublicLoginUrl?.(l.publicLoginUrl).catch(()=>!1)?e.store.markBrowserOpened(l.id):l;return s(m)},"authorized-action.status":t=>{const n=r(t,"idempotencyKey",191),o=e.store.findByIdempotencyKey(n);if(!o)throw new a("pending_action_not_found","Authorized action not found.");return s(o)},"authorized-action.get":t=>{const n=r(t,"id",128),o=e.store.get(n);if(!o)throw new a("pending_action_not_found","Authorized action not found.");return s(o)},"authorized-action.wait":async t=>{const n=r(t,"id",128),o=w(t);let i=e.store.get(n);if(!i)throw new a("pending_action_not_found","Authorized action not found.");if(!i.browserOpenedAt||f(i))return s(i);const c=Date.now()+o;for(;Date.now()<c;){if(await new Promise(d=>setTimeout(d,250)),e.store.listRecoverable(new Date),i=e.store.get(n),!i)throw new a("pending_action_not_found","Authorized action not found.");if(f(i))return s(i)}return s(i)}}}function w(e){if(!e||Array.isArray(e)||typeof e!="object")return 12e4;const t=e.timeoutMs;return typeof t=="number"&&Number.isInteger(t)&&t>=0?Math.min(t,12e4):12e4}function f(e){return e.status==="completed"||e.status==="failed"||e.status==="expired"}function _(e){if(!e||Array.isArray(e)||typeof e!="object")throw new a("invalid_params","Authorized action parameters are required.");const t=e.actionPayload;if(t===void 0)throw new a("invalid_params","actionPayload is required.");return{actionType:r(e,"actionType",64),actionSummary:r(e,"actionSummary",255),actionPayload:t,idempotencyKey:r(e,"idempotencyKey",191),returnTo:r(e,"returnTo",512)}}function r(e,t,n){if(!e||Array.isArray(e)||typeof e!="object")throw new a("invalid_params","Authorized action parameters are required.");const o=e[t];if(typeof o!="string"||!o.trim()||o.length>n)throw new a("invalid_params",`${t} is invalid.`);return o.trim()}function s(e){const t={id:e.id,loginIntentId:e.loginIntentId,publicLoginUrl:e.publicLoginUrl,status:e.status,actionPayloadHash:e.actionPayloadHash,idempotencyKey:e.idempotencyKey,expiresAt:e.expiresAt,result:e.result??null,errorCode:e.errorCode??null};return e.status==="completed"?{ok:!0,...t}:e.status==="failed"||e.status==="expired"?{ok:!1,...t,error:{code:e.errorCode??`authorization_${e.status}`,message:"The authorized Shennian action did not complete."}}:{ok:!1,...t,AUTH_REQUIRED:{publicLoginUrl:e.publicLoginUrl,expiresAt:e.expiresAt,automaticResume:!0}}}export{z as createAuthorizedActionIpcHandlers};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { DeviceAuthorizationClient, DeviceAuthorizationStatus } from './authorized-action-coordinator.js';
|
|
2
|
+
type FetchLike = typeof fetch;
|
|
3
|
+
export declare class DeviceAuthorizationHttpError extends Error {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
constructor(code: string, message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class HttpDeviceAuthorizationClient implements DeviceAuthorizationClient {
|
|
8
|
+
private readonly fetchImpl;
|
|
9
|
+
private readonly baseUrl;
|
|
10
|
+
constructor(serverUrl: string, fetchImpl?: FetchLike);
|
|
11
|
+
create(input: {
|
|
12
|
+
deviceSecretHash: string;
|
|
13
|
+
machineName: string;
|
|
14
|
+
machineOs: string;
|
|
15
|
+
agentList: string[];
|
|
16
|
+
actionType: string;
|
|
17
|
+
actionSummary: string;
|
|
18
|
+
actionPayloadHash: string;
|
|
19
|
+
returnTo: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
loginIntentId: string;
|
|
22
|
+
publicLoginUrl: string;
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
}>;
|
|
25
|
+
getStatus(input: {
|
|
26
|
+
loginIntentId: string;
|
|
27
|
+
deviceSecret: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
status: DeviceAuthorizationStatus;
|
|
30
|
+
actionPayloadHash: string;
|
|
31
|
+
}>;
|
|
32
|
+
claim(input: {
|
|
33
|
+
loginIntentId: string;
|
|
34
|
+
deviceSecret: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
status: 'claimed';
|
|
37
|
+
actionPayloadHash: string;
|
|
38
|
+
machineId?: string;
|
|
39
|
+
machineToken?: string;
|
|
40
|
+
}>;
|
|
41
|
+
cancel(input: {
|
|
42
|
+
loginIntentId: string;
|
|
43
|
+
deviceSecret: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
status: string;
|
|
46
|
+
}>;
|
|
47
|
+
private request;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class a extends Error{code;constructor(t,n){super(n),this.code=t,this.name="DeviceAuthorizationHttpError"}}class c{fetchImpl;baseUrl;constructor(t,n=fetch){this.fetchImpl=n,this.baseUrl=t.replace(/\/+$/,"")}async create(t){const e=await this.request("/auth/device-authorizations",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)});if(!e.loginIntentId||!e.publicLoginUrl||!e.expiresAt)throw new a("invalid_create_response","Invalid device authorization response.");return e}async getStatus(t){const e=await this.request(`/auth/device-authorizations/${encodeURIComponent(t.loginIntentId)}/status`,{headers:o(t.deviceSecret)});if(!["waiting_user","approved","denied","expired","claimed"].includes(e.status??"")||typeof e.actionPayloadHash!="string")throw new a("invalid_status_response","Invalid device authorization status.");return e}async claim(t){const e=await this.request(`/auth/device-authorizations/${encodeURIComponent(t.loginIntentId)}/claim`,{method:"POST",headers:o(t.deviceSecret)});if(e.status!=="claimed"||typeof e.actionPayloadHash!="string")throw new a("invalid_claim_response","Invalid device authorization claim.");return e}async cancel(t){const e=await this.request(`/auth/device-authorizations/${encodeURIComponent(t.loginIntentId)}/cancel`,{method:"POST",headers:o(t.deviceSecret)});if(typeof e.status!="string")throw new a("invalid_cancel_response","Invalid device authorization cancellation.");return{status:e.status}}async request(t,n){let e;try{e=await this.fetchImpl(`${this.baseUrl}${t}`,{...n,signal:AbortSignal.timeout(1e4)})}catch{throw new a("authorization_server_unavailable","Shennian authorization server is unavailable.")}if(!e.ok){let r=`authorization_http_${e.status}`;try{const s=await e.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(r=s.error)}catch{}throw new a(r,"Shennian device authorization request failed.")}try{return await e.json()}catch{throw new a("invalid_authorization_response","Invalid Shennian authorization response.")}}}function o(i){return{"x-shennian-device-secret":i}}export{a as DeviceAuthorizationHttpError,c as HttpDeviceAuthorizationClient};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
|
|
2
|
+
import { RoomHostSessionObservationStore } from './host-session-observations.js';
|
|
3
|
+
import type { RoomSessionRegistry } from './session-registry.js';
|
|
4
|
+
export declare function createRoomHostSessionObservationIpcHandlers(input: {
|
|
5
|
+
store: RoomHostSessionObservationStore;
|
|
6
|
+
registry?: RoomSessionRegistry;
|
|
7
|
+
getMachineId: () => string | undefined;
|
|
8
|
+
onRegistrationIdle?: (registryKey: string) => void;
|
|
9
|
+
}): Record<string, DaemonIpcHandler>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import d from"node:path";import{DaemonIpcError as a}from"../daemon-ipc/protocol.js";function u(e){return{"room-host-session.observe":t=>{const r=h(t),s=e.getMachineId()?.trim();if(!s)throw new a("machine_not_authorized","This machine is not authorized yet.");const i=e.store.observe({machineId:s,runtimeAdapter:y(r.runtimeAdapter),sourceSessionKey:o(r.sourceSessionKey,"sourceSessionKey",1024),hostContextHash:c(r.hostContextHash,"hostContextHash"),workspaceHash:r.workspaceHash==null?null:c(r.workspaceHash,"workspaceHash"),workspacePath:l(r.workspacePath),lifecycleState:f(r.lifecycleState),eventName:o(r.eventName,"eventName",64)}),n=e.registry?.getBySource({machineId:s,runtimeAdapter:i.runtimeAdapter,sourceSessionKey:i.sourceSessionKey});return n&&(e.registry.updateLifecycle(n.registryKey,i.lifecycleState==="active"?"active":i.lifecycleState==="idle"?"inactive":"closed"),i.lifecycleState==="idle"&&e.onRegistrationIdle?.(n.registryKey),i.lifecycleState==="idle"&&n.binding?.activeActivation&&e.registry.clearActiveActivation(n.registryKey)),{observationKey:i.observationKey,runtimeAdapter:i.runtimeAdapter,lifecycleState:i.lifecycleState,registrationEpoch:i.registrationEpoch,lastSeenAt:i.lastSeenAt}}}}function l(e){if(e==null||e==="")return null;const t=o(e,"workspacePath",4096);if(!d.isAbsolute(t))throw new a("invalid_params","workspacePath is invalid.");return t}function h(e){if(!e||Array.isArray(e)||typeof e!="object")throw new a("invalid_params","Room host Session observation parameters are required.");return e}function o(e,t,r){if(typeof e!="string"||!e.trim()||e.length>r)throw new a("invalid_params",`${t} is invalid.`);return e.trim()}function c(e,t){const r=o(e,t,64);if(!/^[a-f0-9]{64}$/.test(r))throw new a("invalid_params",`${t} is invalid.`);return r}function y(e){const t=o(e,"runtimeAdapter",32);if(t!=="codex"&&t!=="claude_code"&&t!=="workbuddy")throw new a("invalid_params","runtimeAdapter is unsupported.");return t}function f(e){const t=o(e,"lifecycleState",16);if(t!=="active"&&t!=="idle"&&t!=="closed")throw new a("invalid_params","lifecycleState is invalid.");return t}export{u as createRoomHostSessionObservationIpcHandlers};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { RoomRuntimeAdapter } from '@shennian/wire';
|
|
2
|
+
export type RoomHostSessionLifecycle = 'active' | 'idle' | 'closed';
|
|
3
|
+
export type RoomHostSessionObservation = {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
observationKey: string;
|
|
6
|
+
machineId: string;
|
|
7
|
+
runtimeAdapter: RoomRuntimeAdapter;
|
|
8
|
+
sourceSessionKey: string;
|
|
9
|
+
hostContextHash: string;
|
|
10
|
+
workspaceHash: string | null;
|
|
11
|
+
workspacePath: string | null;
|
|
12
|
+
lifecycleState: RoomHostSessionLifecycle;
|
|
13
|
+
registrationEpoch: number;
|
|
14
|
+
lastEventName: string;
|
|
15
|
+
observedAt: string;
|
|
16
|
+
lastSeenAt: string;
|
|
17
|
+
lastActiveAt: string | null;
|
|
18
|
+
lastIdleAt: string | null;
|
|
19
|
+
closedAt: string | null;
|
|
20
|
+
};
|
|
21
|
+
export type ObserveRoomHostSessionInput = {
|
|
22
|
+
machineId: string;
|
|
23
|
+
runtimeAdapter: RoomRuntimeAdapter;
|
|
24
|
+
sourceSessionKey: string;
|
|
25
|
+
hostContextHash: string;
|
|
26
|
+
workspaceHash?: string | null;
|
|
27
|
+
workspacePath?: string | null;
|
|
28
|
+
lifecycleState: RoomHostSessionLifecycle;
|
|
29
|
+
eventName: string;
|
|
30
|
+
now?: Date;
|
|
31
|
+
};
|
|
32
|
+
export declare class RoomHostSessionObservationError extends Error {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
constructor(code: string, message: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class RoomHostSessionObservationStore {
|
|
37
|
+
readonly filePath: string;
|
|
38
|
+
private state;
|
|
39
|
+
constructor(filePath?: string);
|
|
40
|
+
observe(input: ObserveRoomHostSessionInput): RoomHostSessionObservation;
|
|
41
|
+
getBySource(input: {
|
|
42
|
+
machineId: string;
|
|
43
|
+
runtimeAdapter: RoomRuntimeAdapter;
|
|
44
|
+
sourceSessionKey: string;
|
|
45
|
+
}): RoomHostSessionObservation | null;
|
|
46
|
+
list(): RoomHostSessionObservation[];
|
|
47
|
+
private load;
|
|
48
|
+
private persist;
|
|
49
|
+
}
|
|
50
|
+
export declare function buildRoomHostSessionObservationKey(input: {
|
|
51
|
+
machineId: string;
|
|
52
|
+
runtimeAdapter: RoomRuntimeAdapter;
|
|
53
|
+
sourceSessionKey: string;
|
|
54
|
+
}): string;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import S from"node:crypto";import r from"node:fs";import u from"node:path";import{resolveShennianPath as P}from"../config/index.js";const h=/^[a-f0-9]{64}$/,H=/^[A-Za-z][A-Za-z0-9]{0,63}$/,x=new Set(["codex","claude_code","workbuddy"]);class n extends Error{code;constructor(t,o){super(o),this.code=t,this.name="RoomHostSessionObservationError"}}class ${filePath;state;constructor(t=P("runtime","room-host-session-observations.json")){this.filePath=t,this.state=this.load()}observe(t){p(t);const o=f(t),s=this.state.records[o],a=(t.now??new Date).toISOString(),l=!!(s&&t.eventName==="SessionStart"&&(s.lifecycleState==="closed"||s.hostContextHash!==t.hostContextHash)),c=s?.lifecycleState==="closed"&&t.eventName!=="SessionStart"?"closed":t.lifecycleState,k=l?null:s?.lastActiveAt??null,_=l?null:s?.lastIdleAt??null,d={schemaVersion:1,observationKey:o,machineId:t.machineId,runtimeAdapter:t.runtimeAdapter,sourceSessionKey:t.sourceSessionKey,hostContextHash:t.hostContextHash,workspaceHash:t.workspaceHash??s?.workspaceHash??null,workspacePath:t.workspacePath??s?.workspacePath??null,lifecycleState:c,registrationEpoch:s?s.registrationEpoch+(l?1:0):1,lastEventName:t.eventName,observedAt:s?.observedAt??a,lastSeenAt:a,lastActiveAt:c==="active"?a:k,lastIdleAt:c==="idle"?a:_,closedAt:c==="closed"?s?.closedAt??a:null};return this.state.records[o]=d,this.persist(),m(d)}getBySource(t){const o=this.state.records[f(t)];return o?m(o):null}list(){return Object.values(this.state.records).sort((t,o)=>t.observedAt.localeCompare(o.observedAt)).map(m)}load(){try{const t=r.lstatSync(this.filePath);if(!t.isFile()||t.isSymbolicLink())throw b();return process.platform!=="win32"&&(t.mode&63)!==0&&r.chmodSync(this.filePath,384),I(JSON.parse(r.readFileSync(this.filePath,"utf8")))}catch(t){if(t.code==="ENOENT")return{schemaVersion:1,records:{}};throw t instanceof n?t:new n("corrupt_observation_store","Room host Session observations are corrupt.")}}persist(){const t=u.dirname(this.filePath);r.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(t,448);const o=`${this.filePath}.tmp-${process.pid}-${S.randomBytes(6).toString("hex")}`;try{if(r.writeFileSync(o,`${JSON.stringify(this.state,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(o,384),process.platform==="win32"&&r.existsSync(this.filePath)){const s=r.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw b();r.unlinkSync(this.filePath)}r.renameSync(o,this.filePath),process.platform!=="win32"&&r.chmodSync(this.filePath,384)}finally{try{r.unlinkSync(o)}catch{}}}}function f(e){return v(e.machineId),y(e.runtimeAdapter),A(e.sourceSessionKey),S.createHash("sha256").update(`${e.machineId}\0${e.runtimeAdapter}\0${e.sourceSessionKey}`,"utf8").digest("hex")}function p(e){v(e.machineId),y(e.runtimeAdapter),A(e.sourceSessionKey),h.test(e.hostContextHash)||i("host_context_hash"),e.workspaceHash!=null&&!h.test(e.workspaceHash)&&i("workspace_hash"),e.workspacePath!=null&&w(e.workspacePath),["active","idle","closed"].includes(e.lifecycleState)||i("lifecycle_state"),H.test(e.eventName)||i("event_name")}function v(e){/^[A-Za-z0-9_.:@/-]{1,512}$/.test(e)||i("machine_id")}function y(e){x.has(e)||i("runtime_adapter")}function A(e){(!e||e.length>1024||/[\0\r\n]/.test(e))&&i("source_session_key")}function I(e){if(!e||typeof e!="object"||e.schemaVersion!==1)throw new n("corrupt_observation_store","Unsupported observation store.");const t=e.records;if(!t||typeof t!="object"||Array.isArray(t))throw new n("corrupt_observation_store","Invalid observation records.");const o={};for(const[s,a]of Object.entries(t)){if(g(a),s!==a.observationKey||s!==f(a))throw new n("corrupt_observation_store","Observation key mismatch.");o[s]={...a,workspacePath:a.workspacePath??null,lastActiveAt:a.lastActiveAt??(a.lifecycleState==="active"?a.lastSeenAt:null),lastIdleAt:a.lastIdleAt??(a.lifecycleState==="idle"?a.lastSeenAt:null)}}return{schemaVersion:1,records:o}}function g(e){(!e||e.schemaVersion!==1)&&i("observation"),p({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:e.hostContextHash,workspaceHash:e.workspaceHash,workspacePath:e.workspacePath??null,lifecycleState:e.lifecycleState,eventName:e.lastEventName}),h.test(e.observationKey)||i("observation_key"),(!Number.isInteger(e.registrationEpoch)||e.registrationEpoch<1)&&i("registration_epoch");for(const t of[e.observedAt,e.lastSeenAt])Number.isFinite(Date.parse(t))||i("observation_time");for(const t of[e.lastActiveAt,e.lastIdleAt])t!=null&&!Number.isFinite(Date.parse(t))&&i("transition_time");e.closedAt!=null&&!Number.isFinite(Date.parse(e.closedAt))&&i("closed_at"),e.workspacePath!=null&&w(e.workspacePath)}function w(e){(!u.isAbsolute(e)||e.length>4096||/[\0\r\n]/.test(e))&&i("workspace_path")}function i(e){throw new n(`invalid_${e}`,`Invalid ${e}.`)}function b(){return new n("unsafe_observation_store","Room host Session observation store is not a regular file.")}function m(e){return structuredClone(e)}export{n as RoomHostSessionObservationError,$ as RoomHostSessionObservationStore,f as buildRoomHostSessionObservationKey};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type FetchLike = typeof fetch;
|
|
2
|
+
export type TrustedRoomAgentBootstrap = {
|
|
3
|
+
documentationUrl: 'https://shennian.net/agent-install.md';
|
|
4
|
+
installCommand: 'npm install -g shennian@latest';
|
|
5
|
+
upgradeCommand: 'shennian upgrade';
|
|
6
|
+
minimumCliVersion: string;
|
|
7
|
+
actions: readonly [
|
|
8
|
+
'room.create',
|
|
9
|
+
'room.join',
|
|
10
|
+
'room.read',
|
|
11
|
+
'room.send',
|
|
12
|
+
'room.mode',
|
|
13
|
+
'room.status'
|
|
14
|
+
];
|
|
15
|
+
supportedRuntimes: readonly ['codex', 'claude_code', 'workbuddy'];
|
|
16
|
+
};
|
|
17
|
+
export type RoomClientPreparation = {
|
|
18
|
+
status: 'ready';
|
|
19
|
+
} | {
|
|
20
|
+
status: 'install_required';
|
|
21
|
+
documentationUrl: string;
|
|
22
|
+
command: string;
|
|
23
|
+
} | {
|
|
24
|
+
status: 'upgrade_required';
|
|
25
|
+
documentationUrl: string;
|
|
26
|
+
command: string;
|
|
27
|
+
};
|
|
28
|
+
export declare class RoomInviteBootstrapError extends Error {
|
|
29
|
+
readonly code: string;
|
|
30
|
+
constructor(code: string, message: string);
|
|
31
|
+
}
|
|
32
|
+
export declare function fetchTrustedRoomAgentBootstrap(fetchImpl?: FetchLike): Promise<TrustedRoomAgentBootstrap>;
|
|
33
|
+
export declare function validateTrustedRoomAgentBootstrap(value: unknown): TrustedRoomAgentBootstrap;
|
|
34
|
+
export declare function resolveRoomClientPreparation(currentVersion: string | null, bootstrap: TrustedRoomAgentBootstrap): RoomClientPreparation;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const c="https://shennian.net/api/room-invites/agent-bootstrap",a=["room.create","room.join","room.read","room.send","room.mode","room.status"],s=["codex","claude_code","workbuddy"];class r extends Error{code;constructor(n,t){super(t),this.code=n,this.name="RoomInviteBootstrapError"}}async function h(o=fetch){let n;try{n=await o(c,{headers:{accept:"application/json"},redirect:"error",signal:AbortSignal.timeout(1e4)})}catch{throw new r("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.")}if(!n.ok)throw new r("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.");let t;try{t=await n.json()}catch{throw i()}return d(t)}function d(o){if(!o||typeof o!="object"||Array.isArray(o))throw i();const n=o,t=m(n.install),e=m(n.roomTool);if(n.schemaVersion!==1||n.product!=="shennian"||n.inviteKind!=="room"||t.documentationUrl!=="https://shennian.net/agent-install.md"||t.npmPackage!=="shennian"||t.distTag!=="latest"||t.installCommand!=="npm install -g shennian@latest"||t.upgradeCommand!=="shennian upgrade"||typeof t.minimumCliVersion!="string"||!/^\d+\.\d+\.\d+$/.test(t.minimumCliVersion)||e.schemaVersion!==1||!l(e.actions,a)||!l(e.supportedRuntimes,s))throw i();return{documentationUrl:"https://shennian.net/agent-install.md",installCommand:"npm install -g shennian@latest",upgradeCommand:"shennian upgrade",minimumCliVersion:t.minimumCliVersion,actions:a,supportedRuntimes:s}}function f(o,n){if(o===null)return{status:"install_required",documentationUrl:n.documentationUrl,command:n.installCommand};const t=u(o),e=u(n.minimumCliVersion);if(!t||!e)throw new r("invalid_local_cli_version","The installed Shennian CLI version could not be verified.");return p(t,e)<0?{status:"upgrade_required",documentationUrl:n.documentationUrl,command:n.upgradeCommand}:{status:"ready"}}function m(o){if(!o||typeof o!="object"||Array.isArray(o))throw i();return o}function l(o,n){return Array.isArray(o)&&o.length===n.length&&o.every((t,e)=>t===n[e])}function u(o){const n=/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(o.trim());if(!n)return null;const t=[Number(n[1]),Number(n[2]),Number(n[3])];return t.every(Number.isSafeInteger)?t:null}function p(o,n){for(let t=0;t<3;t+=1)if(o[t]!==n[t])return o[t]-n[t];return 0}function i(){return new r("invalid_room_bootstrap","Official Shennian Room setup information failed validation.")}export{r as RoomInviteBootstrapError,h as fetchTrustedRoomAgentBootstrap,f as resolveRoomClientPreparation,d as validateTrustedRoomAgentBootstrap};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
|
|
2
|
+
import { RoomInvocationTokenAuthority } from './invocation-token.js';
|
|
3
|
+
export declare function createRoomInvocationTokenIpcHandlers(input: {
|
|
4
|
+
authority: RoomInvocationTokenAuthority;
|
|
5
|
+
getMachineCredentials: () => {
|
|
6
|
+
machineId?: string;
|
|
7
|
+
machineToken?: string;
|
|
8
|
+
};
|
|
9
|
+
}): Record<string, DaemonIpcHandler>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";import{buildRoomBindingActionParameters as c}from"./room-binding-ipc.js";import{normalizeRoomActionParameters as d}from"./room-action-schema.js";function p(o){return{"room-invocation.issue":t=>{const i=m(t),n=o.getMachineCredentials();if(!n.machineId?.trim()||!n.machineToken?.trim())throw new r("machine_not_authorized","This machine is not authorized yet.");const e=l(i.action);return o.authority.issue({machineId:n.machineId,machineCredential:n.machineToken,registryKey:a(i.registryKey,"registryKey",64),action:e,targetTool:a(i.targetTool,"targetTool",64),actionParameters:e==="room.bind"?s(i.actionParameters):d(e,i.actionParameters)})}}}function s(o){const t=m(o),i=t.activationCapability??"notify_only",n=t.activationMode??"auto_follow";if(i!=="managed"&&i!=="idle_resume"&&i!=="notify_only")throw new r("invalid_params","activationCapability is invalid.");if(n!=="auto_follow"&&n!=="mention_only"&&n!=="paused")throw new r("invalid_params","activationMode is invalid.");return c({roomId:a(t.roomId,"roomId",191),agentIdentityId:a(t.agentIdentityId,"agentIdentityId",191),activationCapability:i,activationMode:n})}function m(o){if(!o||Array.isArray(o)||typeof o!="object")throw new r("invalid_params","Room invocation parameters are required.");return o}function a(o,t,i){if(typeof o!="string"||!o.trim()||o.length>i)throw new r("invalid_params",`${t} is invalid.`);return o.trim()}function l(o){const t=a(o,"action",64);if(!["room.bind","room.create","room.join","room.read","room.send","room.mode","room.status"].includes(t))throw new r("invalid_params","Room invocation action is unsupported.");return t}export{p as createRoomInvocationTokenIpcHandlers};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RoomRuntimeAdapter } from '@shennian/wire';
|
|
2
|
+
import { type JsonValue } from './pending-authorized-actions.js';
|
|
3
|
+
import { RoomSessionRegistry } from './session-registry.js';
|
|
4
|
+
export type RoomInvocationAction = 'room.bind' | 'room.create' | 'room.join' | 'room.read' | 'room.send' | 'room.mode' | 'room.status';
|
|
5
|
+
export type RoomInvocationClaims = {
|
|
6
|
+
invocationId: string;
|
|
7
|
+
bindingIntentId?: string;
|
|
8
|
+
machineId: string;
|
|
9
|
+
accountCredentialHash: string;
|
|
10
|
+
registryKey: string;
|
|
11
|
+
runtimeAdapter: RoomRuntimeAdapter;
|
|
12
|
+
sourceSessionKeyHash: string;
|
|
13
|
+
nianSessionId: string;
|
|
14
|
+
action: RoomInvocationAction;
|
|
15
|
+
targetTool: string;
|
|
16
|
+
actionParametersHash: string;
|
|
17
|
+
issuedAt: string;
|
|
18
|
+
expiresAt: string;
|
|
19
|
+
};
|
|
20
|
+
type IssueInput = {
|
|
21
|
+
machineId: string;
|
|
22
|
+
machineCredential: string;
|
|
23
|
+
registryKey: string;
|
|
24
|
+
action: RoomInvocationAction;
|
|
25
|
+
targetTool: string;
|
|
26
|
+
actionParameters: JsonValue;
|
|
27
|
+
ttlMs?: number;
|
|
28
|
+
now?: Date;
|
|
29
|
+
};
|
|
30
|
+
type ConsumeInput = Omit<IssueInput, 'ttlMs'> & {
|
|
31
|
+
invocationToken: string;
|
|
32
|
+
};
|
|
33
|
+
export declare class RoomInvocationTokenError extends Error {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
constructor(code: string, message: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Process-local by design. Outstanding tokens become invalid when the daemon
|
|
39
|
+
* restarts, which is safer than persisting another reusable capability.
|
|
40
|
+
*/
|
|
41
|
+
export declare class RoomInvocationTokenAuthority {
|
|
42
|
+
private readonly registry;
|
|
43
|
+
private readonly records;
|
|
44
|
+
constructor(registry: RoomSessionRegistry);
|
|
45
|
+
issue(input: IssueInput): {
|
|
46
|
+
invocationToken: string;
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
};
|
|
49
|
+
consume(input: ConsumeInput): RoomInvocationClaims;
|
|
50
|
+
private validateContext;
|
|
51
|
+
private prune;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import h from"node:crypto";import{canonicalizeActionPayload as u,hashActionPayload as v}from"./pending-authorized-actions.js";const f="sni_v1_",y=10*6e4,w=5e3,T=15*6e4,I=16,S=/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/,A=new Set(["room.bind","room.create","room.join","room.read","room.send","room.mode","room.status"]);class n extends Error{code;constructor(e,o){super(o),this.code=e,this.name="RoomInvocationTokenError"}}class b{registry;records=new Map;constructor(e){this.registry=e}issue(e){const o=e.now??new Date;this.prune(o);const t=this.validateContext(e);if([...this.records.values()].filter(g=>g.registryKey===e.registryKey&&g.status==="issued").length>=I)throw new n("invocation_rate_limited","Too many active Room invocations for this Agent session.");const s=e.ttlMs??y;if(!Number.isInteger(s)||s<w||s>T)throw new n("invalid_invocation_ttl","Room invocation TTL is invalid.");const d=`${f}${h.randomBytes(32).toString("base64url")}`,c=r(d),l=o.toISOString(),m=new Date(o.getTime()+s).toISOString(),_=h.randomUUID();return this.records.set(c,{invocationId:_,...e.action==="room.bind"||e.action==="room.create"||e.action==="room.join"?{bindingIntentId:`${e.action.replace(".","-")}:${_}`}:{},machineId:e.machineId,accountCredentialHash:r(e.machineCredential),registryKey:e.registryKey,runtimeAdapter:t.runtimeAdapter,sourceSessionKeyHash:r(t.sourceSessionKey),nianSessionId:t.nianSessionId,action:e.action,targetTool:e.targetTool,actionParametersHash:v(e.actionParameters),tokenHash:c,status:"issued",issuedAt:l,expiresAt:m}),{invocationToken:d,expiresAt:m}}consume(e){const o=e.now??new Date,t=r(e.invocationToken),i=this.records.get(t);if(!i||!e.invocationToken.startsWith(f))throw new n("invocation_token_invalid","Room invocation token is invalid.");if(i.status!=="issued")throw new n("invocation_token_replayed","Room invocation token was already used.");if(new Date(i.expiresAt).getTime()<=o.getTime())throw this.records.delete(t),new n("invocation_token_expired","Room invocation token expired.");i.status="consumed",i.consumedAt=o.toISOString();const s=this.validateContext(e),d={machineId:e.machineId,accountCredentialHash:r(e.machineCredential),registryKey:e.registryKey,runtimeAdapter:s.runtimeAdapter,sourceSessionKeyHash:r(s.sourceSessionKey),nianSessionId:s.nianSessionId,action:e.action,targetTool:e.targetTool,actionParametersHash:v(e.actionParameters)};for(const[c,l]of Object.entries(d)){const m=i[c];if(typeof m!="string"||!k(m,l))throw new n("invocation_context_mismatch",`Room invocation ${c} does not match.`)}return x(i)}validateContext(e){if(!e.machineId.trim()||!e.machineCredential.trim())throw new n("machine_not_authorized","This machine is not authorized yet.");if(!A.has(e.action))throw new n("invalid_invocation_action","Unsupported Room invocation action.");if(!S.test(e.targetTool))throw new n("invalid_target_tool","Invalid Room target tool.");u(e.actionParameters);const o=this.registry.getByRegistryKey(e.registryKey);if(!o||o.lifecycleState!=="active")throw new n("room_session_unavailable","The exact Agent session is not active.");if(o.machineId!==e.machineId)throw new n("room_session_machine_mismatch","The Agent session belongs to another machine.");if(!o.capabilities.canIdentifySession)throw new n("room_session_unverifiable","The Agent runtime cannot verify its current session.");return o}prune(e){for(const[o,t]of this.records)new Date(t.expiresAt).getTime()<=e.getTime()&&this.records.delete(o)}}function x(a){const{tokenHash:e,status:o,consumedAt:t,...i}=a;return structuredClone(i)}function r(a){return h.createHash("sha256").update(a,"utf8").digest("hex")}function k(a,e){const o=Buffer.from(a,"utf8"),t=Buffer.from(e,"utf8");return o.length===t.length&&h.timingSafeEqual(o,t)}export{b as RoomInvocationTokenAuthority,n as RoomInvocationTokenError};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RoomMessageView } from '@shennian/wire';
|
|
2
|
+
type MirrorState = {
|
|
3
|
+
schemaVersion: 1;
|
|
4
|
+
roomId: string;
|
|
5
|
+
lastSyncedSequence: string;
|
|
6
|
+
messageCount: number;
|
|
7
|
+
lastError: string | null;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
};
|
|
10
|
+
export type RoomMirrorEvent = {
|
|
11
|
+
schemaVersion: 1;
|
|
12
|
+
roomId: string;
|
|
13
|
+
sequence: string;
|
|
14
|
+
messageId: string;
|
|
15
|
+
kind: RoomMessageView['kind'];
|
|
16
|
+
author: {
|
|
17
|
+
type: RoomMessageView['author']['principalType'];
|
|
18
|
+
membershipId: string;
|
|
19
|
+
ownerUserId: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
ownerDisplayName: string | null;
|
|
22
|
+
};
|
|
23
|
+
mentions: NonNullable<RoomMessageView['mentions']>;
|
|
24
|
+
text: string | null;
|
|
25
|
+
replyToMessageId: string | null;
|
|
26
|
+
attachments: RoomMessageView['attachments'];
|
|
27
|
+
createdAt: string;
|
|
28
|
+
editedAt: string | null;
|
|
29
|
+
deletedAt: string | null;
|
|
30
|
+
activationCause: RoomMessageView['activationCause'];
|
|
31
|
+
};
|
|
32
|
+
export declare class RoomJsonlMirrorError extends Error {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
constructor(code: string, message: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class RoomJsonlMirror {
|
|
37
|
+
readonly roomId: string;
|
|
38
|
+
readonly directory: string;
|
|
39
|
+
readonly metadataPath: string;
|
|
40
|
+
readonly messagesPath: string;
|
|
41
|
+
readonly statePath: string;
|
|
42
|
+
readonly attachmentsDirectory: string;
|
|
43
|
+
constructor(roomId: string, rootDirectory?: string);
|
|
44
|
+
append(input: {
|
|
45
|
+
messages: RoomMessageView[];
|
|
46
|
+
historyFromSequence: string;
|
|
47
|
+
roomName?: string | null;
|
|
48
|
+
now?: Date;
|
|
49
|
+
}): {
|
|
50
|
+
appended: number;
|
|
51
|
+
lastSyncedSequence: string;
|
|
52
|
+
};
|
|
53
|
+
rebuild(input: {
|
|
54
|
+
messages: RoomMessageView[];
|
|
55
|
+
historyFromSequence: string;
|
|
56
|
+
roomName?: string | null;
|
|
57
|
+
now?: Date;
|
|
58
|
+
}): MirrorState;
|
|
59
|
+
markAccessRevoked(now?: Date): void;
|
|
60
|
+
recordSyncError(code: string, now?: Date): void;
|
|
61
|
+
readState(): MirrorState;
|
|
62
|
+
search(input: {
|
|
63
|
+
query: string;
|
|
64
|
+
limit?: number;
|
|
65
|
+
}): RoomMirrorEvent[];
|
|
66
|
+
private ensureLayout;
|
|
67
|
+
private recoverPartialLine;
|
|
68
|
+
private reconcileStateFromMessages;
|
|
69
|
+
private readMetadata;
|
|
70
|
+
private writeMetadata;
|
|
71
|
+
private writeState;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import p from"node:crypto";import n from"node:fs";import l from"node:path";import{StringDecoder as L}from"node:string_decoder";import{resolveShennianPath as k}from"../config/index.js";const C=/^[A-Za-z0-9_.:@-]{1,191}$/,w=2*1024*1024;class a extends Error{code;constructor(e,r){super(r),this.code=e,this.name="RoomJsonlMirrorError"}}class z{roomId;directory;metadataPath;messagesPath;statePath;attachmentsDirectory;constructor(e,r=k("rooms")){this.roomId=e,b(e,"room_id"),this.directory=l.join(r,e),this.metadataPath=l.join(this.directory,"metadata.json"),this.messagesPath=l.join(this.directory,"messages.jsonl"),this.statePath=l.join(this.directory,"state.json"),this.attachmentsDirectory=l.join(this.directory,"attachments"),this.ensureLayout(),this.recoverPartialLine(),this.reconcileStateFromMessages()}append(e){const r=u(e.historyFromSequence),o=this.readState(),i=BigInt(o.lastSyncedSequence),s=[];let c=i;for(const O of e.messages){const I=q(O,this.roomId),f=BigInt(I.sequence);if(!(f<=i)){if(f<=c)throw new a("room_mirror_sequence_order","Room mirror messages must be strictly ordered.");s.push(I),c=f}}const m=e.now??new Date;if(this.writeMetadata({schemaVersion:1,roomId:this.roomId,roomName:e.roomName?.trim()||this.readMetadata()?.roomName||null,historyFromSequence:r,access:"active",updatedAt:m.toISOString()}),s.length===0)return{appended:0,lastSyncedSequence:o.lastSyncedSequence};const d=s.map(v).join(""),S=l.join(this.directory,`.messages-batch-${process.pid}-${p.randomBytes(8).toString("hex")}.jsonl`);try{y(S,d,"wx"),_(S,this.roomId),$(this.messagesPath,d)}finally{try{n.unlinkSync(S)}catch{}}const g={schemaVersion:1,roomId:this.roomId,lastSyncedSequence:s.at(-1).sequence,messageCount:o.messageCount+s.length,lastError:null,updatedAt:m.toISOString()};return this.writeState(g),{appended:s.length,lastSyncedSequence:g.lastSyncedSequence}}rebuild(e){u(e.historyFromSequence);const r=e.messages.map(m=>q(m,this.roomId));x(r);const o=r.map(v).join(""),i=l.join(this.directory,`.messages-rebuild-${process.pid}-${p.randomBytes(8).toString("hex")}.jsonl`);try{y(i,o,"wx"),_(i,this.roomId),P(i,this.messagesPath)}finally{try{n.unlinkSync(i)}catch{}}const s=e.now??new Date;this.writeMetadata({schemaVersion:1,roomId:this.roomId,roomName:e.roomName?.trim()||null,historyFromSequence:e.historyFromSequence,access:"active",updatedAt:s.toISOString()});const c={schemaVersion:1,roomId:this.roomId,lastSyncedSequence:r.at(-1)?.sequence??"0",messageCount:r.length,lastError:null,updatedAt:s.toISOString()};return this.writeState(c),c}markAccessRevoked(e=new Date){const r=this.readMetadata();this.writeMetadata({schemaVersion:1,roomId:this.roomId,roomName:r?.roomName??null,historyFromSequence:r?.historyFromSequence??"1",access:"revoked",updatedAt:e.toISOString()})}recordSyncError(e,r=new Date){const o=this.readState();this.writeState({...o,lastError:e.replace(/[^a-z0-9_-]/gi,"_").slice(0,120)||"room_sync_failed",updatedAt:r.toISOString()})}readState(){return F(this.statePath,this.roomId)}search(e){const r=e.query.trim().toLocaleLowerCase(),o=e.limit??20;if(!r||r.length>200)throw new a("invalid_room_search_query","Invalid Room search query.");if(!Number.isInteger(o)||o<1||o>100)throw new a("invalid_room_search_limit","Invalid Room search limit.");const i=[];return R(this.messagesPath,s=>{const c=N(s,this.roomId);c.deletedAt||!D(c).toLocaleLowerCase().includes(r)||(i.push(c),i.length>o&&i.shift())}),i.reverse()}ensureLayout(){A(this.directory),A(this.attachmentsDirectory),n.existsSync(this.messagesPath)||y(this.messagesPath,"","wx")}recoverPartialLine(){const e=h(this.messagesPath);if(e.size===0)return;const r=n.openSync(this.messagesPath,"r+");try{const o=Buffer.alloc(1);if(n.readSync(r,o,0,1,e.size-1),o[0]===10)return;const i=64*1024;let s=e.size;for(;s>0;){const c=Math.min(i,s);s-=c;const m=Buffer.alloc(c);n.readSync(r,m,0,c,s);const d=m.lastIndexOf(10);if(d>=0){n.ftruncateSync(r,s+d+1),n.fsyncSync(r);return}}n.ftruncateSync(r,0),n.fsyncSync(r)}finally{n.closeSync(r)}}reconcileStateFromMessages(){const e=_(this.messagesPath,this.roomId),r=F(this.statePath,this.roomId);(r.lastSyncedSequence!==e.lastSequence||r.messageCount!==e.messageCount)&&this.writeState({...r,lastSyncedSequence:e.lastSequence,messageCount:e.messageCount,updatedAt:new Date().toISOString()})}readMetadata(){if(!n.existsSync(this.metadataPath))return null;h(this.metadataPath);const e=JSON.parse(n.readFileSync(this.metadataPath,"utf8"));if(e.schemaVersion!==1||e.roomId!==this.roomId||e.access!=="active"&&e.access!=="revoked"||typeof e.historyFromSequence!="string")throw new a("corrupt_room_mirror_metadata","Room mirror metadata is corrupt.");return u(e.historyFromSequence),e}writeMetadata(e){M(this.metadataPath,e)}writeState(e){M(this.statePath,e)}}function q(t,e){if(t.roomId!==e)throw new a("room_mirror_room_mismatch","Room mirror message mismatch.");if(b(t.id,"message_id"),u(t.sequence),!t.author||!["human","agent"].includes(t.author.principalType))throw new a("invalid_room_mirror_message","Invalid Room message author.");const r=t.attachments.map(o=>{if(o.downloadRef!==`shennian-room-attachment:${o.attachmentId}`||!/^[a-f0-9]{64}$/.test(o.sha256)||!/^\d+$/.test(o.byteSize))throw new a("invalid_room_mirror_attachment","Invalid Room attachment metadata.");return{...o}});return{schemaVersion:1,roomId:e,sequence:t.sequence,messageId:t.id,kind:t.kind,author:{type:t.author.principalType,membershipId:t.author.membershipId,ownerUserId:t.author.ownerUserId,displayName:t.author.displayName,ownerDisplayName:t.author.ownerDisplayName??null},mentions:[...t.mentions??[]],text:t.text??null,replyToMessageId:t.replyToMessageId??null,attachments:r,createdAt:t.createdAt,editedAt:t.editedAt??null,deletedAt:t.deletedAt??null,activationCause:t.activationCause??null}}function v(t){const e=`${JSON.stringify(t)}
|
|
2
|
+
`;if(Buffer.byteLength(e,"utf8")>w)throw new a("room_mirror_line_too_large","Room mirror event is too large.");return e}function x(t){let e=0n;const r=new Set;for(const o of t){const i=BigInt(o.sequence);if(i<=e||r.has(o.messageId))throw new a("room_mirror_sequence_order","Room mirror messages must be unique and strictly ordered.");e=i,r.add(o.messageId)}}function _(t,e){h(t);const r=[];return R(t,o=>r.push(N(o,e))),x(r),{lastSequence:r.at(-1)?.sequence??"0",messageCount:r.length}}function R(t,e){h(t);const r=n.openSync(t,"r"),o=Buffer.alloc(64*1024),i=new L("utf8");let s="";try{for(;;){const c=n.readSync(r,o,0,o.length,null);if(c===0)break;if(s+=i.write(o.subarray(0,c)),Buffer.byteLength(s,"utf8")>w+o.length)throw new a("room_mirror_line_too_large","Room mirror event is too large.");let m=s.indexOf(`
|
|
3
|
+
`);for(;m>=0;){const d=s.slice(0,m);s=s.slice(m+1),d&&e(d),m=s.indexOf(`
|
|
4
|
+
`)}}if(s+=i.end(),s)throw new a("corrupt_room_mirror","Room mirror has an incomplete line.")}finally{n.closeSync(r)}}function N(t,e){if(Buffer.byteLength(t,"utf8")>w)throw new a("room_mirror_line_too_large","Room mirror event is too large.");let r;try{r=JSON.parse(t)}catch{throw new a("corrupt_room_mirror","Room mirror JSONL is corrupt.")}const o=r;if(o.schemaVersion!==1||o.roomId!==e||typeof o.sequence!="string"||typeof o.messageId!="string")throw new a("corrupt_room_mirror","Room mirror event is invalid.");return u(o.sequence),o}function D(t){return[t.text,t.author.displayName,t.author.ownerDisplayName,...t.mentions.map(e=>e.displayName),...t.attachments.map(e=>e.name)].filter(e=>typeof e=="string").join(`
|
|
5
|
+
`)}function F(t,e){if(!n.existsSync(t))return{schemaVersion:1,roomId:e,lastSyncedSequence:"0",messageCount:0,lastError:null,updatedAt:new Date(0).toISOString()};h(t);const r=JSON.parse(n.readFileSync(t,"utf8"));if(r.schemaVersion!==1||r.roomId!==e||typeof r.lastSyncedSequence!="string"||!Number.isSafeInteger(r.messageCount)||r.messageCount<0)throw new a("corrupt_room_mirror_state","Room mirror state is corrupt.");return u(r.lastSyncedSequence),r}function u(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new a("invalid_room_sequence","Invalid Room sequence.");return t}function b(t,e){if(!C.test(t))throw new a(`invalid_${e}`,`Invalid ${e}.`)}function A(t){if(n.existsSync(t)){const e=n.lstatSync(t);if(!e.isDirectory()||e.isSymbolicLink())throw new a("unsafe_room_mirror_path","Unsafe Room mirror directory.")}else n.mkdirSync(t,{recursive:!0,mode:448});process.platform!=="win32"&&n.chmodSync(t,448)}function h(t){const e=n.lstatSync(t);if(!e.isFile()||e.isSymbolicLink())throw new a("unsafe_room_mirror_path","Unsafe Room mirror file.");return process.platform!=="win32"&&(e.mode&63)!==0&&n.chmodSync(t,384),e}function y(t,e,r){n.writeFileSync(t,e,{encoding:"utf8",mode:384,flag:r}),process.platform!=="win32"&&n.chmodSync(t,384)}function $(t,e){h(t);const r=n.openSync(t,"a");try{n.writeFileSync(r,e,"utf8"),n.fsyncSync(r)}finally{n.closeSync(r)}}function P(t,e){process.platform==="win32"&&n.existsSync(e)&&n.unlinkSync(e),n.renameSync(t,e),process.platform!=="win32"&&n.chmodSync(e,384)}function M(t,e){const r=`${t}.tmp-${process.pid}-${p.randomBytes(8).toString("hex")}`;try{y(r,`${JSON.stringify(e,null,2)}
|
|
6
|
+
`,"wx"),P(r,t)}finally{try{n.unlinkSync(r)}catch{}}}export{z as RoomJsonlMirror,a as RoomJsonlMirrorError};
|