shennian 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/shennian-adapter-host.js +4 -0
- package/dist/publish-build-manifest.json +144 -74
- package/dist/resources/agent-adapters/claude.md +33 -0
- package/dist/resources/agent-adapters/codex.md +33 -0
- package/dist/resources/agent-adapters/cursor.md +32 -0
- package/dist/resources/agent-adapters/custom.md +40 -0
- package/dist/resources/agent-adapters/opencode.md +31 -0
- package/dist/resources/agent-adapters/pi-coding-agent.md +31 -0
- package/dist/resources/agent-adapters/workbuddy.md +32 -0
- package/dist/src/adapter-kit/check.js +4 -0
- package/dist/src/adapter-kit/host.js +2 -0
- package/dist/src/adapter-kit/official-adapters.js +1 -0
- package/dist/src/adapter-kit/registry.js +2 -0
- package/dist/src/adapter-kit/submission-client.js +1 -0
- package/dist/src/adapter-kit/types.js +1 -0
- package/dist/src/agents/acp/client-services.js +1 -1
- package/dist/src/agents/acp/process-client.js +1 -1
- package/dist/src/agents/config-status.js +1 -1
- package/dist/src/agents/custom.js +5 -4
- package/dist/src/agents/detect.js +1 -1
- package/dist/src/agents/model-registry/cache.js +1 -1
- package/dist/src/agents/model-registry/discovery.js +3 -3
- package/dist/src/agents/model-registry/opencode-api.js +1 -1
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/agents/node-adapter.js +7 -0
- package/dist/src/agents/opencode.js +1 -1
- package/dist/src/agents/pi-coding-agent.js +3 -3
- package/dist/src/agents/pi-turn-diagnostics.js +1 -0
- package/dist/src/agents/process-tree.js +1 -0
- package/dist/src/agents/public-agent-client.js +1 -0
- package/dist/src/commands/adapter.js +49 -0
- package/dist/src/commands/agent-readiness.js +2 -2
- package/dist/src/commands/agent-workspace.js +3 -0
- package/dist/src/commands/agent.js +2 -2
- package/dist/src/commands/room.js +4 -3
- package/dist/src/commands/runtime.js +5 -5
- package/dist/src/commands/support.js +4 -0
- package/dist/src/daemon-ipc/agent-readiness.js +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/manager/runtime.js +4 -4
- package/dist/src/room/activation-policy.js +1 -1
- package/dist/src/room/activation-service.js +2 -1
- package/dist/src/room/attachment-upload.js +1 -1
- package/dist/src/room/invite-bootstrap.js +1 -1
- package/dist/src/room/live-delivery.js +1 -1
- package/dist/src/room/managed-prompt-composer.js +2 -2
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.js +1 -1
- package/dist/src/room/public-room-client.js +1 -1
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/session/handlers/agent-workspace-validation.js +1 -1
- package/dist/src/session/handlers/chat-recovery.js +1 -1
- package/dist/src/session/handlers/chat.js +1 -1
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue-helpers.js +8 -3
- package/dist/src/session/queue.js +1 -1
- package/dist/src/support/support-client.js +1 -0
- package/node_modules/@shennian/wire/dist/adapter-submission.js +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -1
- package/node_modules/@shennian/wire/dist/session.js +1 -1
- package/node_modules/@shennian/wire/dist/support.js +1 -0
- package/package.json +4 -4
- package/dist/src/agents/acp/process-tree.js +0 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import p from"node:fs";import m from"node:path";import{fileURLToPath as w}from"node:url";import{ADAPTER_SUBMISSION_FILENAME as y,ADAPTER_SUBMISSION_MAX_BYTES as v,scanAdapterSubmissionSource as S}from"@shennian/wire";import{AdapterCheckError as x,checkNodeAdapter as I}from"../adapter-kit/check.js";import{findOfficialAdapter as P,OFFICIAL_ADAPTERS as N,readOfficialAdapterDoc as M}from"../adapter-kit/official-adapters.js";import{AdapterSubmissionClientError as C,HttpAdapterSubmissionClient as D}from"../adapter-kit/submission-client.js";import{getNodeAdapter as R,getPassedCheck as g,listNodeAdapters as $,recordPassedCheck as E,registerNodeAdapter as j,removeNodeAdapter as T}from"../adapter-kit/registry.js";import{loadConfig as V}from"../config/index.js";const f=3;function Q(i,s={}){const d=i.command("adapter").description("Create, check and manage local Shennian Node Adapters (protocol V1)"),A=s.hostPath??O(),k=s.hostNodeArgs??[],b=s.submissionClient??new D;d.command("init <directory>").description("Create a dependency-free Node Adapter V1 template").requiredOption("--name <name>","Display name for the adapter").action((n,o)=>{const t=m.resolve(n),e=m.join(t,"adapter.mjs");if(p.existsSync(e)){a("adapter_file_exists",`Refusing to overwrite ${e}`);return}p.mkdirSync(t,{recursive:!0}),p.writeFileSync(e,q(o.name),{encoding:"utf8",mode:384,flag:"wx"}),l("adapter.init",{entry:e,protocolVersion:1,requiredMethods:["listModels","start","send","stop","resume"],maxCheckAttempts:f},{type:"implement_and_check",entry:e,workDir:t,command:"shennian adapter check <entry> --workdir <allowed-project-directory>",rules:["Inspect only non-sensitive installation, version and machine-interface facts.","Never read or print provider credentials or environment values.","Pass systemPrompt through a real system/instructions channel; never prepend it to user text.",`Stop after ${f} failed check iterations and explain the remaining stage/error to the user.`]})}),d.command("check <entry>").description("Run the full protocol V1 completeness check through the bundled Adapter Runtime").option("--workdir <directory>","Working directory",process.cwd()).option("--model <id>","Preferred model ID; falls back to the dynamic default model").option("--timeout <ms>","Timeout per step in milliseconds","60000").action(async(n,o)=>{const t=Number.parseInt(o.timeout,10);if(!Number.isSafeInteger(t)||t<1e3||t>3e5){a("invalid_timeout","Timeout must be between 1000 and 300000 milliseconds.");return}const e=m.resolve(n);if(!p.statSync(e,{throwIfNoEntry:!1})?.isFile()){a("adapter_entry_not_found",`Adapter entry does not exist: ${e}`);return}try{const r=await I({hostPath:A,entryPath:e,workDir:m.resolve(o.workdir),modelId:o.model,timeoutMs:t,nodeArgs:k});E(e,{name:r.name,version:r.version,workDir:m.resolve(o.workdir),models:r.models,defaultModel:r.defaultModel}),l("adapter.check",{stage:"passed",name:r.name,version:r.version,models:r.models,defaultModel:r.defaultModel,modelId:r.modelId,systemPromptVerified:r.systemPromptVerified,resumeVerified:r.resumeVerified,maxCheckAttempts:f},{type:"register_adapter",entry:e,command:"shennian adapter add <stable-name> --entry <entry>",after:"shennian agent discover --refresh"})}catch(r){if(r instanceof x){F(r.stage,r.code);return}a("adapter_check_failed","Adapter V1 check could not complete. Raw Runtime output was withheld because it may contain private machine data.",_("start","adapter_check_failed"))}}),d.command("add <name>").description("Register a local Node Adapter that already passed `adapter check` with an unchanged file").requiredOption("--entry <file>","Path to adapter.mjs").action((n,o)=>{if(!/^[a-zA-Z0-9_-]+$/.test(n)){a("invalid_adapter_name","Adapter name may contain only letters, numbers, hyphens, and underscores.");return}if(V().customAgents?.[n]){a("adapter_name_conflict",`A legacy custom runtime already uses the name "${n}". Choose a different stable name.`,{type:"choose_adapter_name",rule:"letters, numbers, hyphens and underscores only"});return}const t=m.resolve(o.entry);if(!p.statSync(t,{throwIfNoEntry:!1})?.isFile()){a("adapter_entry_not_found",`Adapter entry does not exist: ${t}`);return}const e=g(t);if(!e){a("adapter_not_checked","Run `shennian adapter check` on this exact file first; `add` only accepts an adapter whose passing check still matches the current file contents.");return}const r=j(n,t,e);l("adapter.add",{adapterRef:`custom:${n}`,name:r.name,version:r.version,models:r.models,defaultModel:r.defaultModel},{type:"refresh_discovery",command:"shennian agent discover --refresh",expectedAgentRef:`custom:${n}`})}),d.command("list").description("List official Adapters and locally registered Node Adapters").action(()=>{const n=N.map(t=>({kind:"official",id:t.id,name:t.name})),o=Object.entries($()).map(([t,e])=>({kind:"local",id:`custom:${t}`,name:e.name,version:e.version,models:e.models,defaultModel:e.defaultModel,registeredAt:e.registeredAt}));l("adapter.list",{adapters:[...n,...o]})}),d.command("show <adapter-id>").description("Show an official Adapter's Markdown detail, or a local adapter's registration summary").action(n=>{const o=n.startsWith("custom:")?n.slice(7):n,t=R(o);if(t){l("adapter.show",{kind:"local",id:`custom:${o}`,name:t.name,version:t.version,models:t.models,defaultModel:t.defaultModel,entryPath:t.entryPath,registeredAt:t.registeredAt,checkedAt:t.checkedAt});return}const e=P(n);if(!e){a("adapter_not_found",`No official or local adapter named "${n}" exists.`);return}const r=M(n);if(!r){a("adapter_doc_missing",`Detail document for "${n}" is not available in this build.`);return}l("adapter.show",{kind:"official",id:e.id,name:e.name,doc:r})}),d.command("remove <name>").description("Unregister a local Node adapter without deleting its source").action(n=>{if(!T(n)){a("adapter_not_found",`No local Node adapter named "${n}" exists.`);return}l("adapter.remove",{adapterRef:`custom:${n}`,sourceDeleted:!1})}),d.command("submit <entry>").description("Submit one checked adapter.mjs to Shennian for private official review").requiredOption("--agent <name>","General upstream Agent that this implementation adapts").option("--general-purpose","Confirm this is a reusable public Agent adapter, not private or project-specific").option("--confirm-share","Confirm the user explicitly approved sharing this source with Shennian").action(async(n,o)=>{if(!o.generalPurpose){a("adapter_submission_not_general","Private, organization-internal or project-specific adapters must not be submitted.",{type:"stop",reason:"Keep the Adapter local."});return}if(!o.confirmShare){a("adapter_submission_consent_required","Ask the user for explicit approval before sharing adapter.mjs with Shennian.",{type:"ask_user",question:"Submit this reusable adapter.mjs to Shennian for official review?"});return}const t=m.resolve(n);if(m.basename(t)!==y||!p.statSync(t,{throwIfNoEntry:!1})?.isFile()){a("adapter_submission_file_invalid","Submission accepts exactly one text file named adapter.mjs.");return}const e=g(t);if(!e){a("adapter_not_checked","Run `shennian adapter check` on this exact file first; submission requires the unchanged passing fingerprint.");return}let r;try{const c=p.readFileSync(t);if(r=c.toString("utf8"),!Buffer.from(r,"utf8").equals(c)){a("adapter_submission_file_invalid","adapter.mjs must be valid UTF-8 text.");return}}catch{a("adapter_submission_file_invalid","adapter.mjs could not be read.");return}const u=S(r);if(u.byteSize===0||u.byteSize>v){a("adapter_submission_size_invalid",`adapter.mjs must contain 1-${v} UTF-8 bytes.`);return}if(!u.ok){a("adapter_submission_sensitive_content","Remove credentials, environment values, absolute paths and private business content before submission.",{type:"generate_clean_single_file",issues:u.issues});return}const h=o.agent.trim();if(!h||h.length>191){a("adapter_submission_agent_invalid","Agent name must contain 1-191 characters.");return}try{const c=await b.submit({filename:y,content:r,sha256:e.entrySha256,targetAgent:h,adapterName:e.name,adapterVersion:e.version,checkedAt:e.checkedAt,models:e.models,defaultModel:e.defaultModel,clientVersion:s.clientVersion??"unknown",platform:process.platform});l("adapter.submit",{submissionId:c.submissionId,status:c.status,sha256:e.entrySha256,duplicate:c.duplicate===!0,officialAdapterRegistered:!1})}catch(c){if(c instanceof C){a(c.code,c.message);return}a("adapter_submission_failed","Adapter submission failed without exposing local source or credentials.")}})}function O(){return w(new URL("../../bin/shennian-adapter-host.js",import.meta.url))}function q(i){return`/**
|
|
2
|
+
* Shennian Adapter \u534F\u8BAE V1.
|
|
3
|
+
* \u72EC\u7ACB\u5B89\u88C5\u7684 Shennian Client \u81EA\u5E26 Adapter Runtime\uFF0C\u5728\u81EA\u5DF1\u7684\u5B50\u8FDB\u7A0B\u91CC\u52A0\u8F7D
|
|
4
|
+
* \u8FD9\u4E2A\u6587\u4EF6\uFF1B\u4E0D\u9700\u8981 npm install\uFF0C\u4E5F\u4E0D\u9700\u8981\u7406\u89E3 JSON Lines\u3002
|
|
5
|
+
*
|
|
6
|
+
* \u4E94\u4E2A\u51FD\u6570\u5168\u90E8\u5FC5\u9700\uFF1AlistModels / start / send / stop / resume\u3002
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
name: ${JSON.stringify(i.trim()||"My Adapter")},
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
|
|
12
|
+
async listModels({ workDir }) {
|
|
13
|
+
// \u4ECE\u5F53\u524D\u4E0A\u6E38 SDK / CLI / app-server \u52A8\u6001\u67E5\u8BE2\u6A21\u578B\uFF1B\u6BCF\u6B21\u771F\u5B9E\u8C03\u7528\uFF0C
|
|
14
|
+
// \u4E0D\u8981\u628A\u67D0\u6B21\u63A2\u6D4B\u7ED3\u679C\u5199\u6B7B\u8FDB\u6E90\u7801\u3002
|
|
15
|
+
void workDir
|
|
16
|
+
return { models: [], defaultModel: '' }
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async start({ sessionId, workDir, modelId, systemPrompt }) {
|
|
20
|
+
// \u5728\u8FD9\u91CC\u521B\u5EFA\u4E0A\u6E38 SDK \u5BA2\u6237\u7AEF\u3001app-server \u8FDE\u63A5\u6216 CLI \u4F1A\u8BDD\u3002
|
|
21
|
+
// systemPrompt \u5FC5\u987B\u901A\u8FC7\u4E0A\u6E38\u771F\u5B9E\u7684 system / instructions \u53C2\u6570\u6CE8\u5165\uFF0C
|
|
22
|
+
// \u7981\u6B62\u62FC\u63A5\u8FDB\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\u3002
|
|
23
|
+
void sessionId; void workDir; void modelId; void systemPrompt
|
|
24
|
+
throw new Error('Implement start() before checking this adapter.')
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async send({ session, text, modelId, reasoningEffort, attachments, signal, output }) {
|
|
28
|
+
// text \u53EA\u662F\u5F53\u524D\u7528\u6237\u6D88\u606F\uFF0C\u4E0D\u5305\u542B\u7CFB\u7EDF\u63D0\u793A\u8BCD\u3002\u7528 output.delta(...) \u6D41\u5F0F
|
|
29
|
+
// \u8F93\u51FA\u6B63\u6587\uFF0C\u6700\u540E\u5FC5\u987B\u8C03\u7528 output.final() \u6216 output.error(...)\u3002
|
|
30
|
+
void session; void text; void modelId; void reasoningEffort
|
|
31
|
+
void attachments; void signal
|
|
32
|
+
output.error('Implement send() before checking this adapter.', 'send_failed')
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async stop({ session, reason }) {
|
|
36
|
+
// \u4E2D\u65AD\u5F53\u524D\u4E0A\u6E38\u8BF7\u6C42\u5E76\u91CA\u653E\u672C\u5730\u8D44\u6E90\u3002
|
|
37
|
+
void session; void reason
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async resume({ sessionId, nativeSessionId, workDir, modelId, systemPrompt }) {
|
|
41
|
+
// \u5FC5\u987B\u6062\u590D\u8FD9\u4E2A\u7CBE\u786E\u7684 nativeSessionId\uFF0C\u7981\u6B62\u6062\u590D\u201C\u6700\u8FD1\u4E00\u6B21\u201D\u3002
|
|
42
|
+
void sessionId; void workDir; void modelId; void systemPrompt
|
|
43
|
+
throw new Error(\`Implement resume() to restore native session \${nativeSessionId}.\`)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
`}function l(i,s,d){process.stdout.write(`${JSON.stringify({ok:!0,command:i,result:s,...d?{nextAction:d}:{}})}
|
|
47
|
+
`)}function a(i,s,d=null){process.stdout.write(`${JSON.stringify({ok:!1,error:i,message:s,nextAction:d})}
|
|
48
|
+
`),process.exitCode=1}function F(i,s){process.stdout.write(`${JSON.stringify({ok:!1,command:"adapter.check",stage:i,error:s,message:L(i,s),nextAction:_(i,s)})}
|
|
49
|
+
`),process.exitCode=1}function L(i,s){return s==="adapter_contract_invalid"?"The Adapter V1 module contract is incomplete or could not be loaded.":s==="models_invalid"?"Dynamic model discovery did not return a valid non-empty model list and default model.":s==="system_prompt_unsupported"?"The upstream did not prove that systemPrompt uses a real system/instructions channel.":s==="stop_failed"?"The Adapter did not stop the checked session cleanly.":s==="resume_failed"?"The Adapter did not resume the exact checked native session.":`Adapter V1 check failed at the ${i} stage (${s}).`}function _(i,s){return{type:"edit_and_retry",stage:i,error:s,guidance:{contract:"Keep one default-export object with non-empty name/version and all five required functions. Do not restore protocolVersion 2.",models:"Query the installed upstream machine interface dynamically; return unique non-empty model ids and a defaultModel from that list.",start:"Check only the upstream executable, version, login state and documented machine interface, then implement start with workDir, modelId and systemPrompt.",systemPrompt:"Map systemPrompt to the upstream system/instructions parameter. Never concatenate it with the user message.",send:"Send only the user text and attachments, forward output events, and produce exactly one terminal result.",stop:"Abort the active upstream turn and release the process or connection before returning.",resume:"Restore the exact nativeSessionId and the same systemPrompt; never select a recent or latest session."}[i]??"Inspect the Adapter V1 implementation and retry the same check once.",command:"Retry the same shennian adapter check command after editing adapter.mjs.",maxAttempts:f,safety:"Do not read, print or copy credentials, environment values, private project content or full provider logs."}}export{Q as registerAdapterCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as
|
|
2
|
-
`),
|
|
1
|
+
import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";const d={method_not_found:"Update the existing Shennian installation: Desktop through Desktop updates, standalone CLI through its own update flow. Do not install a second Client.",daemon_unavailable:"Check shennian status --json on the target computer. Do not resume an intentional pause without the user requesting it.",daemon_paused:"Shennian is paused. Ask the user to resume it before checking.",ipc_timeout:"The daemon did not answer in time, so the result is unknown; the machine is usually overloaded. Check its load and memory (uptime, free -m) and wait for the running check to end; do not repeatedly send tests. If the AI's own processes are still running afterwards, stop only those exact processes.",agent_check_in_progress:"Wait for the running check to finish before trying again.",agent_validation_runtime_unavailable:"Refresh discovery. If the tool is still missing, guide the user through its official installation on this computer.",agent_validation_runtime_auth_required:"Guide the user through the provider\u2019s own sign-in on this computer, then retry the selected AI and model.",agent_validation_model_unavailable:"Choose a model returned by discovery and check the provider account\u2019s model access.",agent_validation_quota_exceeded:"Check the provider account\u2019s credits, subscription or rate limit before another test.",agent_validation_network_failed:"Check the target computer\u2019s connection to the provider, then retry when it is available.",agent_validation_workdir_invalid:"Use an existing absolute project directory on this computer that the user allows Shennian to use.",agent_validation_workdir_not_found:"The working directory does not exist on this computer. Ask the user for an existing absolute project directory (it may have been moved, deleted or is on an unmounted drive); do not create it for them.",agent_validation_workdir_not_directory:"The working directory path is a file, not a folder. Ask the user for the project folder.",agent_validation_workdir_unavailable:"The working directory exists but this user cannot access it. Check its permissions on this computer or choose another directory.",agent_validation_start_failed:"The AI could not start, or did not finish starting within 60 seconds. Run the tool once on this computer to see its own error, fix its installation or configuration, then retry once.",agent_validation_response_timeout:"The AI started but gave no hello reply within 60 seconds and its processes were stopped. Check the computer's load and free memory first, then its provider sign-in, selected model and network; retry once and do not repeatedly send tests.",agent_validation_response_mismatch:"The AI replied but not with hello. Check the selected model; a reply that ignores simple instructions usually means a wrong or misconfigured model.",agent_validation_response_failed:"The AI started but failed while replying. Check its provider sign-in, selected model, quota and network, then retry once."},l={agent_validation_runtime_unavailable:"start",agent_validation_workdir_invalid:"start",agent_validation_workdir_not_found:"start",agent_validation_workdir_not_directory:"start",agent_validation_workdir_unavailable:"start",agent_validation_start_failed:"start",agent_validation_runtime_auth_required:"auth",agent_validation_model_unavailable:"model",agent_validation_quota_exceeded:"quota",agent_validation_network_failed:"network",agent_validation_response_timeout:"reply",agent_validation_response_mismatch:"reply",agent_validation_response_failed:"reply"},c=new Set(["claude","codex","cursor","workbuddy","opencode","pi-coding-agent"]);function u(t,o){const n=d[t]??"Return to the AI preparation guide on this machine. Check the selected directory, account, model and network; do not reinstall by default.",e=o&&typeof o=="object"&&!Array.isArray(o)&&typeof o.agentType=="string"?o.agentType:null;return!e||!c.has(e)||!["agent_validation_start_failed","agent_validation_response_failed","agent_validation_response_mismatch"].includes(t)?n:`${n} If the provider's own machine interface works but the official Adapter still fails, read shennian adapter show ${e}, create a local V1 file with shennian adapter init, and follow adapter check's structured stage/error/nextAction. Stop after three failed edit/check iterations.`}const h=9e4,_="A successful check only proves one standard-mode hello reply; it does not prove the AI can send messages in a Room.",p={authState:'unknown means Shennian has no login probe for this AI; it is not "not logged in".',defaultModelId:"null means no model is marked default; it does not mean no model is available. The AI picks its own model when none is passed."};async function g(t,o,n=s){try{const e=await n({method:t,params:o,timeoutMs:h});if(t==="agent.check"&&(!e||typeof e!="object"||Array.isArray(e)||e.ready!==!0))throw new r("agent_check_invalid_response","The daemon did not confirm a successful hello reply.");return t==="agent.discover"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,notes:p}}:t==="agent.check"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,note:_}}:{ok:!0,command:t,result:e}}catch(e){const i=e instanceof r?e.code:"agent_check_failed",a=l[i]??(t==="agent.check"&&i==="ipc_timeout"?"reply":void 0);return{ok:!1,command:t,error:i,...a?{stage:a}:{},nextAction:u(i,o)}}}function y(t){const o=async(n,e)=>{const i=await g(n,e);process.stdout.write(`${JSON.stringify(i)}
|
|
2
|
+
`),i.ok||(process.exitCode=1)};t.command("discover").description("Read the daemon AI snapshot; use --refresh only to rerun local provider and model discovery").option("--agent <type>","Return only one discovered Agent type").option("--compact","Return capability summary without the full model list").option("--refresh","Rerun discovery; with --agent, probe only that Agent").option("--json","Print JSON (default)").action(n=>o("agent.discover",{...n.agent?{agentType:n.agent}:{},compact:n.compact===!0,refresh:n.refresh===!0})),t.command("check <agentType>").description("Send one hello test using the selected AI and model; uses provider quota, creates no managed Agent or room").requiredOption("--workdir <path>","Absolute project directory on this machine").option("--model <id>","Model returned by discovery; otherwise use the provider default").option("--json","Print JSON (default)").action((n,e)=>o("agent.check",{agentType:n,workDir:e.workdir,modelId:e.model??null}))}export{h as AGENT_READINESS_IPC_TIMEOUT_MS,g as executeAgentReadinessCommand,y as registerAgentReadinessCommands};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import O from"node:crypto";import E from"node:path";import{callDaemonIpc as M}from"../daemon-ipc/client.js";import{DaemonIpcError as k}from"../daemon-ipc/protocol.js";import{PublicAgentClient as q,PublicAgentClientError as b}from"../agents/public-agent-client.js";const C={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function W(e,n={}){const i=n.client??new q,t=n.io??C,a=n.callDaemon??M;e.command("list").description("List Shennian Agents owned by the paired user on this computer").action(()=>c("agent.list",t,()=>i.list())),e.command("get <agent-id>").description("Read one exact Shennian Agent").action(o=>c("agent.get",t,()=>i.get(l(o,"agent_id_invalid")))),e.command("create").description("Check one local AI configuration, then create a standard full-access Shennian Agent").requiredOption("--name <name>","Agent display name").requiredOption("--description <description>","Public Room introduction").requiredOption("--agent <type>","Exact type returned by agent discover").requiredOption("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--system-prompt <prompt>","Private system prompt","").action(o=>c("agent.create",t,async()=>{const s=L(o);return await R(s,a),i.create(s)})),e.command("update <agent-id>").description("Update one exact Shennian Agent; runtime changes are checked before saving").option("--name <name>","Agent display name").option("--description <description>","Public Room introduction").option("--agent <type>","Exact type returned by agent discover").option("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--clear-model","Use the runtime default model").option("--system-prompt <prompt>","Private system prompt").action((o,s)=>c("agent.update",t,async()=>{const _=l(o,"agent_id_invalid"),m=F(s);Object.keys(m).length||d("agent_changes_required","Provide at least one Agent field to update.");const p=N(await i.get(_)),u={...p,...m};return I(p)!==I(u)&&await R(u,a),i.update(_,m)})),e.command("delete <agent-id>").description("Delete one exact Shennian Agent and its dedicated Room conversations").action(o=>c("agent.delete",t,()=>i.delete(l(o,"agent_id_invalid"))));const g=e.command("room").description("List, add, or remove this Agent in explicit Rooms");g.command("list <agent-id>").description("List Rooms currently linked to one exact Agent").action(o=>c("agent.room.list",t,()=>i.listRooms(l(o,"agent_id_invalid")))),g.command("add <agent-id> <room-url>").description("Create a dedicated conversation and add this Agent to one explicit Room").option("--mode <mode>","auto_follow, mention_only, or paused","auto_follow").action((o,s,_)=>c("agent.room.add",t,async()=>{const m=l(o,"agent_id_invalid"),p=x(s),u=$(_.mode),A=`cli-agent-room:${O.randomUUID()}`,f=await i.createRoomSession(m,{roomId:p,assignmentIntentId:A,activationMode:u});if(f.existing===!0)return f;const h=v(f.session,"invalid_server_response"),P=y(h.id,"invalid_server_response"),U=y(h.agentType,"invalid_server_response"),D=await a({method:"room.agent.bind-managed",params:{roomId:p,agentIdentityId:m,nianSessionId:P,assignmentIntentId:A,agentType:U,activationMode:u},timeoutMs:12e4});return{session:h,binding:D}})),g.command("remove <agent-id> <room-url>").description("Remove this Agent from one explicit Room and delete its dedicated conversation").action((o,s)=>c("agent.room.remove",t,()=>i.removeFromRoom(l(o,"agent_id_invalid"),x(s))))}function L(e){return{displayName:r(e.name,191,"agent_name_invalid",!0),description:r(e.description,1e3,"agent_description_invalid",!0),runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0),workDir:S(e.workdir),modelId:e.model?r(e.model,191,"agent_model_invalid",!0):null,systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}function F(e){return e.clearModel&&e.model!==void 0&&d("agent_model_ambiguous","Use either --model or --clear-model, not both."),{...e.name===void 0?{}:{displayName:r(e.name,191,"agent_name_invalid",!0)},...e.description===void 0?{}:{description:r(e.description,1e3,"agent_description_invalid",!0)},...e.agent===void 0?{}:{runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0)},...e.workdir===void 0?{}:{workDir:S(e.workdir)},...e.model===void 0&&!e.clearModel?{}:{modelId:e.clearModel?null:r(e.model,191,"agent_model_invalid",!0)},...e.systemPrompt===void 0?{}:{systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}}async function R(e,n){const i=y(e.runtimeAdapter,"agent_runtime_invalid"),t=y(e.workDir,"agent_workdir_invalid"),a=typeof e.modelId=="string"?e.modelId:null;if(v(await n({method:"agent.check",params:{agentType:i,workDir:t,modelId:a},timeoutMs:9e4}),"agent_check_invalid_response").ready!==!0)throw new k("agent_check_invalid_response","The daemon did not confirm a successful hello reply.")}function I(e){return JSON.stringify([e.runtimeAdapter??null,e.workDir??null,e.modelId??null,typeof e.systemPrompt=="string"?e.systemPrompt.trim():""])}function N(e){return v(e.agent,"invalid_server_response")}async function c(e,n,i){try{const t=await i();n.write(`${JSON.stringify({ok:!0,command:e,result:t})}
|
|
2
|
+
`)}catch(t){if(t instanceof b){w(n,t.code,t.message,t.nextAction);return}if(t instanceof k){w(n,t.code,t.message,null);return}w(n,"agent_command_failed","Shennian Agent command failed.",null)}}function l(e,n){return/^[A-Za-z0-9_.:@/-]{1,191}$/.test(e)||d(n,"Use the exact Agent id returned by Shennian."),e}function x(e){let n;try{n=new URL(e)}catch{d("invalid_room_ref","Use an official Room URL returned by Shennian.")}const i=n.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(n.protocol!=="https:"||n.hostname!=="app.shennian.net"||n.port||n.username||n.password||n.hash||n.search||!i)&&d("invalid_room_ref","Use an official Room URL returned by Shennian."),i[1]}function $(e){if(e==="auto_follow"||e==="mention_only"||e==="paused")return e;d("invalid_activation_mode","--mode must be auto_follow, mention_only, or paused.")}function S(e){const n=r(e,1024,"agent_workdir_invalid",!0);return E.isAbsolute(n)||d("agent_workdir_invalid","Use an absolute working directory."),n}function r(e,n,i,t=!1){const a=typeof e=="string"?e.trim():"";return(t&&!a||a.length>n||a.includes("\0"))&&d(i,"Agent field is invalid."),a}function y(e,n){return(typeof e!="string"||!e)&&d(n,"Shennian returned an invalid Agent response."),e}function v(e,n){return(!e||typeof e!="object"||Array.isArray(e))&&d(n,"Shennian returned an invalid Agent response."),e}function d(e,n){throw new b(e,n)}function w(e,n,i,t){e.write(`${JSON.stringify({ok:!1,error:n,message:i,nextAction:t})}
|
|
3
|
+
`),e.fail()}export{W as registerAgentWorkspaceCommands};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{registerAgentReadinessCommands as
|
|
2
|
-
`),
|
|
1
|
+
import{registerAgentReadinessCommands as h}from"./agent-readiness.js";import{registerAgentWorkspaceCommands as A}from"./agent-workspace.js";import{createAgent as f}from"../agents/adapter.js";import{AcpAgentAdapter as w}from"../agents/acp/adapter.js";import{discoverAcpAgents as C}from"../agents/acp/discovery.js";import{callDaemonIpc as v}from"../daemon-ipc/client.js";import{readAcpManifests as y,removeAcpManifest as $,validateAcpId as s,writeAcpManifest as P}from"../agents/acp/manifests.js";function O(n,a={}){const e=n.command("agent").description("Discover local AI tools and manage Shennian Agents");h(e),A(e,a);const t=e.command("integration").description("Manage locally registered ACP integrations");R(t),l(e.command("add <id>",{hidden:!0})),p(e.command("remove <id>",{hidden:!0})),u(e.command("auth <id>",{hidden:!0}))}function R(n){l(n.command("add <id>")),n.command("list").description("List locally registered ACP Agents").action(()=>{o(!0,"agent.integration.list",{agents:y().map(a=>({agentRef:`acp:${a.id}`,name:a.name,command:a.launch.command,args:a.launch.args??[]}))})}),p(n.command("remove <id>")),u(n.command("auth <id>"))}function l(n){n.description("Register an installed ACP v1 Agent (does not install it)").requiredOption("--name <name>","Display name").requiredOption("--command <command>","Absolute executable path or command name").option("--arg <args...>","Arguments passed to the ACP Agent").action((a,e)=>{try{const t=s(a);P({schemaVersion:1,id:t,name:e.name,protocol:"acp",command:e.command,args:e.arg??[],enabled:!0}),o(!0,"agent.add",{agentRef:`acp:${t}`,installedByShennian:!1})}catch(t){o(!1,"agent.add",void 0,t instanceof Error?t.message:String(t))}})}function p(n){n.description("Remove a local ACP Agent registration (does not uninstall it)").action(a=>{try{const e=s(a),t=$(e);o(t,"agent.remove",t?{agentRef:`acp:${e}`}:void 0,t?void 0:`No local ACP Agent named "${e}" exists.`)}catch(e){o(!1,"agent.remove",void 0,e instanceof Error?e.message:String(e))}})}function u(n){n.description("Authenticate an installed ACP Agent on this computer").option("--method <method>","Authentication method id").action(async(a,e)=>{let t=null;try{const r=s(a.replace(/^acp:/,""));if(!(await C()).some(i=>i.type===`acp:${r}`))throw new Error(`Installed ACP Agent "${r}" was not found.`);const c=f(`acp:${r}`);if(!(c instanceof w))throw new Error("ACP Agent adapter is unavailable");t=c,await t.start(`acp-auth-${process.pid}`,process.cwd());const d=t.getAuthMethods(),m=e.method?d.find(i=>i.id===e.method):d.length===1?d[0]:void 0;if(!m)throw new Error(d.length?`Choose an authentication method with --method: ${d.map(i=>i.id).join(", ")}`:"This ACP Agent did not advertise an authentication method.");await t.authenticate(m.id,{interactiveTerminal:!0});let g=!1;try{g=(await v({method:"agent.auth.completed",params:{agentType:`acp:${r}`}})).resumed}catch{}o(!0,"agent.auth",{agentRef:`acp:${r}`,authenticated:!0,resumed:g})}catch(r){o(!1,"agent.auth",void 0,r instanceof Error?r.message:String(r))}finally{await t?.stop().catch(()=>{})}})}function o(n,a,e,t){process.stdout.write(`${JSON.stringify(n?{ok:n,command:a,result:e}:{ok:n,command:a,error:"acp_agent_manifest_error",message:t,nextAction:null})}
|
|
2
|
+
`),n||(process.exitCode=1)}export{O as registerAgentCommand};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import b from"node:crypto";import U from"node:fs";import R from"node:path";import{PublicAttachmentCache as j}from"../room/public-attachment-cache.js";import{PublicRoomClient as k,PublicRoomClientError as S}from"../room/public-room-client.js";import{callDaemonIpc as D}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as N,resolveManagedRoomIpcPaths as P}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as L}from"../room/managed-context.js";import{createRoomInviteQr as $}from"../room/invite-qr.js";import{externalRoomNextAction as I,managedRoomNextAction as C}from"../room/room-error-guidance.js";const T={write:e=>{process.stdout.write(e)},fail:()=>{process.exitCode=1}};function oe(e,t={}){const o=t.client??new k,a=t.cache??new j({client:o}),s=t.io??T,u=t.createInviteQr??$,c=e.command("room").description("Use one explicitly referenced Shennian Room");c.command("schedule").description("Manage scheduled work in the current managed conversation").requiredOption("--operation <operation>","list/create/update/pause/resume/delete").option("--id <id>").option("--version <version>").option("--operation-id <operationId>").option("--time-description <timeDescription>").option("--timezone <timezone>").option("--prompt <prompt>").action(async r=>p("room.schedule",s,async()=>{const n=h();return n||i("managed_room_context_required","Scheduling is only available in managed Room context.",I()),v(n,"schedule",{...r,version:r.version?Number(r.version):void 0})})),c.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async r=>p("room.create",s,async()=>{w("create");const n=r.name.trim();(!n||n.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const m=await o.create(n),d=_(m.room,"invalid_room_response");typeof d.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation.");const f=await u(d.inviteUrl).catch(()=>({imagePath:null,mimeType:"image/png",payload:d.inviteUrl}));return{...m,sharing:{inviteUrl:d.inviteUrl,inviteQr:f}}})),c.command("join <invite-url>").description("Join or request access through an official Room invitation").option("--message <message>","Introduction and reason required when owner approval is enabled").action(async(r,n)=>p("room.join",s,async()=>{w("join");const m=A(r,"invite"),d=n.message?.trim();return d&&d.length>500&&i("join_message_too_long","Join request message must be at most 500 characters."),o.join(m.roomId,m.inviteToken,d)})),c.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async r=>p("room.open",s,async()=>{w("open");const n=A(r,"stable");return o.open(n.roomId)})),c.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async r=>p("room.status",s,()=>{const n=h();return n?(r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(n,"status",{})):o.status(x(r))})),c.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(r,n)=>{await p("room.read",s,async()=>{n.before&&n.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!z(n.before)&&i("invalid_sequence","--before sequence is invalid."),n.after&&!z(n.after)&&i("invalid_sequence","--after sequence is invalid.");const m=Number(n.limit);(!Number.isInteger(m)||m<1||m>50)&&i("invalid_limit","--limit must be between 1 and 50.");const d=h();if(d)return r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(d,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:m});const f=x(r),y=await o.read(f,{before:n.before,after:n.after,limit:m});return Q(y,f,a)})}),c.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",E,[]).action(async(r,n)=>{await p("room.send",s,async()=>{const m=n.text?.trim()??"";!m&&n.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const d=h();if(d)return r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(d,"send",{clientMessageId:`managed:${b.randomUUID()}`,text:m,files:n.file});const f=x(r),y=[];for(const O of n.file)y.push(await F(o,f,O));return o.send(f,{clientMessageId:`cli:${b.randomUUID()}`,...m?{text:m}:{},...y.length>0?{attachmentIds:y}:{}})})}),c.command("pause").description("Pause the current managed Agent in its bound Room").requiredOption("--reason <reason>","Short reason shown to all Room members").action(async r=>p("room.pause",s,()=>{const n=h();return n||i("managed_room_context_required","Pause is only available in managed Room context.",I()),v(n,"pause",{reason:r.reason})})),c.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(r,n)=>{await p("room.attachment.get",s,async()=>{const m=h(),d=m?r:n;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&i("invalid_attachment_ref","attachment-id is invalid."),m?v(m,"attachment.get",{attachmentId:d}):a.materialize(x(r),d)})}),c.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async r=>p("room.leave",s,()=>(w("leave"),o.leave(M(r))))),c.command("list",{hidden:!0}).action(()=>g(s,"command_removed","Room listing is not available to Agents.",null)),c.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>g(s,"command_removed","Room search is not available to Agents.",null)),c.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>g(s,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}async function p(e,t,o){try{const a=await o();t.write(`${JSON.stringify({ok:!0,command:e,result:a})}
|
|
2
|
-
`)}
|
|
3
|
-
`),e.
|
|
1
|
+
import x from"node:crypto";import q from"node:fs";import I from"node:path";import{PublicAttachmentCache as T}from"../room/public-attachment-cache.js";import{PublicRoomClient as $,PublicRoomClientError as O}from"../room/public-room-client.js";import{callDaemonIpc as E}from"../daemon-ipc/client.js";import{DaemonIpcError as N}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as C,resolveManagedRoomIpcPaths as V}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as F}from"../room/managed-context.js";import{createRoomInviteQr as G}from"../room/invite-qr.js";import{externalRoomNextAction as j,managedRoomNextAction as H,SHENNIAN_INSTALL_URL as M}from"../room/room-error-guidance.js";import{attachmentMediaKind as J}from"../room/attachment-upload.js";const Q={write:o=>{process.stdout.write(o)},fail:()=>{process.exitCode=1}};function pe(o,n={}){const t=n.client??new $,c=n.cache??new T({client:t}),a=n.io??Q,v=n.createInviteQr??G,p=async(i,e)=>{const s=await v(i).catch(()=>({imagePath:null,mimeType:"image/png",payload:i}));return{inviteUrl:i,installUrl:M,text:Z(i,e),inviteQr:s}},m=o.command("room").description("Use one explicitly referenced Shennian Room");m.command("schedule").description("Manage scheduled work in the current managed conversation").requiredOption("--operation <operation>","list/create/update/pause/resume/delete").option("--id <id>").option("--version <version>").option("--operation-id <operationId>").option("--time-description <timeDescription>").option("--timezone <timezone>").option("--prompt <prompt>").action(async i=>l("room.schedule",a,async()=>{const e=b();return e||r("managed_room_context_required","Scheduling is only available in managed Room context.",j()),g(e,"schedule",{...i,version:i.version?Number(i.version):void 0})})),m.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async i=>l("room.create",a,async()=>{u("create");const e=i.name.trim();(!e||e.length>80)&&r("invalid_room_name","Room name must contain 1 to 80 characters.");const s=await t.create(e,x.randomUUID()),d=f(s.room,"invalid_room_response");return typeof d.inviteUrl!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...s,sharing:await p(d.inviteUrl,typeof d.name=="string"?d.name:e)}})),m.command("list").description("List Rooms for the paired user; duplicate names remain separate candidates").action(()=>l("room.list",a,()=>(u("list"),t.list()))),m.command("get <room-ref>").description("Get one explicitly referenced Room and its current version").action(i=>l("room.get",a,()=>(u("get"),t.get(h(i))))),m.command("update <room-ref>").description("Update one Room using the version returned by room get or room list").requiredOption("--version <version>","Exact Room version returned by Shennian").option("--name <name>","New Room name").option("--visibility <visibility>","private or public_read").option("--join-policy <policy>","open, approval, or closed").action((i,e)=>l("room.update",a,()=>{u("update");const s=e.name?.trim();return e.name!==void 0&&(!s||s.length>80)&&r("invalid_room_name","Room name must contain 1 to 80 characters."),e.visibility!==void 0&&e.visibility!=="private"&&e.visibility!=="public_read"&&r("invalid_room_visibility","--visibility must be private or public_read."),e.joinPolicy!==void 0&&e.joinPolicy!=="open"&&e.joinPolicy!=="approval"&&e.joinPolicy!=="closed"&&r("invalid_room_join_policy","--join-policy must be open, approval, or closed."),s===void 0&&e.visibility===void 0&&e.joinPolicy===void 0&&r("room_update_required","Provide --name, --visibility, or --join-policy."),t.update(h(i),{version:z(e.version),...s===void 0?{}:{name:s},...e.visibility===void 0?{}:{visibility:e.visibility},...e.joinPolicy===void 0?{}:{joinPolicy:e.joinPolicy}})})),m.command("delete <room-ref>").description("Delete this user's Room chat record without physically deleting the Room").requiredOption("--version <version>","Exact Room version returned by Shennian").action((i,e)=>l("room.delete",a,()=>(u("delete"),t.deleteRecord(h(i),z(e.version))))),m.command("join <invite-url>").description("Join or request access through an official Room invitation").option("--message <message>","Introduction and reason required when owner approval is enabled").action(async(i,e)=>l("room.join",a,async()=>{u("join");const s=P(i,"invite"),d=e.message?.trim();return d&&d.length>500&&r("join_message_too_long","Join request message must be at most 500 characters."),t.join(s.roomId,s.inviteToken,d)})),m.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async i=>l("room.open",a,async()=>{u("open");const e=P(i,"stable");return t.open(e.roomId)})),m.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async i=>l("room.status",a,()=>{const e=b();return e?(i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(e,"status",{})):t.status(A(i))})),m.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(i,e)=>{await l("room.read",a,async()=>{e.before&&e.after&&r("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),e.before&&!L(e.before)&&r("invalid_sequence","--before sequence is invalid."),e.after&&!L(e.after)&&r("invalid_sequence","--after sequence is invalid.");const s=Number(e.limit);(!Number.isInteger(s)||s<1||s>50)&&r("invalid_limit","--limit must be between 1 and 50.");const d=b();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(d,"read",{...e.before?{before:e.before}:{},...e.after?{after:e.after}:{},limit:s});const R=A(i),w=await t.read(R,{before:e.before,after:e.after,limit:s});return W(w,R,c)})}),m.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",K,[]).action(async(i,e)=>{await l("room.send",a,async()=>{const s=e.text?.trim()??"";!s&&e.file.length===0&&r("room_message_empty","Provide --text or at least one --file."),e.file.length>10&&r("room_attachments_too_many","A message can contain at most 10 files.");const d=b();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(d,"send",{clientMessageId:`managed:${x.randomUUID()}`,text:s,files:e.file});const R=A(i),w=[];for(const k of e.file)w.push(await B(t,R,k));return t.send(R,{clientMessageId:`cli:${x.randomUUID()}`,...s?{text:s}:{},...w.length>0?{attachmentIds:w}:{}})})}),m.command("pause").description("Pause the current managed Agent in its bound Room").requiredOption("--reason <reason>","Short reason shown to all Room members").action(async i=>l("room.pause",a,()=>{const e=b();return e||r("managed_room_context_required","Pause is only available in managed Room context.",j()),g(e,"pause",{reason:i.reason})})),m.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(i,e)=>{await l("room.attachment.get",a,async()=>{const s=b(),d=s?i:e;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&r("invalid_attachment_ref","attachment-id is invalid."),s?g(s,"attachment.get",{attachmentId:d}):c.materialize(A(i),d)})}),m.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async i=>l("room.leave",a,()=>(u("leave"),t.leave(h(i)))));const y=m.command("invite").description("Read or reset the current Room invitation");y.command("get <room-ref>").description("Get the current invitation and version for one Room").action(i=>l("room.invite.get",a,async()=>{u("invite get");const e=f(await t.getInvite(h(i)),"invalid_room_response"),s=f(e.invite,"invalid_room_response");return typeof s.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...e,sharing:await p(s.url)}})),y.command("reset <room-ref>").description("Reset an invitation using the version returned by room invite get").requiredOption("--version <version>","Positive invitation version").action((i,e)=>l("room.invite.reset",a,async()=>{u("invite reset");const s=f(await t.resetInvite(h(i),D(e.version,"room_invite_version")),"invalid_room_response"),d=f(s.invite,"invalid_room_response");return typeof d.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...s,sharing:await p(d.url)}}));const _=m.command("member").description("List or remove Room members");_.command("list <room-ref>").description("List public member references and versions for one Room").action(i=>l("room.member.list",a,()=>(u("member list"),t.listMembers(h(i))))),_.command("remove <room-ref>").description("Remove one exact member reference using its listed version").requiredOption("--member-ref <member-ref>","Public member reference returned by room member list").requiredOption("--version <version>","Positive member version returned by room member list").action((i,e)=>l("room.member.remove",a,()=>(u("member remove"),/^member_[A-Za-z0-9_-]{32,64}$/.test(e.memberRef)||r("invalid_room_member_ref","Use a memberRef returned by room member list."),t.removeMember(h(i),e.memberRef,D(e.version,"room_member_version"))))),m.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>S(a,"command_removed","Room search is not available to Agents.",null)),m.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>S(a,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}function Z(o,n){return[n?"\u9080\u8BF7\u4F60\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\u300C"+n+"\u300D\u3002":"\u70B9\u51FB\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF1A",...n?["\u76F4\u63A5\u70B9\u51FB\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF0C\u8BA9\u5B83\u5B89\u88C5\u795E\u5FF5\u3001\u5B8C\u6210\u914D\u5BF9\u5E76\u52A0\u5165\u7FA4\u804A\u3002"]:[],"\u9080\u8BF7\uFF1A"+o,"\u5B89\u88C5\uFF1A"+M].join(`
|
|
2
|
+
`)}async function l(o,n,t){try{const c=await t();n.write(`${JSON.stringify({ok:!0,command:o,result:c})}
|
|
3
|
+
`)}catch(c){if(c instanceof O){S(n,c.code,c.message,c.nextAction);return}if(c instanceof N){S(n,c.code,c.message,H(c.code));return}S(n,"room_command_failed","Shennian Room command failed.",null)}}function b(){return process.env[F]?.trim()||null}async function g(o,n,t){const c=process.env[C]?.trim();if(!c||!I.isAbsolute(c))throw new N("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return E({method:"managed-room.execute",params:{context:o,action:n,...t},paths:V(c),timeoutMs:n==="send"||n==="attachment.get"?18e4:15e3})}function u(o){b()&&r("managed_room_action_forbidden",`${o} is not available in managed Room context.`)}function A(o){return o||r("room_ref_required","Use an official Room URL returned by Shennian.",j()),h(o)}function h(o){return P(o,"stable").roomId}function P(o,n){let t;const c=n==="invite"?"invalid_invite_url":"invalid_room_ref",a=n==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{t=new URL(o)}catch{r(c,a)}const v=t.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),p=t.searchParams.get("invite");return(t.protocol!=="https:"||t.hostname!=="app.shennian.net"||t.port||t.username||t.password||t.hash||!v||t.searchParams.has("access")||[...t.searchParams.keys()].some(m=>m!=="invite")||n==="stable"&&p!=null||n==="invite"&&(p==null||!/^[A-Za-z0-9_-]{32,256}$/.test(p)))&&r(c,a),{roomId:v[1],...p?{inviteToken:p}:{}}}function L(o){return/^(0|[1-9]\d{0,19})$/.test(o)}function z(o){const n=new Date(o);return(Number.isNaN(n.getTime())||n.toISOString()!==o)&&r("invalid_room_version","Use the exact version returned by room get or room list."),o}function D(o,n){/^[1-9]\d*$/.test(o)||r(n,"Version must be a positive integer.");const t=Number(o);return Number.isSafeInteger(t)||r(n,"Version must be a positive integer."),t}function K(o,n){return[...n,o]}async function B(o,n,t){const c=I.resolve(t);let a;try{a=await q.promises.lstat(c)}catch{r("file_not_found",`File does not exist: ${t}`)}(!a.isFile()||a.isSymbolicLink())&&r("invalid_file",`Not a regular file: ${t}`),(a.size<1||a.size>100*1024*1024)&&r("invalid_file_size","Each file must be between 1 byte and 100 MB.");const v=await q.promises.readFile(c),p=x.createHash("sha256").update(v).digest("hex"),m=X(c),U=await o.initializeAttachment(n,{clientUploadId:`cli:${x.randomUUID()}`,name:I.basename(c),mediaKind:J(I.basename(c),m),mimeType:m,byteSize:a.size,sha256:p}),y=f(U.attachment,"invalid_attachment_response"),_=f(U.upload,"invalid_attachment_response");(typeof y.id!="string"||typeof _.url!="string"||_.method!=="PUT")&&r("invalid_attachment_response","Shennian returned an invalid attachment response.");const i=f(_.headers,"invalid_attachment_response");return Object.values(i).some(e=>typeof e!="string")&&r("invalid_attachment_response","Shennian returned invalid upload headers."),Y(_.url),await o.upload(_.url,i,v),await o.completeAttachment(n,y.id),y.id}function X(o){const n=I.extname(o).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".heif":"image/heif",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[n]??"application/octet-stream"}function W(o,n,t){return Array.isArray(o.messages)||r("invalid_server_response","Shennian returned invalid messages."),{messages:o.messages.map(c=>{const a=f(c,"invalid_server_response"),v=Array.isArray(a.attachments)?a.attachments:[];return{...a,attachments:v.map(p=>{const m=f(p,"invalid_server_response");return(typeof m.attachmentId!="string"||typeof m.name!="string"||typeof m.mimeType!="string"||typeof m.byteSize!="string"||typeof m.sha256!="string")&&r("invalid_server_response","Shennian returned invalid attachment metadata."),{...m,...t.lookup(n,m)}})}})}}function f(o,n){return(!o||typeof o!="object"||Array.isArray(o))&&r(n,"Invalid structured response."),o}function Y(o){let n;try{n=new URL(o)}catch{r("invalid_attachment_response","Invalid upload URL.")}const t=n.protocol==="http:"&&(n.hostname==="localhost"||n.hostname==="127.0.0.1");(n.protocol!=="https:"&&!t||n.username||n.password||n.hash)&&r("invalid_attachment_response","Invalid upload URL.")}function r(o,n,t=null){throw new O(o,n,t)}function S(o,n,t,c){o.write(`${JSON.stringify({ok:!1,error:n,message:t,nextAction:c})}
|
|
4
|
+
`),o.fail()}export{pe as registerRoomCommand};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{loadConfig as l,saveConfig as g}from"../config/index.js";import{spawnCommandString as
|
|
1
|
+
import{loadConfig as l,saveConfig as g}from"../config/index.js";import{spawnCommandString as y}from"../agents/command-spec.js";import{assessCustomRuntimeSecurity as w}from"../agents/custom-managed-runtime.js";function p(n){return n.models?.length?n.models:n.model?[n.model]:[]}async function C(n,s={}){return new Promise((r,t)=>{const e=y(n,["/caps"],{stdio:["ignore","pipe","pipe"]});let c="",u="",m=!1;const a=i=>{m||(m=!0,clearTimeout(v),i())};e.stdout?.on("data",i=>{c+=i.toString()}),e.stderr?.on("data",i=>{u+=i.toString()}),e.on("error",i=>a(()=>t(new Error(`Failed to run command: ${i.message}`)))),e.on("close",()=>a(()=>{const i=c.trim().split(`
|
|
2
2
|
`).find(o=>o.trim().startsWith("{"));if(!i){t(new Error(`Command did not output valid caps JSON.
|
|
3
|
-
stdout: ${
|
|
4
|
-
stderr: ${
|
|
5
|
-
`)}function d(
|
|
6
|
-
`),process.exitCode=1}export{
|
|
3
|
+
stdout: ${c.trim()}
|
|
4
|
+
stderr: ${u.trim()}`));return}try{const o=JSON.parse(i);if(!o.name||!o.mode)throw new Error('Caps JSON must contain "name" and "mode" fields');if(!s.allowNoModels&&p(o).length===0)throw new Error('Caps JSON must contain either "model" or a non-empty "models" array');if(o.mode!=="spawn"&&o.mode!=="stdio")throw new Error(`Caps "mode" must be "spawn" or "stdio", got: ${o.mode}`);r(o)}catch(o){t(o instanceof Error?o:new Error(`Failed to parse caps JSON: ${i}`))}}));const v=setTimeout(()=>{e.kill("SIGTERM"),a(()=>t(new Error("Timed out waiting for caps response (5s)")))},5e3)})}function A(n){const s=n.command("runtime").description("Manage custom Agent runtimes");s.command("add <name>").description("Register a custom Agent runtime").requiredOption("--command <cmd>","Command to run the runtime").action(async(r,t)=>{if(!/^[a-zA-Z0-9_-]+$/.test(r)){d("invalid_runtime_name","Runtime name may contain only letters, numbers, hyphens, and underscores.");return}let e;try{e=await C(t.command)}catch(a){d("runtime_probe_failed",a instanceof Error?a.message:String(a));return}const c={command:t.command,caps:e},u=w(c);if(e.security&&u.state==="unverified"){d("runtime_security_declaration_invalid",`Custom runtime security declaration could not be verified: ${u.reason}`);return}const m=l();m.customAgents=m.customAgents??{},m.customAgents[r]=c,g(m),f("runtime.add",{runtimeRef:`custom:${r}`,name:e.name,models:p(e),mode:e.mode,version:e.version??null,managedProtection:"unverified",securityDeclaration:u.state})}),s.command("list").description("List registered custom Agent runtimes").action(()=>{const r=Object.entries(l().customAgents??{}).map(([t,e])=>({runtimeRef:`custom:${t}`,name:e.caps.name,models:p(e.caps),mode:e.caps.mode,command:e.command,version:e.caps.version??null,managedProtection:"unverified",securityDeclaration:w(e).state}));f("runtime.list",{runtimes:r})}),s.command("remove <name>").description("Unregister a custom Agent runtime").action(r=>{const t=l();if(!t.customAgents?.[r]){d("runtime_not_found",`No custom runtime named "${r}" exists.`);return}delete t.customAgents[r],g(t),f("runtime.remove",{runtimeRef:`custom:${r}`})})}function f(n,s){process.stdout.write(`${JSON.stringify({ok:!0,command:n,result:s})}
|
|
5
|
+
`)}function d(n,s){process.stdout.write(`${JSON.stringify({ok:!1,error:n,message:s,nextAction:null})}
|
|
6
|
+
`),process.exitCode=1}export{p as getCapsModels,C as probeCaps,A as registerRuntimeCommand};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{SupportClient as m,SupportClientError as c}from"../support/support-client.js";const f=2e3,d=[/\bBearer\s+\S+/i,/\b(sk|pk)-[A-Za-z0-9_-]{8,}\b/,/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,/\b[A-Za-z0-9_+/]{40,}={0,2}\b/,/\/Users\/\S+/,/\/home\/\S+/,/\/root\/\S+/,/[A-Za-z]:\\\S+/,/\.env(\.[a-z]+)?\b/i],l={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function S(e,t={}){const n=t.client??new m,o=t.io??l,s=e.command("support").description("Contact Shennian support through the official Room after pairing");s.command("status").description("Check official support Room membership without joining").action(()=>u("support.status",o,()=>n.status())),s.command("ask").description("Send a redacted question and join the official support Room if needed").requiredOption("--text <text>","Redacted question; never include secrets, source, or full logs").action(r=>u("support.ask",o,()=>n.ask(_(r.text)))),s.command("read").description("Read replies after support contact has joined the official Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(r=>u("support.read",o,()=>{r.before&&r.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),r.before&&!p(r.before)&&i("invalid_sequence","--before sequence is invalid."),r.after&&!p(r.after)&&i("invalid_sequence","--after sequence is invalid.");const a=Number(r.limit);return(!Number.isInteger(a)||a<1||a>50)&&i("invalid_limit","--limit must be between 1 and 50."),n.read({before:r.before,after:r.after,limit:a})}))}async function u(e,t,n){try{const o=await n();t.write(`${JSON.stringify({ok:!0,command:e,result:o})}
|
|
2
|
+
`)}catch(o){if(o instanceof c){const s=o.nextAction??b(o.code);t.write(`${JSON.stringify({ok:!1,error:o.code,message:o.message,nextAction:s})}
|
|
3
|
+
`),t.fail();return}t.write(`${JSON.stringify({ok:!1,error:"support_command_failed",message:"Shennian support command failed.",nextAction:null})}
|
|
4
|
+
`),t.fail()}}function _(e){const t=e.trim();return t||i("support_text_required","Support text is required."),t.length>f&&i("support_text_too_long","Support text must be at most 2000 characters."),d.some(n=>n.test(t))&&i("support_text_contains_sensitive_data","Remove tokens, absolute paths, environment values, source code, and full logs before contacting support.",{type:"ask_user",message:"Rewrite the issue as a short redacted summary, then run support ask again."}),t}function b(e){return e==="support_room_not_joined"?{type:"run_command",command:"shennian support ask --text <redacted-question>"}:e==="support_room_pending_approval"||e==="official_room_unavailable"?{type:"retry_later",message:"The official support Room is temporarily unavailable."}:null}function p(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function i(e,t,n=null){throw new c(e,t,n)}export{S as registerSupportCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAvailableAgentType as
|
|
1
|
+
import{isAvailableAgentType as a}from"@shennian/wire";import{DaemonIpcError as n}from"./protocol.js";function g(s){let d=!1;const u=()=>{const e=s.getStatus();if(e.remoteAccessDisabled)throw new n("daemon_paused","Shennian is paused; resume it only when requested.");if(!e.running)throw new n("daemon_unavailable","Shennian is not running.")};return{"agent.discover":async e=>{u();const o=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:null,l=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.compact===!0),p=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.refresh===!0),c=(p?await s.discover(o??void 0):s.getSnapshot()).filter(t=>!o||t.type===o);return{modelTested:!1,compact:l,refreshed:p,agents:c.map(t=>{const i=t.models.find(r=>r.isDefault),y=(i?[i,...t.models.filter(r=>r.id!==i.id)]:t.models).map(r=>({id:r.id,name:r.name,isDefault:r.isDefault===!0}));return{type:t.type,name:t.display?.name??t.type,authState:t.authState??"unknown",...t.runtime?.cliVersion?{version:t.runtime.cliVersion}:{},...t.runtime?.compatibility?{compatibility:t.runtime.compatibility}:{},...t.runtimePlatform?{runtimePlatform:t.runtimePlatform}:{},...t.runtimeIsWsl!==void 0?{runtimeIsWsl:t.runtimeIsWsl}:{},supportsStandardSession:t.supportsStandardSession===!0,...t.supportsAgent!==void 0?{supportsAgent:t.supportsAgent}:{},...t.supportedAgentExecutionModes?{supportedAgentExecutionModes:t.supportedAgentExecutionModes}:{},supportsManagedAgent:t.supportsManagedAgent===!0,supportsManagedNativeFull:t.supportsManagedNativeFull===!0,supportsRoomTools:t.supportsRoomTools===!0,supportsWorker:t.supportsWorker===!0,supportsManagerMode:t.supportsManagerMode===!0,supportsExternalWorkerTarget:t.supportsExternalWorkerTarget===!0,modelCount:t.models.length,defaultModelId:i?.id??null,...l?{}:{models:y,modelsTruncated:!1}}})}},"agent.check":async e=>{if(u(),!e||typeof e!="object"||Array.isArray(e)||typeof e.agentType!="string"||!a(e.agentType)||typeof e.workDir!="string"||!e.workDir.trim()||e.modelId!=null&&typeof e.modelId!="string")throw new n("agent_validation_invalid_configuration","Select an AI and its absolute working directory.");if(d)throw new n("agent_check_in_progress","An AI reply check is already running.");d=!0;try{const o=await s.validate({runtimeAdapter:e.agentType,workDir:e.workDir,modelId:e.modelId??null,sessionMode:"standard"});if(!o.ok)throw new n(o.errorCode,o.errorCode);return{ready:!0,agentType:e.agentType,modelId:e.modelId??null,scope:"hello_reply"}}finally{d=!1}}}}export{g as createAgentReadinessHandlers};
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import
|
|
2
|
-
`),process.exitCode=1}function qo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(z),i.enablePositionalOptions(),Ke(i),qe(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await _e(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=G("env.json");try{const e=JSON.parse(F.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=fo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=co({lockPath:zo,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Jo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:z}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let $=null;try{$=ro()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const D=await oo();if(D){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{ao({fromVersion:z,targetVersion:D.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:D.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${D.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Bo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??W.cn.url,h=de(),l=await so({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),V=O(l),f=new Ro,U=new Po,R=new $o(f),S=new bo(u),ve=new xo({actionClient:S}),Ie=new Oo({actionClient:S});let y=null,r;const q=Vo({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let L=null,j=null,H=null;const b=await me({paths:ho(),handlers:{...Io({daemonVersion:h,protocolVersion:Co,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!L)throw new Error("upgrade_unavailable");return L(e)},prepareExternalUpgrade:e=>{if(!j)throw new Error("external_upgrade_unavailable");return j(e)},cancelExternalUpgrade:()=>{if(!H)throw new Error("external_upgrade_unavailable");return H()}}),...So({getStatus:()=>ie(),getSnapshot:()=>O(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await yo(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=O(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(B=>B.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return wo(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:vo(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const _=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",_),process.once("SIGTERM",_),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",_),process.removeListener("SIGTERM",_);const Y=`${Ko(u)}/relay/machine`,M=m.machineToken;if(!M)return;try{await Ho({serverUrl:u,machineToken:M,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&mo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),T({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,E=null,N=0,v=no();const Z=Ee(m.autoUpgrade),d=new Ae({serverUrl:Y,machineToken:M,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Lo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),T({level:"info",wsEvent:"daemon.connected"}),V.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:V}}),lo(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(V)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),E?.start();const e=++N,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&io(o.jobId,o.to),eo(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),po({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===N&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===N&&d.getState()==="connected"){if(o instanceof Go)return;const t=o instanceof Fo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{N+=1,r?.revokeProjectFsHandles(),E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qo(e)}`),T({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),T({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Ao(d,E,e);return}r.handleReq(e)}});P=go()?new uo(d):null,E=new Eo({registry:f,observations:U,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new To().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{R.revokeSession(e.registryKey),e.lifecycleState==="closed"?r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new to({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(Z),r=new Qe(d,P,h,x,new ko({registry:f,observations:U,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,Ye(M),()=>O(l)),L=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await Ge(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:B=>r.prepareForUpgrade(B),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},j=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},H=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new _o({registry:f,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Uo(f,e)||!Do(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new No,resolveManagedAgentPolicy:e=>Mo({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,jo):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),F.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(A.gray(`
|
|
3
|
-
Disconnecting...`)),T({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),x.stop(),P?.stop(),
|
|
1
|
+
import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import E from"chalk";import Re from"node:os";import G from"node:fs";import{loadConfig as p,saveConfig as be,getConfigPath as ke,getShennianDir as Pe,resolveShennianPath as W,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as Ne,clearDaemonLauncher as re,findRunningDaemonProcessIds as xe,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Oe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAdapterCommand as He}from"./commands/adapter.js";import{registerAgentCommand as Be}from"./commands/agent.js";import{registerManagerCommand as Fe}from"./commands/manager.js";import{registerWorkerCommand as Ge}from"./commands/worker.js";import{handleUpgradeStart as We,registerUpgradeCommand as ze}from"./commands/upgrade.js";import{registerToolsCommand as Je}from"./commands/tools.js";import{registerQueueCommand as Ke}from"./commands/queue.js";import{registerIntegrationCommand as qe}from"./commands/integration.js";import{registerRoomCommand as Qe}from"./commands/room.js";import{registerSupportCommand as Ye}from"./commands/support.js";import{SessionManager as Ze}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Xe}from"./session/handlers/project-fs.js";import{SERVERS as z,regionToUrl as eo,urlToRegion as oo}from"./region.js";import{clearUpgradeAttempt as no,getCurrentVersion as de,handleStartupCrashCheck as to,readUpgradeAttempt as ro}from"./upgrade/engine.js";import{AutoUpgradeScheduler as ao}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as io}from"./upgrade/updater-bootstrap.js";import{launchUpdater as so,markUpdaterHealthy as co}from"./upgrade/updater-launcher.js";import{detectAgents as lo}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as mo}from"./commands/daemon-instance-lock.js";le();const O=de();import{getCachedAgentInfos as $,resolveAgentInfos as po}from"./agents/model-registry.js";import{initCliLogReporter as go,reportLog as T,reportUpgradeAggregateMetric as uo}from"./log-reporter.js";import{isNativeFusionEnabled as fo}from"./native-fusion/config.js";import{NativeSessionFusionService as ho}from"./native-fusion/service.js";import{startDaemonLogRetention as vo}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as Io,resolveManagedRoomIpcPaths as So}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as yo}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as wo}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as Co}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as Ro}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as bo}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as ko}from"./room/session-registry.js";import{HttpRoomBindingClient as Po}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ao}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Eo}from"./room/host-session-observations.js";import{RoomMirrorSyncService as To}from"./room/mirror-sync-service.js";import{handleLiveRoomDelivery as Do}from"./room/live-delivery.js";import{NativeSourceDeletionStore as Uo}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Mo,isCurrentRegistration as _o}from"./room/registration-authority.js";import{RoomActivationService as No,resolveManagedAgentPolicy as xo}from"./room/activation-service.js";import{RoomActivationAudit as Oo}from"./room/activation-audit.js";import{RoomAttachmentCache as $o}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Vo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Lo}from"./room/managed-context.js";import{createManagedRoomExecutor as jo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Ho}from"./session/store.js";import{OWNER_DIRECT_TURN_OPTIONS as Bo}from"./session/queue-helpers.js";import{synchronizeMachinePersonalAuthority as Fo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as Go}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Wo,DeletionSyncTerminalError as zo}from"./session/deletion-sync-state.js";const fe=Pe(),Jo=W("daemon.pid"),Ko=W("daemon.instance.lock");function qo(){try{const a=G.readFileSync(Jo,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function Qo(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function J(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
|
|
2
|
+
`),process.exitCode=1}function Yo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(O),i.enablePositionalOptions(),qe(i),Qe(i),Ye(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=W("env.json");try{const e=JSON.parse(G.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=vo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=mo({lockPath:Ko,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=qo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:O}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let V=null;try{V=io()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const D=await to();if(D){V||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{so({fromVersion:O,targetVersion:D.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:D.jobId},{readiness:V}),console.error(`[upgrade] New version crashed ${D.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Go()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??z.cn.url,h=de(),l=await lo({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),L=$(l),f=new ko,U=new Eo,R=new Lo(f),S=new Po(u),ve=new $o({actionClient:S}),Ie=new Vo({actionClient:S});let y=null,r;const q=jo({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,n)=>r.getManagedRoomActiveRunId(e,n),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let j=null,H=null,B=null;const b=await me({paths:Io(),handlers:{...yo({daemonVersion:h,protocolVersion:bo,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!j)throw new Error("upgrade_unavailable");return j(e)},prepareExternalUpgrade:e=>{if(!H)throw new Error("external_upgrade_unavailable");return H(e)},cancelExternalUpgrade:()=>{if(!B)throw new Error("external_upgrade_unavailable");return B()}}),...wo({getStatus:()=>ie(),getSnapshot:()=>$(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await Co(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=$(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(F=>F.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return Ro(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:So(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const M=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",M),process.once("SIGTERM",M),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",M),process.removeListener("SIGTERM",M);const Y=`${Qo(u)}/relay/machine`,_=m.machineToken;if(!_)return;try{await Fo({serverUrl:u,machineToken:_,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&go(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),T({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,A=null,N=0,v=ro();const Z=Ae(m.autoUpgrade),d=new Ee({serverUrl:Y,machineToken:_,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Ho(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),T({level:"info",wsEvent:"daemon.connected"}),L.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:L}}),po(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(L)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),A?.start();const e=++N,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&co(o.jobId,o.to),no(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),uo({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===N&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===N&&d.getState()==="connected"){if(o instanceof zo)return;const t=o instanceof Wo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{N+=1,r?.revokeProjectFsHandles(),A?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${Yo(e)}`),T({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),T({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Do(d,A,e);return}r.handleReq(e)}});P=fo()?new ho(d):null,A=new To({registry:f,observations:U,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new Uo().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{e.lifecycleState==="closed"?(R.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new ao({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(Z),r=new Ze(d,P,h,x,new Ao({registry:f,observations:U,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,Xe(_),()=>$(l)),j=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await We(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:F=>r.prepareForUpgrade(F),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},H=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},B=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new No({registry:f,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!_o(f,e)||!Mo(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},effectiveModelsBySessionId:t.effectiveModelId!==void 0?{[e.nianSessionId]:t.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new Oo,resolveManagedAgentPolicy:e=>xo({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,Bo):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),G.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(E.gray(`
|
|
3
|
+
Disconnecting...`)),T({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),x.stop(),P?.stop(),A?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>J()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>J()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>J());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ke()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=oo(a.serverUrl);console.log(`Region: ${g} (${z[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(E.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=eo(c),be(g),console.log(E.green(`\u2713 Server set to ${z[c].label}`)),console.log(E.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(E.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i,{clientVersion:O}),Be(i),Fe(i),Ge(i),ze(i),Je(i),Ke(i),i.parse();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import
|
|
2
|
-
`).trim()}function G(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function F(g,e){return`manager-inbox:${N("sha256").update(g).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function J(g){const e=[];let t=0;for await(const r of g){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(g,e,t,r){g.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,t){t?this.pendingWorkerOrigins.set(e,t):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new D}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((t,r)=>{this.handleIpc(t,r)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((t,r)=>{this.server.once("error",r),this.server.listen(0,"127.0.0.1",()=>t())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();p.mkdirSync(M.dirname(e),{recursive:!0}),p.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),p.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),t=JSON.parse(p.readFileSync(e,"utf-8"));(t.url===this.ipcUrl||t.token===this.ipcToken)&&p.unlinkSync(e)}catch{}}getInjectedEnv(e,t,r,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:t??"",SHENNIAN_MANAGER_WORKDIR:f(r),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:f(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,t){if(t){this.managedPolicies.set(e,t);const r=this.registry.getManager(e);r&&!r.managedRoomBound&&this.registry.upsertManager({...r,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,t){const r=this.registry.getManager(e),n=this.managedPolicies.get(e);return!r||!n||f(t)!==r.workDir?null:n}isExternalWorkerController(e,t){const r=this.registry.getManager(e);return r?.controllerKind==="ephemeral"&&f(t)===r.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const t=this.registry.retireManager(e);this.managedPolicies.delete(e);const r=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{r.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{r.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{r.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=r.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{r.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(r.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of t)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return t.map(s=>s.sessionId)}setManagerWorkerDefaults(e,t,r){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:t??null,defaultWorkerModelId:r??null})}getManagerWorkerDefaults(e){const t=this.registry.getManager(e);return{agentType:t?.defaultWorkerAgentType??null,modelId:t?.defaultWorkerModelId??null}}noteManagerAgentSession(e,t,r,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:t,workDir:f(r),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,t){const r=this.findWorker(e);if(!r)return;const n={lastActivityAt:new Date().toISOString(),runId:t.runId};if(t.agentSessionId&&(n.agentSessionId=t.agentSessionId),t.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...r.runRoomOrigins??{},[t.runId]:i})}const a=`${e}:${t.runId}`;let s=t.message||r.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(t.state==="delta"&&t.text&&!t.thinking){const i=(this.workerTextAcc.get(a)??"")+t.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(t.state==="final"||t.state==="error"||t.state==="aborted"){n.status=t.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=t.state==="final"?i:t.message||i),this.workerTextAcc.delete(a),r.runRoomOrigins?.[t.runId]){const o={...r.runRoomOrigins};delete o[t.runId],n.runRoomOrigins=o}}else t.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&t.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:t.runId,roomOrigin:r.runRoomOrigins?.[t.runId]??null,summary:t.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,t.state,t.runId,s,r.runRoomOrigins?.[t.runId]??null,t.errorCode==="auth_required"||t.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,t){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){m(t,401,{ok:!1,error:"Unauthorized"});return}try{const r=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(r.pathname.startsWith("/channel/")||r.pathname.startsWith("/external/")||r.pathname.startsWith("/wechat-rpa/")){m(t,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,r.pathname,n)??await S(a,r.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){m(t,s.status,s.body);return}m(t,404,{ok:!1,error:`Unknown manager IPC path: ${r.pathname}`})}catch(r){if(r instanceof B){m(t,r.status,{ok:!1,error:r.code,message:r.message,nextAction:r.nextAction});return}m(t,400,{ok:!1,error:r instanceof Error?r.message:String(r)})}}async executeScopedWorkerAction(e,t,r,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await S({registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[t],r,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,t,r,n,a,s,u,i){if((await this.opts.dispatchReq(v({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,t,r){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:r,sessionId:e,role:"agent",ts:Date.now(),payload:t},"committed"),q(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:t.sessionId,kind:r==="final"?"worker.final":r==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),t=this.registry.load(),r=this.opts.getRuntime();for(const n of Object.values(t.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=r.sessions.get(n.sessionId)?.currentRunId,i=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=t.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
1
|
+
import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import f from"node:fs";import A from"node:os";import M from"node:path";import{ManagerRegistry as T}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as D}from"../session/index-publisher.js";import{RetiredChannelRuntime as q}from"./retired-external-channel-compat.js";import{resolveShennianPath as P}from"../config/index.js";import{buildExternalChannelInstructions as C}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as w}from"../personal-sync/epoch.js";import{EphemeralWorkerError as B,handleEphemeralWorkerIpcRoute as H,handleWorkerIpcRoute as S}from"./worker-ipc.js";import{deriveWorkerTitle as $,formatWorkerMarkdown as U}from"./worker-markdown.js";import{markTrustedManagerEvent as v}from"../session/manager-event-authority.js";const j=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),y=12*6e4;let b=null;function de(g){b=g}function he(){return b}function m(g){return M.resolve(g||A.homedir())}function p(g,e,r){g.writeHead(e,{"content-type":"application/json; charset=utf-8"}),g.end(JSON.stringify(r))}function R(g){return g.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function G(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function F(g,e){return`manager-inbox:${N("sha256").update(g).update("\0").update(e.map(t=>t.id).join("\0")).digest("hex").slice(0,32)}`}async function J(g){const e=[];let r=0;for await(const t of g){const n=Buffer.isBuffer(t)?t:Buffer.from(t);if(r+=n.length,r>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(g,e,r,t){g.client.sendRes({type:"res",id:e,ok:r,...r?{payload:t}:{error:String(t.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,r){r?this.pendingWorkerOrigins.set(e,r):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((r,t)=>{this.handleIpc(r,t)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((r,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>r())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();f.mkdirSync(M.dirname(e),{recursive:!0}),f.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),f.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),r=JSON.parse(f.readFileSync(e,"utf-8"));(r.url===this.ipcUrl||r.token===this.ipcToken)&&f.unlinkSync(e)}catch{}}getInjectedEnv(e,r,t,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:r??"",SHENNIAN_MANAGER_WORKDIR:m(t),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:m(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,r){if(r){this.managedPolicies.set(e,r);const t=this.registry.getManager(e);t&&!t.managedRoomBound&&this.registry.upsertManager({...t,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,r){const t=this.registry.getManager(e),n=this.managedPolicies.get(e);return!t||!n||m(r)!==t.workDir?null:n}isExternalWorkerController(e,r){const t=this.registry.getManager(e);return t?.controllerKind==="ephemeral"&&m(r)===t.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}getWorkerControllerSessionId(e,r){const t=this.findWorker(e);if(!t||m(r)!==t.workDir)return null;const n=this.registry.getManager(t.managedBy);return!n||n.workDir!==t.workDir?null:n.sessionId}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const r=this.registry.retireManager(e);this.managedPolicies.delete(e);const t=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{t.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{t.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{t.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=t.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{t.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(t.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of r)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return r.map(s=>s.sessionId)}setManagerWorkerDefaults(e,r,t){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:r??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const r=this.registry.getManager(e);return{agentType:r?.defaultWorkerAgentType??null,modelId:r?.defaultWorkerModelId??null}}noteManagerAgentSession(e,r,t,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:r,workDir:m(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,r){const t=this.findWorker(e);if(!t)return;const n={lastActivityAt:new Date().toISOString(),runId:r.runId};if(r.agentSessionId&&(n.agentSessionId=r.agentSessionId),r.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...t.runRoomOrigins??{},[r.runId]:i})}const a=`${e}:${r.runId}`;let s=r.message||t.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(r.state==="delta"&&r.text&&!r.thinking){const i=(this.workerTextAcc.get(a)??"")+r.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(r.state==="final"||r.state==="error"||r.state==="aborted"){n.status=r.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=r.state==="final"?i:r.message||i),this.workerTextAcc.delete(a),t.runRoomOrigins?.[r.runId]){const o={...t.runRoomOrigins};delete o[r.runId],n.runRoomOrigins=o}}else r.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&r.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:r.runId,roomOrigin:t.runRoomOrigins?.[r.runId]??null,summary:r.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(r.state==="final"||r.state==="error"||r.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,r.state,r.runId,s,t.runRoomOrigins?.[r.runId]??null,r.errorCode==="auth_required"||r.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,r){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){p(r,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){p(r,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,t.pathname,n)??await S(a,t.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){p(r,s.status,s.body);return}p(r,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){if(t instanceof B){p(r,t.status,{ok:!1,error:t.code,message:t.message,nextAction:t.nextAction});return}p(r,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async executeScopedWorkerAction(e,r,t,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await S({registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[r],t,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,r,t,n,a,s,u,i){if((await this.opts.dispatchReq(v({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:r,workDir:t,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,r,t){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:t,sessionId:e,role:"agent",ts:Date.now(),payload:r},"committed"),D(this.opts.getRuntime().client))}enqueueWorkerEvent(e,r,t,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:r.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||r.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),r=this.registry.load(),t=this.opts.getRuntime();for(const n of Object.values(r.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=t.sessions.get(n.sessionId)?.currentRunId,i=!!t.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=r.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
3
3
|
|
|
4
4
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
5
5
|
- \u6700\u8FD1\u6587\u672C\uFF1A${d||n.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
|
|
6
6
|
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
|
|
7
|
-
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,t,
|
|
7
|
+
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,r,t,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${k()}`,method:"chat.abort",params:{...w(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,r,!1,t,n)}bindManagerAdapterEvents(e,r){r.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,t.agentSessionId,n.workDir,n.modelId)}t.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(t.state==="final"||t.state==="error"||t.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,r){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,status:r})}async drainManagerInbox(e){const r=this.registry.getManager(e);if(!r||r.deliveringInboxEventIds?.length)return;const t=this.registry.listInboxEvents(e);if(!t.length)return;const n=t.find(d=>d.priority==="high"),a=this.opts.getRuntime().sessions.get(e);if(r.status!=="idle"&&!n||a?.currentRunId&&!n)return;const u=(n??t[0]).roomOrigin??null,i=JSON.stringify(u),o=n?[n]:t.filter(d=>JSON.stringify(d.roomOrigin??null)===i).slice(0,20),c=o.map(d=>{const l=this.registry.getWorkerForManager(e,d.workerSessionId);return U({kind:G(d.kind),sessionId:d.workerSessionId,title:l?.title||$(l?.userGoal||l?.summary||d.summary),body:d.summary})}).join(`
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
`),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...
|
|
11
|
+
`),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...r,status:"interrupting",deliveringInboxEventIds:o.map(l=>l.id)}),await this.interruptAndResumeManager(r,c,h,u)):(this.registry.upsertManager({...r,status:"running",deliveringInboxEventIds:o.map(l=>l.id)}),await this.dispatchManagerContinuation(r,c,!0,h,u)),this.registry.removeInboxEvents(e,o.map(l=>l.id));const d=this.registry.getManager(e);d&&this.registry.upsertManager({...d,deliveringInboxEventIds:[]})}catch(d){const l=this.registry.getManager(e);l&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${d instanceof Error?d.message:String(d)}`)}}acknowledgeManagerInboxDelivery(e){const r=this.registry.getManager(e),t=r?.deliveringInboxEventIds??[];!r||t.length===0||(this.registry.removeInboxEvents(e,t),this.registry.upsertManager({...r,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),r=this.opts.getRuntime();for(const t of Object.values(e.managers)){if(r.sessions.get(t.sessionId)?.currentRunId)continue;(t.status!=="idle"||t.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]});const a=this.registry.listInboxEvents(t.sessionId).filter(s=>s.kind==="worker.health"&&!r.sessions.get(s.workerSessionId)?.currentRunId).map(s=>s.id);this.registry.removeInboxEvents(t.sessionId,a),this.registry.listInboxEvents(t.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(t.sessionId)})}}async dispatchManagerContinuation(e,r,t=!1,n,a){const s=e.agentType??"manager",u=s==="manager"?e.modelId:e.providerModelId??void 0,i={...w(),sessionId:e.sessionId,text:r,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:u,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n,...a?{managerEventRoomOrigin:a}:{}};if((await this.opts.dispatchReq(v({type:"req",id:`manager-${t?"enqueue":"send"}-${k()}`,method:"chat.enqueue",params:i})))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(r=>C(r,void 0,e,"manager")).join(`
|
|
12
12
|
|
|
13
13
|
`).trim()}}export{le as ManagerRuntimeService,he as getManagerRuntimeService,de as setManagerRuntimeService};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function m(i,t){if(i.deletedAt)return[];const r=new Set((i.mentions??[]).filter(e=>e.principalType==="agent").map(e=>e.membershipId)),n=t.filter(p);return i.kind==="system"?o(i)?n.filter(e=>e.binding.activationMode==="auto_follow"&&e.binding.agentMembershipId!==i.systemEvent?.subjectMembershipId).map(e=>({registration:e,cause:"room_event"})):[]:i.author.principalType==="agent"?n.filter(e=>e.binding.agentMembershipId!==i.author.membershipId&&r.has(e.binding.agentMembershipId)).map(e=>({registration:e,cause:"explicit_mention"})):r.size>0?n.filter(e=>r.has(e.binding.agentMembershipId)).map(e=>({registration:e,cause:"explicit_mention"})):n.filter(e=>e.binding.activationMode==="auto_follow").map(e=>({registration:e,cause:"human_message"}))}function o(i){const t=i.systemEvent;return!t||(t.type==="membership_joined"||t.type==="membership_left")&&t.subjectPrincipalType==="agent"?!1:t.type==="membership_joined"||t.type==="membership_left"||t.type==="membership_removed"||t.type==="room_renamed"}function p(i){return!!(i.lifecycleState!=="closed"&&i.binding?.status==="active"&&i.binding.activationMode!=="paused"&&i.binding.agentMembershipId)}export{m as selectRoomActivationTargets};
|