shennian 0.5.41 → 0.5.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +92 -57
- package/dist/src/agents/config-status.js +1 -1
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/agents/project-client.js +1 -1
- package/dist/src/agents/shennian.js +6 -3
- package/dist/src/commands/config.js +1 -1
- package/dist/src/commands/pair.js +11 -11
- package/dist/src/commands/project.js +4 -4
- package/dist/src/commands/schedule.js +1 -1
- package/dist/src/commands/steward.js +12 -10
- package/dist/src/commands/worker.js +2 -2
- package/dist/src/config/server-binding.js +1 -0
- package/dist/src/guides/collaboration.js +2 -2
- package/dist/src/index.js +3 -3
- package/dist/src/manager/direct-worker-notifications.js +2 -0
- package/dist/src/manager/registry.js +1 -1
- package/dist/src/manager/runtime.js +8 -7
- package/dist/src/manager/worker-ipc.js +3 -1
- package/dist/src/personal-agent/automatic-model.js +1 -0
- package/dist/src/personal-agent/model-settings.js +2 -2
- package/dist/src/personal-agent/official-credential.js +1 -1
- package/dist/src/personal-agent/sdk-session.js +2 -2
- package/dist/src/personal-agent/system-prompt.js +3 -0
- package/dist/src/personal-workspace/service.js +1 -1
- package/dist/src/schedules/personal-context.js +1 -1
- package/dist/src/schedules/server-client.js +1 -1
- package/dist/src/session/handlers/personal-workspace.js +1 -1
- package/dist/src/session/handlers/personal.js +1 -1
- package/dist/src/session/handlers/steward.js +1 -1
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue.js +1 -1
- package/dist/src/steward/context.js +7 -0
- package/dist/src/steward/project-event.js +3 -0
- package/dist/src/steward/session.js +2 -2
- package/dist/src/steward/templates.js +12 -5
- package/dist/src/steward/worker-settings.js +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -1
- package/node_modules/@shennian/wire/dist/instance.js +1 -0
- package/node_modules/@shennian/wire/dist/message-payload.js +4 -4
- package/package.json +2 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import{personalModelSummary as
|
|
2
|
-
`)}catch(
|
|
3
|
-
`),
|
|
4
|
-
`)}catch{
|
|
5
|
-
`),
|
|
6
|
-
`)}
|
|
7
|
-
`)}
|
|
8
|
-
`)
|
|
9
|
-
`)}catch
|
|
10
|
-
`),
|
|
1
|
+
import{readWorkerSettings as u,saveWorkerSettings as y}from"../steward/worker-settings.js";import{personalModelSummary as h,savePersonalModel as S}from"../personal-agent/model-settings.js";import w from"node:fs";import{primaryMachineRequest as k}from"../steward/primary-machine.js";import{configurePersonalPaths as _}from"../steward/configure.js";import{readPersonalPaths as v}from"../steward/settings.js";import{getStewardDir as g}from"../steward/workspace.js";import{callDaemonIpc as x}from"../daemon-ipc/client.js";import{DaemonIpcError as c}from"../daemon-ipc/protocol.js";import{STEWARD_SAY_MAX_CHARS as f,STEWARD_SESSION_ID_ENV as b}from"../steward/session.js";const N={write:a=>process.stdout.write(a),fail:()=>{process.exitCode=1}};function U(a,i={}){const r=i.io??N,l=i.callDaemon??x,n=i.env??process.env,p=i.readStdin??(()=>w.readFileSync(0,"utf8")),s=a.command("steward").description("Talk to the user as the Shennian steward");s.command("primary").description("Read the account primary computer, or explicitly select this paired computer").option("--set-current","Select this paired computer as primary; never migrates files").option("--expected-revision <number>","Exact current primary-setting revision").option("--confirm","Confirm the primary computer selection").action(async e=>{try{const t=Number(e.expectedRevision);if(e.setCurrent&&(!e.confirm||e.expectedRevision===void 0||!Number.isSafeInteger(t)||t<0))throw new Error("personal_primary_confirmation_required");const o=await k(e.setCurrent?{expectedRevision:t,confirmed:!0}:void 0);r.write(`${JSON.stringify({ok:!0,...o})}
|
|
2
|
+
`)}catch(t){r.write(`${JSON.stringify({ok:!1,error:t instanceof Error&&/^personal_[a-z_]+$/.test(t.message)?t.message:"personal_primary_request_failed"})}
|
|
3
|
+
`),r.fail()}}),s.command("model").description("Configure the bundled personal Agent model; keys are accepted through stdin only").option("--official","Use the official Shennian model (default)").option("--custom","Use your own OpenAI-compatible model").option("--model <id>","Provider model ID").option("--base-url <url>","Provider Base URL").option("--context-window <tokens>","Provider context capacity").option("--key-stdin","Read API key from stdin; never put a key in process arguments").action(e=>{try{if(e.official&&e.custom)throw new Error;const o=e.official||e.custom||e.model||e.baseUrl||e.keyStdin||e.contextWindow?S({mode:e.official?"official":"custom",...e.model?{model:e.model}:{},...e.baseUrl?{baseUrl:e.baseUrl}:{},...e.keyStdin?{apiKey:p().trim()}:{},...e.contextWindow?{contextWindow:Number(e.contextWindow)}:{}}):h();r.write(`${JSON.stringify({ok:!0,...o})}
|
|
4
|
+
`)}catch{r.write(`${JSON.stringify({ok:!1,error:"personal_model_settings_invalid"})}
|
|
5
|
+
`),r.fail()}}),s.command("worker-defaults").description("Read or set the personal steward default Worker; does not install or sign in to providers").option("--agent <type>","Default discovered Worker Agent").option("--model <id>","Default model, otherwise use provider default").option("--workdir <path>","Fallback start directory; explicit task/project directory wins").option("--clear","Reset to builtin Shennian and the default personal directory").action(e=>{try{if(e.clear&&(e.agent||e.model||e.workdir)||e.model&&!e.agent)throw new Error;const t=e.clear?y({agentType:"shennian",modelId:null,workDir:null}):e.agent||e.workdir?y({...u(),...e.agent?{agentType:e.agent,modelId:e.model??null}:{},...e.workdir?{workDir:e.workdir}:{}}):u();r.write(`${JSON.stringify({ok:!0,...t})}
|
|
6
|
+
`)}catch{r.write(`${JSON.stringify({ok:!1,error:"personal_worker_settings_invalid"})}
|
|
7
|
+
`),r.fail()}}),s.command("paths").description("Show Personal Agent and diary directories (no credentials)").action(()=>{r.write(`${JSON.stringify({ok:!0,workspace:g(),diary:v().diary??null})}
|
|
8
|
+
`)}),s.command("configure").description("Set local Personal Agent / diary directories; restart Client after changing workspace").option("--workspace <directory>","Absolute Personal Agent directory").option("--diary <directory>","Absolute diary directory").option("--clear-diary","Disconnect the diary directory without deleting any files").action(e=>{try{r.write(`${JSON.stringify({ok:!0,..._(e),restartRequired:!0})}
|
|
9
|
+
`)}catch{r.write(`${JSON.stringify({ok:!1,error:"personal_configuration_failed",message:"Use absolute, non-symlink directories separate from Client credentials."})}
|
|
10
|
+
`),r.fail()}}),s.command("say [text]").description("Send a message the user will see on their phone; in a steward session everything else you output stays hidden from them").option("--file <path>","Read the message from a file").option("--stdin","Read the message from standard input").action(async(e,t)=>{try{const o=n[b]?.trim(),d=A(e,t,p),m=await l({method:"steward.say",params:o?{sessionId:o,text:d}:{text:d}});r.write(`${JSON.stringify({ok:!0,command:"steward.say",result:m})}
|
|
11
|
+
`)}catch(o){const d=o instanceof c,m=!d&&o?.code==="ENOENT";r.write(`${JSON.stringify({ok:!1,error:d?o.code:m?"daemon_unavailable":"steward_command_failed",message:d?o.message:m?"Shennian is not running on this computer.":"Shennian steward command failed.",nextAction:null})}
|
|
12
|
+
`),r.fail()}})}function A(a,i,r){if([a!==void 0,i.file!==void 0,i.stdin===!0].filter(Boolean).length!==1)throw new c("steward_say_invalid","Provide exactly one of: text, --file, --stdin.");let n;if(i.file!==void 0)try{n=w.readFileSync(i.file,"utf8")}catch{throw new c("steward_say_invalid","The message file could not be read.")}else if(i.stdin)try{n=r()}catch{throw new c("steward_say_invalid","Standard input could not be read.")}else n=a??"";if(n=n.trim(),!n||n.length>f)throw new c("steward_say_invalid",`Provide a non-empty message of at most ${f} characters.`);return n}export{U as registerStewardCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import c from"node:fs";import{resolveShennianPath as
|
|
2
|
-
`),r.ok||(process.exitCode=1)}function k(r){const t=r.command("worker").description("Start and control an external temporary Worker by opaque reference");t.command("start").description("Start a new Worker; returns the only reference that can control it").
|
|
1
|
+
import c from"node:fs";import{resolveShennianPath as m}from"../config/index.js";function u(){const r=m("runtime","manager-ipc.json");let t;try{t=c.statSync(r)}catch{throw new Error("daemon_unavailable")}if(process.platform!=="win32"){const n=typeof process.getuid=="function"?process.getuid():t.uid;if(t.uid!==n||(t.mode&63)!==0)throw new Error("worker_runtime_permissions_invalid")}const e=JSON.parse(c.readFileSync(r,"utf-8")),o=typeof e.url=="string"?e.url:"",s=typeof e.token=="string"?e.token:"";if(!/^http:\/\/127\.0\.0\.1:\d+$/.test(o)||!s)throw new Error("daemon_unavailable");return{url:o,token:s}}function l(r){return r==="daemon_unavailable"?"Check shennian status --json and start the existing paired daemon if it was not intentionally paused.":r==="worker_runtime_permissions_invalid"?"Repair or update the existing Shennian installation; do not bypass the local runtime file permissions.":"Check the local Shennian daemon and retry the same command once."}async function a(r,t){try{const e=u(),o=r==="wait"?Math.max(2e3,Math.min(Number(t.timeoutMs||6e5)+5e3,605e3)):r==="start"||r==="send"?18e4:15e3,s=await fetch(`${e.url}/workers/${r}`,{method:"POST",headers:{authorization:`Bearer ${e.token}`,"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(o)}),n=await s.json().catch(()=>({ok:!1,error:"worker_invalid_response",message:s.statusText}));return s.ok&&n.ok?n:{ok:!1,error:n.error||"worker_command_failed",...n.message?{message:n.message}:{},nextAction:n.nextAction||"Check the selected Agent, working directory, account, and Worker reference."}}catch(e){const o=e instanceof Error&&e.message==="daemon_unavailable"||e instanceof Error&&e.message==="worker_runtime_permissions_invalid"?e.message:e instanceof Error&&e.name==="TimeoutError"?"ipc_timeout":"worker_command_failed";return{ok:!1,error:o,message:e instanceof Error?e.message:String(e),nextAction:l(o)}}}function d(r){return r.messageFile?c.readFileSync(r.messageFile,"utf8"):r.message??""}function i(r){process.stdout.write(`${JSON.stringify(r)}
|
|
2
|
+
`),r.ok||(process.exitCode=1)}function k(r){const t=r.command("worker").description("Start and control an external temporary Worker by opaque reference");t.command("start").description("Start a new Worker; returns the only reference that can control it").option("--agent <agent>","Agent type in the daemon snapshot, for example claude").option("--request-id <id>","Stable personal delegation ID; reuse on retries").option("--workdir <path>","Task/project directory; otherwise personal Worker start directory, or current directory outside steward").option("--title <title>","Short Worker title").option("--model <model>","Optional model id from the daemon snapshot; otherwise use the Agent default").option("--message <text>","Worker task").option("--message-file <path>","Read the Worker task from a file").option("--json","Print JSON (default)").action(async e=>i(await a("start",{agentType:e.agent,requestId:e.requestId,stewardSessionId:process.env.SHENNIAN_MANAGED_ROOM_CONTEXT?void 0:process.env.SHENNIAN_STEWARD_SESSION_ID,workDir:e.workdir??(process.env.SHENNIAN_STEWARD_SESSION_ID&&!process.env.SHENNIAN_MANAGED_ROOM_CONTEXT?void 0:process.cwd()),title:e.title,modelId:e.model,message:d(e)}))),t.command("list").description("Recover Worker references for the current personal steward only").action(async()=>i(await a("list",{stewardSessionId:process.env.SHENNIAN_MANAGED_ROOM_CONTEXT?void 0:process.env.SHENNIAN_STEWARD_SESSION_ID}))),t.command("report").description("Report meaningful progress or a blocker to the personal steward").requiredOption("--worker-id <id>","Worker ID supplied in the delegated task").requiredOption("--message <text>","Progress or blocker, at most 8000 characters").action(async e=>i(await a("report",e))),t.command("read").description("Read a Worker transcript using the reference returned by worker start").requiredOption("--ref <worker-ref>","Opaque Worker reference").option("--limit <n>","Message limit","200").option("--json","Print JSON (default)").action(async e=>i(await a("read",{workerRef:e.ref,limit:Number(e.limit)}))),t.command("send").description("Queue another message for a Worker").requiredOption("--ref <worker-ref>","Opaque Worker reference").option("--message <text>","Message text").option("--message-file <path>","Read the message from a file").option("--json","Print JSON (default)").action(async e=>i(await a("send",{workerRef:e.ref,message:d(e)}))),t.command("wait").description("Wait once for Worker progress or a terminal event").requiredOption("--ref <worker-ref>","Opaque Worker reference").option("--timeout <seconds>","Maximum wait in seconds (1-600)","600").option("--json","Print JSON (default)").action(async e=>{const o=Math.max(1,Math.min(Number(e.timeout)||600,600));i(await a("wait",{workerRef:e.ref,timeoutMs:o*1e3}))}),t.command("stop").description("Stop a Worker using its opaque reference").requiredOption("--ref <worker-ref>","Opaque Worker reference").option("--json","Print JSON (default)").action(async e=>i(await a("stop",{workerRef:e.ref})))}export{a as executeWorkerCommand,k as registerWorkerCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{normalizeInstanceOrigin as t}from"@shennian/wire";function o(r,e){if(!r)return;const n=t(r);if((e.machineToken||e.accessToken||e.refreshToken)&&n!==t(e.serverUrl??"https://shennian.net"))throw new Error("Client is already bound to another server. Stop and reset the installation before rebinding; server overrides cannot reuse credentials or history.");return n}export{o as assertSingleServerBinding};
|
|
@@ -9,9 +9,9 @@ const e=[{id:"rooms",title:"\u7FA4\u804A\u3001\u9080\u8BF7\u4E0E\u8BA8\u8BBA\u65
|
|
|
9
9
|
\u5E26\u8FDB\u7FA4\u524D\u786E\u8BA4\u76EE\u6807\u667A\u80FD\u4F53\u548C\u76EE\u6807\u7FA4\uFF0C\u67E5 help agent room\uFF0C\u7528\u521B\u5EFA\u6216\u672C\u8F6E list/get \u8FD4\u56DE\u7684\u7CBE\u786E\u5F15\u7528\uFF1B\u6709\u6B67\u4E49\u5148\u95EE\u3002\u9A8C\u8BC1\u771F\u5B9E\u52A0\u5165\uFF0C\u4E0D\u628A\u771F\u4EBA CLI \u6D88\u606F\u8BF4\u6210\u72EC\u7ACB\u6210\u5458\u3002
|
|
10
10
|
AI \u5E7F\u573A\u4ECE\u624B\u673A\u529F\u80FD\u5165\u53E3\u6D4F\u89C8\u771F\u5B9E\u4ECB\u7ECD\u548C\u4F7F\u7528\u65B9\u5F0F\uFF0C\u4E0D\u7F16\u9020\u670D\u52A1\u3001\u4EF7\u683C\u6216\u53EF\u7528\u6027\u3002\u5171\u4EAB\u672C\u673A AI \u8BF7\u5148\u8BFB help agent share\uFF0C\u8BF4\u660E\u8BBF\u5BA2\u8BBF\u95EE\u3001\u6A21\u578B\u989D\u5EA6\u3001\u6743\u9650\u4E0E\u5BF9\u8BDD\u53EF\u89C1\u6027\u540E\u6309\u7528\u6237\u6388\u6743\u64CD\u4F5C\uFF1B\u53EA\u6709\u771F\u5B9E active \u548C\u5B8C\u6574 shareUrl \u624D\u8BF4\u53EF\u7528\uFF0C\u4E0D\u628A\u8349\u7A3F\u3001\u4ECB\u7ECD\u9875\u6216\u6392\u961F\u5F53\u53D1\u5E03\u6210\u529F\u3002
|
|
11
11
|
\u7BA1\u5BB6\u8D1F\u8D23\u534F\u8C03\uFF1B\u5177\u4F53\u5B9E\u73B0\u53EF\u7531\u5F53\u524D\u5DF2\u6388\u6743\u7684\u672C\u673A AI \u5B8C\u6210\u3002\u7528\u6237\u660E\u786E\u59D4\u6258\u5176\u4ED6 AI \u65F6\uFF0C\u67E5 help worker\uFF0Cworker start/read/send/wait/stop \u4F7F\u7528\u539F\u6837 workerRef\uFF1B\u4E34\u65F6 Worker \u4E0D\u81EA\u52A8\u6210\u4E3A\u7FA4\u6210\u5458\uFF0C\u7ED3\u679C\u4E0D\u81EA\u52A8\u53D1\u7FA4\u3002\u4E0D\u8981\u56E0\u4E3A\u6709\u8FD9\u4EFD\u6307\u5357\u5C31\u83B7\u5F97\u8C03\u7528\u5176\u4ED6 AI\u3001\u5B89\u88C5\u3001\u5206\u4EAB\u6216\u6269\u5927\u6743\u9650\u7684\u6388\u6743\u3002`},{id:"projects",title:"\u9879\u76EE\u4E0E\u9879\u76EE\u4EFB\u52A1",summary:"\u521B\u5EFA\u9879\u76EE\u7684\u624B\u673A\u8DEF\u5F84\uFF0C\u4EE5\u53CA\u67E5\u8BE2\u548C\u63A8\u8FDB\u65E2\u6709\u9879\u76EE\u4EFB\u52A1\u7684 CLI\u3002",keywords:["\u9879\u76EE","\u4EFB\u52A1","\u5DE5\u4F5C\u76EE\u5F55","project","\u7F16\u7A0B","\u4F5C\u4E1A","\u8D44\u6599"],execution:"mixed",body:`# \u9879\u76EE\u4E0E\u4EFB\u52A1
|
|
12
|
-
\u624B\u673A \u529F\u80FD \u2192 \u9879\u76EE \u53EF\u521B\u5EFA\u9879\u76EE\uFF0C\u5148\u786E\u8BA4\u672C\u4EBA\u7535\u8111\u548C\u5DE5\u4F5C\u76EE\u5F55\u3002\u76EE\u5F55\u5DF2\u6709\u5185\u5BB9\u5148\u770B\u89C4\u5219\uFF0C\u4E0D\u8986\u76D6\u3002\
|
|
12
|
+
\u624B\u673A \u529F\u80FD \u2192 \u9879\u76EE \u53EF\u521B\u5EFA\u9879\u76EE\uFF0C\u5148\u786E\u8BA4\u672C\u4EBA\u7535\u8111\u548C\u5DE5\u4F5C\u76EE\u5F55\u3002\u76EE\u5F55\u5DF2\u6709\u5185\u5BB9\u5148\u770B\u89C4\u5219\uFF0C\u4E0D\u8986\u76D6\u3002\u53EF\u7528 project create --machine <\u7CBE\u786EID> --workdir <\u7EDD\u5BF9\u76EE\u5F55> [--name <\u540D\u79F0>] \u6CE8\u518C\u9879\u76EE\uFF0C\u4E0D\u521B\u5EFA\u6216\u642C\u8FC1\u6587\u4EF6\uFF1Bproject update <project-id> --name <\u540D\u79F0> \u6216 --agent <AI> [--model <\u6A21\u578B>] \u4FEE\u6539\u663E\u793A\u540D\u4E0E\u9ED8\u8BA4\u6267\u884C\u5668\uFF0C\u4E0D\u79FB\u52A8\u9879\u76EE\u76EE\u5F55\u3002\u53EA\u7BA1\u7406\u672C\u4EBA\u7535\u8111\uFF0C\u5DF2\u6709\u76EE\u5F55\u5148\u6838\u5BF9\u89C4\u5219\u4E0E\u6743\u9650\u3002
|
|
13
13
|
\u5148\u67E5 shennian help project\uFF0C\u518D project list \u8BFB\u53D6\u771F\u5B9E\u9879\u76EE\uFF0C\u6309\u7CBE\u786E project-id \u7528 project task list\u3002\u65B0\u5EFA\u4EFB\u52A1\u67E5 help project task create\uFF0C\u7ED9\u51FA\u660E\u786E instruction\u3001\u53EF\u7528 AI/\u6A21\u578B\u53CA\u7A33\u5B9A request-id\uFF1B\u91CD\u8BD5\u590D\u7528\u540C\u4E00\u4E2A request-id\u3002\u8DDF\u8FDB\u7528 task read/send\uFF0C\u590D\u7528\u7CBE\u786E task-id\uFF1B\u505C\u6B62\u9700\u7528\u6237\u660E\u786E\u8981\u6C42\uFF0C\u4E14\u4E0D\u662F\u53EF\u6062\u590D\u7684\u6301\u4E45\u6682\u505C\u3002
|
|
14
|
-
\u7B80\u5355\u3001\u5DF2\u6388\u6743\u4E14\u672C\u8F6E\u80FD\u5B89\u5168\u505A\u5B8C\u7684\u5DE5\u4F5C\u53EF\u4EE5\u76F4\u63A5\u5B8C\u6210\uFF0C\u4E0D\u673A\u68B0\u65B0\u5EFA\u9879\u76EE\u4EFB\u52A1\u3002\u590D\u6742\u6216\u957F\u671F\u5DE5\u4F5C\u5148\u786E\
|
|
14
|
+
\u7B80\u5355\u3001\u5DF2\u6388\u6743\u4E14\u672C\u8F6E\u80FD\u5B89\u5168\u505A\u5B8C\u7684\u5DE5\u4F5C\u53EF\u4EE5\u76F4\u63A5\u5B8C\u6210\uFF0C\u4E0D\u673A\u68B0\u65B0\u5EFA\u9879\u76EE\u4EFB\u52A1\u3002\u590D\u6742\u6216\u957F\u671F\u5DE5\u4F5C\u5148\u786E\u5B9A\u9879\u76EE\u76EE\u5F55\u548C\u9A8C\u6536\u8981\u6C42\uFF1B\u7528\u6237\u660E\u786E\u59D4\u6258\u540E\u53EF\u7528 worker start \u5B89\u6392\u672C\u673A\u540E\u53F0\u6267\u884C\uFF0C\u4E0D\u91CD\u590D\u786E\u8BA4\u5E38\u89C4\u62C6\u89E3\u3002\u4E3B Agent \u9ED8\u8BA4 Worker \u5728\u7CFB\u7EDF\u8BBE\u7F6E\u914D\u7F6E\uFF0C\u6587\u5B57\u6216\u8BED\u97F3\u6307\u5B9A\u7528 --agent/--model \u53EA\u8986\u76D6\u672C\u6B21\u3002\u7BA1\u5BB6\u4E0A\u4E0B\u6587\u542F\u52A8\u9700\u7A33\u5B9A --request-id\uFF1B\u5B8C\u6210\u3001\u5931\u8D25\u548C\u91CD\u8981\u8FDB\u5C55\u4E8B\u4EF6\u56DE\u4F20\u7BA1\u5BB6\uFF0C\u7531 steward say \u6C47\u62A5\uFF0C\u4E0D\u5FAA\u73AF wait \u6216\u521B\u5EFA\u5341\u5206\u949F\u8F6E\u8BE2\u3002worker list \u4EC5\u5728\u4E2A\u4EBA\u7BA1\u5BB6\u4E0A\u4E0B\u6587\u6062\u590D\u672C\u4EBA\u4EFB\u52A1\u5F15\u7528\uFF1B\u666E\u901A\u5916\u90E8 Worker \u4ECD\u987B\u539F\u6837 workerRef\uFF0C\u4E0D\u81EA\u52A8\u56DE\u4F20\u3002\u4E2A\u4EBA\u7BA1\u5BB6\u521B\u5EFA\u9879\u76EE\u4EFB\u52A1\u65F6\u4F1A\u6CE8\u518C\u8BE5\u4EFB\u52A1\u7684\u7EC8\u6001/\u9700\u5904\u7406\u72B6\u6001\u56DE\u4F20\uFF0C\u652F\u6301\u672C\u4EBA\u5176\u4ED6\u7535\u8111\uFF1B\u4EE5\u8FD4\u56DE\u7684 tracking.enabled \u4E3A\u51C6\u3002\u6CE8\u518C\u5931\u8D25\u7528 project task watch <\u540C\u4E00task-id> \u8865\u63A5\uFF0C\u4E0D\u91CD\u65B0\u521B\u5EFA\uFF1B\u79BB\u7EBF\u4FDD\u7559\u56DE\u4F20\uFF0C\u65E7 Client \u660E\u786E\u5347\u7EA7\uFF0C\u4E0D\u9759\u9ED8\u6362\u7535\u8111\u3002\u4E0D\u56E0\u5EFA\u4E86\u9879\u76EE\u6216\u542F\u52A8\u4EFB\u52A1\u5C31\u5BA3\u79F0\u6700\u7EC8\u5DE5\u4F5C\u5B8C\u6210\u3002
|
|
15
15
|
\u8BFB\u8D44\u6599\u3001\u4F5C\u4E1A\u8F85\u5BFC\u3001\u4F1A\u8BAE\u7EAA\u8981\u4E0E\u5468\u56DE\u987E\u53EA\u4F7F\u7528\u7528\u6237\u63D0\u4F9B\u6216\u6388\u6743\u53EF\u8BFB\u7684\u5185\u5BB9\uFF1B\u628A\u4E8B\u5B9E\u548C\u63A8\u65AD\u5206\u5F00\uFF0C\u4E0D\u865A\u6784\u8BB0\u5F55\u3002\u6CA1\u6709\u6587\u4EF6/\u5386\u53F2\u8BBF\u95EE\u6743\u9650\u5C31\u95EE\u7528\u6237\uFF0C\u4E0D\u4EE5\u904D\u5386\u6574\u4E2A\u7535\u8111\u4EE3\u66FF\u660E\u786E\u6388\u6743\u3002`},{id:"contacts",title:"\u8054\u7CFB\u4EBA",summary:"\u533A\u5206\u5DF2\u4FDD\u5B58\u8054\u7CFB\u4EBA\u3001\u672C\u4EBA\u667A\u80FD\u4F53\u4E0E\u5DF2\u6709\u7FA4\u804A\uFF0C\u786E\u8BA4\u5BF9\u8C61\u540E\u4EA4\u6D41\u3002",keywords:["\u8054\u7CFB\u4EBA","\u670B\u53CB","\u901A\u8BAF\u5F55","contact"],execution:"mixed",body:`# \u8054\u7CFB\u4EBA
|
|
16
16
|
\u5148\u67E5 shennian help contact\uFF1B\u652F\u6301\u65F6\u7528 contact list\uFF08\u6309 help \u53EF\u7528 query \u8FC7\u6EE4\uFF09\u3002\u8054\u7CFB\u4EBA\u5217\u8868\u4E0D\u662F room list \u7684\u4E00\u5BF9\u4E00\u5386\u53F2\u63A8\u65AD\uFF0C\u4E0D\u80FD\u628A\u6709\u8FC7\u804A\u5929\u8BF4\u6210\u5DF2\u4FDD\u5B58\u8054\u7CFB\u4EBA\u3002
|
|
17
17
|
\u624B\u673A \u529F\u80FD \u2192 \u8054\u7CFB\u4EBA \u53EF\u6D4F\u89C8\u4E0E\u9009\u62E9\u5BF9\u8C61\u3002\u4EC5\u5217\u7528\u6237\u6388\u6743\u8303\u56F4\uFF0C\u4E0D\u626B\u63CF\u7CFB\u7EDF\u901A\u8BAF\u5F55\u3002\u9700\u8981\u53D1\u6D88\u606F\u65F6\u786E\u8BA4\u5B9E\u9645\u5BF9\u8BDD/\u7FA4\u76EE\u6807\u4E0E\u6B63\u6587\uFF0C\u518D\u6309 help room \u6216\u624B\u673A\u5165\u53E3\u64CD\u4F5C\uFF0C\u4E0D\u7F16\u9020\u4E0D\u5B58\u5728\u7684 contact send \u547D\u4EE4\u3002
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{registerConfigCommand as
|
|
2
|
-
`),process.exitCode=1}function lo(i){const u=[`trigger=${i.trigger??"unknown"}`,`phase=${i.phase}`,`code=${i.code??"unknown"}`,`attempt=${i.reconnectAttempt}`];return i.reason&&u.push(`reason=${i.reason}`),i.error&&u.push(`error=${i.error}`),u.join(" ")}const a=new Pe;a.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(T),a.enablePositionalOptions(),ct(a),dt(a),lt(a),mt(a),ke(a),Ce(a),a.option("--api <url>","Server URL override").option("--name <name>","Machine name",Te.hostname()).action(async i=>{const u=m(),I=i.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??u.serverUrl??void 0;await $e(I,i.name)}),a.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async i=>{const u=K("env.json");try{const e=JSON.parse(z.readFileSync(u,"utf-8"));for(const[o,t]of Object.entries(e))process.env[o]||(process.env[o]=t);me()}catch{}Oe(Ne(process.env,m()));const I=Tt();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(I),process.exit(0));const l=Ve(),C=Rt({lockPath:io,instanceId:l});C.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(C.ownerPid?` (PID ${C.ownerPid})`:"")+", skipping duplicate start"),clearInterval(I),process.exit(0));try{const e=so();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),C.release(),process.exit(0)}catch{}}catch{}const Q=Be(process.env,ve)?[]:je(process.pid);Q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${Q[0]}), skipping duplicate start`),C.release(),process.exit(0)),se(process.pid,l,{version:T}),ce(process.pid,void 0,l),process.on("exit",()=>{ne(process.pid,l),re(l)}),process.on("exit",C.release);let V=null;try{V=St()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const M=await ht();if(M){V||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{yt({fromVersion:T,targetVersion:M.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:M.jobId},{readiness:V}),console.error(`[upgrade] New version crashed ${M.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{oo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const d=m(),p=i.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??d.serverUrl??gt.cn.url,f=de(),c=await le({serverUrl:p,authToken:d.machineToken??d.accessToken}),S=c.map(e=>e.type);let j=L(c);const h=new xt,U=new Vt,k=new Yt(h),y=new $t(p),Ie=new Jt({actionClient:y}),Se=new Qt({actionClient:y});let w=null,r;const Y=Zt({authority:k,actionClient:y,attachmentCache:Ie,attachmentUploader:Se,observations:U,getMachineCredentials:()=>({machineToken:m().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,o)=>r.getManagedRoomActiveRunId(e,o),scheduleExecutor:(e,o,t)=>be({serverUrl:p,machineToken:m().machineToken,sessionId:e,action:o,args:t}),onPaused:({registryKey:e,sessionId:o})=>{const n=h.getByRegistryKey(e)?.binding;n&&h.setBinding({registryKey:e,bindingId:n.bindingId,roomId:n.roomId,agentIdentityId:n.agentIdentityId,agentMembershipId:n.agentMembershipId,status:"paused",activationMode:"paused"}),k.revokeSession(e),r?.discardPendingManagedRoomMessages(o)}});let H=null,F=null,B=null,W=null,Z=!1;const b=await pe({paths:Dt(),handlers:{...Ut({daemonVersion:f,protocolVersion:Ot,getStatus:()=>ie(),getAgents:()=>c,requestShutdown:()=>{W?W():Z=!0},completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),bindManagedRoom:e=>r.bindManagedRoomFromLocal(e),stewardSay:e=>r.stewardSayFromLocal(e),startUpgrade:async e=>{if(!H)throw new Error("upgrade_unavailable");return H(e)},prepareExternalUpgrade:e=>{if(!F)throw new Error("external_upgrade_unavailable");return F(e)},cancelExternalUpgrade:()=>{if(!B)throw new Error("external_upgrade_unavailable");return B()}}),..._t({getStatus:()=>ie(),getSnapshot:()=>L(c).map(e=>{const o=c.find(t=>t.type===e.type);return{...e,runtimePlatform:o?.runtimePlatform,runtimeIsWsl:o?.runtimeIsWsl}}),getUnavailable:it,discover:async e=>{if(!r)throw new Error("daemon_starting");const o=await ue(!0,e);if(e){const n=c.filter(g=>g.type!==e);c.splice(0,c.length,...n,...o.detectedAgents)}else c.splice(0,c.length,...o.detectedAgents);const t=L(c);return s.sendEvent({type:"event",event:"machine.agents",payload:{agentList:c.map(n=>n.type),agents:t}}),o.agents.map(n=>{const g=o.detectedAgents.find(q=>q.type===n.type);return{...n,runtimePlatform:g?.runtimePlatform,runtimeIsWsl:g?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return Nt(e,{ignoreNativeSession:(o,t)=>P?.ignoreNativeSource(o,t)})}})}});let A;try{A=await pe({paths:Mt(),handlers:{"managed-room.execute":e=>Y(e),...Re(p,h)}})}catch(e){throw await b.close(),e}let X=!1;const _=()=>{X||(X=!0,Promise.allSettled([b.close(),A.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",_),process.once("SIGTERM",_),!d.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),A.close()]),process.exitCode=1;return}process.removeListener("SIGINT",_),process.removeListener("SIGTERM",_);const ee=`${co(p)}/relay/machine`,N=d.machineToken;if(!N)return;try{await to({serverUrl:p,machineToken:N,machineId:d.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),A.close()]),process.exit(1)}d.machineId&&bt(p,d.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${ee}... (v${f}) agents: ${S.join(",")}`),D({level:"info",wsEvent:"daemon.start",metadata:{version:f,agents:S}});let P=null,E=null,R=0,G=!1,v=vt();const te=Ue(d.autoUpgrade),O=d.machineId?new Ct({serverUrl:p,machineId:d.machineId,getMachineToken:()=>m().machineToken,getDetectedAgents:()=>le({serverUrl:p,authToken:m().machineToken}),onRuntimeChanged:async()=>{const e=R,o=await ue(!0);if(c.splice(0,c.length,...o.detectedAgents),S.splice(0,S.length,...o.agentList),j=o.agents,!(await s.sendReq({type:"req",id:De(),method:"machine.agents.report",params:{agents:o.agents}},2e4)).ok)throw new Error("machine_capability_report_rejected");e===R&&s.getState()==="connected"&&O?.start()},onError:()=>console.warn("[public-agent] delivery attempt failed")}):null,s=new _e({serverUrl:ee,machineToken:N,cliVersion:f,agentList:S,autoUpgradePolicy:te,sessionSyncProtocolVersion:ge,sessionSyncCapabilities:fe,daemonInstallationId:Xt(),dataEpoch:he,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),D({level:"info",wsEvent:"daemon.connected"}),s.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:j}}),kt(c,{serverUrl:d.serverUrl??p,authToken:d.machineToken??d.accessToken}).then(t=>{JSON.stringify(t)!==JSON.stringify(j)&&s.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:t}})}).catch(()=>{}),v&&s.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=++R,o=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const t=v;v=null,t.jobId&&wt(t.jobId,t.to),ft(),console.log(`[${new Date().toISOString()}] [upgrade] ${t.operation==="rollback"?"Rolled back":"Healthy"}: ${t.from} \u2192 ${t.to}`),At({kind:"upgrade",result:t.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),s.sendEvent({type:"event",event:"upgrade",payload:{state:t.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:t.from,to:t.to,...t.jobId?{jobId:t.jobId}:{}}}),t.operation!=="rollback"&&s.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:t.from,to:t.to}})}G||(G=!0,rt({clientVersion:f}).then(t=>{const n=t.filter(g=>!g.ok||!["current","registered","updated"].includes(g.result.status));n.length>0&&console.warn(`[skill] ${n.length} installed Skill copies need attention; run shennian setup for details`)}).catch(()=>console.warn("[skill] Official Skill refresh failed; Client remains available")).finally(()=>{G=!1})),e===R&&s.getState()==="connected"&&(P?.handleConnected(),O?.start())}catch(t){if(console.error("[session.deletions] startup catch-up failed",t),e===R&&s.getState()==="connected"){if(t instanceof ro)return;const n=t instanceof no?Math.max(1e3,t.retryAfterMs):5e3;setTimeout(()=>{o()},n).unref?.()}}};o()},onDisconnected:e=>{R+=1,r?.revokeProjectFsHandles(),O?.stop(),E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${lo(e)}`),D({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}`),D({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Ht(s,E,e);return}r.handleReq(e)}});P=Pt()?new Et(s):null,E=new jt({registry:h,observations:U,actionClient:y,getMachineCredentials:()=>({machineToken:m().machineToken}),isSessionDeleted:e=>new Ft().hasSession(e),onMessages:async(e,o,t,n)=>{await w?.processMessages(e,o,t,n)},onBindingPaused:e=>{e.lifecycleState==="closed"?(k.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new It({currentVersion:f,machineId:d.machineId??"unknown-machine",serverUrl:d.serverUrl??p,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:ge,dataEpoch:he,advertisedCapabilities:fe,updaterProtocolVersion:1,startUpgrade:async(e,o)=>{const{handleUpgradeStart:t}=await import("./commands/upgrade.js");await t(s,"auto-upgrade",e,{currentVersion:f,prepareForUpgrade:n=>r.prepareForUpgrade(n),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:o?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(te),r=new pt(s,P,f,x,new Lt({registry:h,observations:U,bindingClient:y,getMachineCredentials:()=>{const e=m();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}),{prepare:()=>He(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{Fe(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>k.revokeToken(e),Y,null,ut(N),()=>L(c)),H=async e=>{const o=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=typeof o.targetVersion=="string"?o.targetVersion:void 0,n=o.confirmedMajor===!0;return await Xe(s,`local-upgrade-${Date.now()}`,t,{currentVersion:f,confirmedMajor:n,prepareForUpgrade:q=>r.prepareForUpgrade(q),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},F=e=>{const o=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n=`desktop-${(typeof o.targetVersion=="string"?o.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:n,...r.prepareForUpgrade(n)}},B=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),w=new Gt({registry:h,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const o=m().machineToken?.trim(),t=e.binding;if(!o||!t)throw new Error("managed_room_context_unavailable");const n=await y.status({machineToken:o,roomId:t.roomId,bindingId:t.bindingId});if(!Wt(h,e)||!Bt(e,n))throw new Error("managed_room_binding_unavailable");return{roomName:n.room.name,memberCount:n.room.memberCount,members:n.room.members,agentsByIdentityId:n.agent?{[n.agentIdentityId]:n.agent}:{},effectiveModelsBySessionId:n.effectiveModelId!==void 0?{[e.nianSessionId]:n.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:n.agent?{[n.agentIdentityId]:n.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,o,t,n)=>{const g=m().machineToken;if(!g)throw new Error("machine_not_authorized");return k.issue({registryKey:e,machineCredential:g,activationCauseMessageId:o,allowedActions:t,propagateWorkerRoomOrigin:n})},audit:new Kt,prepareUnboundDirectTurn:zt,resolveManagedAgentPolicy:e=>qt({...e,runtime:c.find(o=>o.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>Ae({serverUrl:p,machineToken:m().machineToken,req:e,client:s,queue:r.getScheduleQueue(),registry:h}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(h.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const t=await y.status({machineToken:m().machineToken,roomId:e.roomId,bindingId:e.bindingId});return t.status==="active"&&t.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>w.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,o)=>w.prepareDirectConversationTurn(e,o)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?w.prepareDirectConversationTurn(e.sessionId,eo):e.source==="manager_event"?w.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:w.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),z.mkdirSync(ve,{recursive:!0}),se(process.pid,l,{version:f}),ce(process.pid,void 0,l),s.connect(),process.stdin.resume();let oe=!1;const $=async()=>{if(!oe){oe=!0,console.log(Ee.gray(`
|
|
3
|
-
Disconnecting...`)),D({level:"info",wsEvent:"daemon.stop"}),clearInterval(
|
|
1
|
+
import{registerConfigCommand as we}from"./commands/config.js";import{registerLocalAppsCommand as Ce}from"./commands/local-apps.js";import{registerReminderCommand as Re}from"./commands/reminder.js";import{personalScheduleHandlers as ke}from"./schedules/personal-handlers.js";import{registerScheduleCommand as be}from"./commands/schedule.js";import{executeServerSchedule as Ae,deliverServerSchedule as Ee}from"./schedules/server-client.js";import{Command as Pe}from"commander";import Te from"chalk";import De from"node:os";import{randomUUID as Me}from"node:crypto";import z from"node:fs";import{loadConfig as p,getShennianDir as Ue,resolveShennianPath as J,normalizeAutoUpgradePolicy as _e}from"./config/index.js";import{CliRelayClient as Ne}from"./relay/client.js";import{collectKnownLogSecrets as Oe,installCliLogRedaction as xe}from"./log-redaction.js";import{registerPairCommand as $e,runSmartStart as Le}from"./commands/pair.js";import{registerLogoutCommand as Ve}from"./commands/logout.js";import{clearDaemonPidIfOwner as re,createDaemonInstanceId as je,clearDaemonLauncher as ae,findRunningDaemonProcessIds as He,isRemoteAccessDisabled as ie,prepareRemoteAccessPause as Fe,stopPreparedRemoteAccessService as Be,isIsolatedDaemonE2eInstance as We,getDaemonStatus as se,registerDaemonCommand as Ge,writeDaemonPid as ce,writeDaemonLauncher as de}from"./commands/daemon.js";import{registerRuntimeCommand as qe}from"./commands/runtime.js";import{registerAdapterCommand as Ke}from"./commands/adapter.js";import{registerAgentCommand as ze}from"./commands/agent.js";import{registerManagerCommand as Je}from"./commands/manager.js";import{registerWorkerCommand as Qe}from"./commands/worker.js";import{registerProjectCommand as Ye}from"./commands/project.js";import{registerStewardCommand as Ze}from"./commands/steward.js";import{registerRecordingCommand as Xe}from"./commands/recordings.js";import{handleUpgradeStart as eo,registerUpgradeCommand as oo}from"./commands/upgrade.js";import{registerToolsCommand as to}from"./commands/tools.js";import{registerQueueCommand as no}from"./commands/queue.js";import{registerSkillCommand as ro}from"./commands/skill.js";import{refreshExistingSkillForHosts as ao}from"./skill/hosts.js";import{registerSetupCommand as io}from"./commands/setup.js";import{getUnavailableAcpAgents as so}from"./agents/acp/discovery.js";import{registerCommandReference as co}from"./commands/command-reference.js";import{registerIntegrationCommand as mo}from"./commands/integration.js";import{registerRoomCommand as lo}from"./commands/room.js";import{registerContactCommand as po}from"./commands/contact.js";import{registerSupportCommand as go}from"./commands/support.js";import{SessionManager as uo}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as fo}from"./session/handlers/project-fs.js";import{SERVERS as ho}from"./region.js";import{clearUpgradeAttempt as vo,getCurrentVersion as me,handleStartupCrashCheck as Io,readUpgradeAttempt as So}from"./upgrade/engine.js";import{AutoUpgradeScheduler as yo}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as wo}from"./upgrade/updater-bootstrap.js";import{launchUpdater as Co,markUpdaterHealthy as Ro}from"./upgrade/updater-launcher.js";import{detectAgents as le}from"./agents/detect.js";import{PublicAgentDeliveryWorker as ko}from"./agents/public-delivery-worker.js";import{augmentProcessPath as pe}from"./env-path.js";import{acquireDaemonInstanceLock as bo}from"./commands/daemon-instance-lock.js";pe();const T=me();import{getCachedAgentInfos as L,resolveAgentInfos as Ao}from"./agents/model-registry.js";import{initCliLogReporter as Eo,reportLog as D,reportUpgradeAggregateMetric as Po}from"./log-reporter.js";import{isNativeFusionEnabled as To}from"./native-fusion/config.js";import{NativeSessionFusionService as Do}from"./native-fusion/service.js";import{startDaemonLogRetention as Mo}from"./daemon-log.js";import{startDaemonIpcServer as ge}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as Uo,resolveManagedRoomIpcPaths as _o}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as No}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as Oo}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as ue}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as xo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as $o}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as Lo}from"./room/session-registry.js";import{HttpRoomBindingClient as Vo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as jo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ho}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Fo}from"./room/mirror-sync-service.js";import{handleLiveRoomDelivery as Bo}from"./room/live-delivery.js";import{NativeSourceDeletionStore as Wo}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Go,isCurrentRegistration as qo}from"./room/registration-authority.js";import{RoomActivationService as Ko,resolveManagedAgentPolicy as zo}from"./room/activation-service.js";import{prepareStandaloneAgentTurn as Jo}from"./room/standalone-agent-turn.js";import{RoomActivationAudit as Qo}from"./room/activation-audit.js";import{RoomAttachmentCache as Yo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Zo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Xo}from"./room/managed-context.js";import{createManagedRoomExecutor as et}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as fe,SESSION_SYNC_ADVERTISED_CAPABILITIES as he}from"@shennian/wire";import{getDaemonInstallationId as ot}from"./session/store.js";import{OWNER_DIRECT_TURN_OPTIONS as tt}from"./session/queue-helpers.js";import{synchronizeMachinePersonalAuthority as nt}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ve,ensurePersonalDataEpoch as rt}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as at,DeletionSyncTerminalError as it}from"./session/deletion-sync-state.js";const Ie=Ue(),st=J("daemon.pid"),ct=J("daemon.instance.lock");function dt(){try{const i=z.readFileSync(st,"utf-8").trim();if(i.startsWith("{")){const f=JSON.parse(i),m=Number(f.pid);return Number.isInteger(m)&&m>0?m:null}const l=parseInt(i,10);return Number.isInteger(l)&&l>0?l:null}catch{return null}}function mt(i){return i.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function Q(){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 lt(i){const l=[`trigger=${i.trigger??"unknown"}`,`phase=${i.phase}`,`code=${i.code??"unknown"}`,`attempt=${i.reconnectAttempt}`];return i.reason&&l.push(`reason=${i.reason}`),i.error&&l.push(`error=${i.error}`),l.join(" ")}import{assertSingleServerBinding as V}from"./config/server-binding.js";const a=new Pe;a.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(T),a.enablePositionalOptions(),a.hook("preAction",(i,l)=>{const f=l.optsWithGlobals(),m=p();for(const I of[f.api,f.server,process.env.SHENNIAN_DESKTOP_SERVER_URL])V(I,m)}),mo(a),lo(a),po(a),go(a),be(a),Re(a),a.option("--api <url>","Server URL override").option("--name <name>","Machine name",De.hostname()).action(async i=>{const l=p(),f=V(i.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??void 0,l);await Le(f,i.name)}),a.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async i=>{const l=J("env.json");try{const e=JSON.parse(z.readFileSync(l,"utf-8"));for(const[t,o]of Object.entries(e))process.env[t]||(process.env[t]=o);pe()}catch{}xe(Oe(process.env,p()));const f=Mo();ie()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(f),process.exit(0));const m=je(),I=bo({lockPath:ct,instanceId:m});I.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(I.ownerPid?` (PID ${I.ownerPid})`:"")+", skipping duplicate start"),clearInterval(f),process.exit(0));try{const e=dt();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),I.release(),process.exit(0)}catch{}}catch{}const Y=We(process.env,Ie)?[]:He(process.pid);Y.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${Y[0]}), skipping duplicate start`),I.release(),process.exit(0)),ce(process.pid,m,{version:T}),de(process.pid,void 0,m),process.on("exit",()=>{re(process.pid,m),ae(m)}),process.on("exit",I.release);let j=null;try{j=wo()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const M=await Io();if(M){j||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Co({fromVersion:T,targetVersion:M.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:M.jobId},{readiness:j}),console.error(`[upgrade] New version crashed ${M.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{rt()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const s=p(),g=V(i.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??ho.cn.url,s),h=me(),d=await le({serverUrl:g,authToken:s.machineToken??s.accessToken}),y=d.map(e=>e.type);let H=L(d);const v=new Lo,U=new Ho,k=new Xo(v);V(g,s);const w=new Vo(g),Se=new Yo({actionClient:w}),ye=new Zo({actionClient:w});let C=null,r;const Z=et({authority:k,actionClient:w,attachmentCache:Se,attachmentUploader:ye,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,t)=>r.getManagedRoomActiveRunId(e,t),scheduleExecutor:(e,t,o)=>Ae({serverUrl:g,machineToken:p().machineToken,sessionId:e,action:t,args:o}),onPaused:({registryKey:e,sessionId:t})=>{const n=v.getByRegistryKey(e)?.binding;n&&v.setBinding({registryKey:e,bindingId:n.bindingId,roomId:n.roomId,agentIdentityId:n.agentIdentityId,agentMembershipId:n.agentMembershipId,status:"paused",activationMode:"paused"}),k.revokeSession(e),r?.discardPendingManagedRoomMessages(t)}});let F=null,B=null,W=null,G=null,X=!1;const b=await ge({paths:Uo(),handlers:{...No({daemonVersion:h,protocolVersion:$o,getStatus:()=>se(),getAgents:()=>d,requestShutdown:()=>{G?G():X=!0},completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),bindManagedRoom:e=>r.bindManagedRoomFromLocal(e),stewardSay:e=>r.stewardSayFromLocal(e),startUpgrade:async e=>{if(!F)throw new Error("upgrade_unavailable");return F(e)},prepareExternalUpgrade:e=>{if(!B)throw new Error("external_upgrade_unavailable");return B(e)},cancelExternalUpgrade:()=>{if(!W)throw new Error("external_upgrade_unavailable");return W()}}),...Oo({getStatus:()=>se(),getSnapshot:()=>L(d).map(e=>{const t=d.find(o=>o.type===e.type);return{...e,runtimePlatform:t?.runtimePlatform,runtimeIsWsl:t?.runtimeIsWsl}}),getUnavailable:so,discover:async e=>{if(!r)throw new Error("daemon_starting");const t=await ue(!0,e);if(e){const n=d.filter(u=>u.type!==e);d.splice(0,d.length,...n,...t.detectedAgents)}else d.splice(0,d.length,...t.detectedAgents);const o=L(d);return c.sendEvent({type:"event",event:"machine.agents",payload:{agentList:d.map(n=>n.type),agents:o}}),t.agents.map(n=>{const u=t.detectedAgents.find(K=>K.type===n.type);return{...n,runtimePlatform:u?.runtimePlatform,runtimeIsWsl:u?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return xo(e,{ignoreNativeSession:(t,o)=>E?.ignoreNativeSource(t,o)})}})}});let A;try{A=await ge({paths:_o(),handlers:{"managed-room.execute":e=>Z(e),...ke(g,v)}})}catch(e){throw await b.close(),e}let ee=!1;const _=()=>{ee||(ee=!0,Promise.allSettled([b.close(),A.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",_),process.once("SIGTERM",_),!s.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),A.close()]),process.exitCode=1;return}process.removeListener("SIGINT",_),process.removeListener("SIGTERM",_);const oe=`${mt(g)}/relay/machine`,N=s.machineToken;if(!N)return;try{await nt({serverUrl:g,machineToken:N,machineId:s.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),A.close()]),process.exit(1)}s.machineId&&Eo(g,s.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${oe}... (v${h}) agents: ${y.join(",")}`),D({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:y}});let E=null,P=null,R=0,q=!1,S=So();const te=_e(s.autoUpgrade),O=s.machineId?new ko({serverUrl:g,machineId:s.machineId,getMachineToken:()=>p().machineToken,getDetectedAgents:()=>le({serverUrl:g,authToken:p().machineToken}),onRuntimeChanged:async()=>{const e=R,t=await ue(!0);if(d.splice(0,d.length,...t.detectedAgents),y.splice(0,y.length,...t.agentList),H=t.agents,!(await c.sendReq({type:"req",id:Me(),method:"machine.agents.report",params:{agents:t.agents}},2e4)).ok)throw new Error("machine_capability_report_rejected");e===R&&c.getState()==="connected"&&O?.start()},onError:()=>console.warn("[public-agent] delivery attempt failed")}):null,c=new Ne({serverUrl:oe,machineToken:N,cliVersion:h,agentList:y,autoUpgradePolicy:te,sessionSyncProtocolVersion:fe,sessionSyncCapabilities:he,daemonInstallationId:ot(),dataEpoch:ve,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),D({level:"info",wsEvent:"daemon.connected"}),c.sendEvent({type:"event",event:"machine.agents",payload:{agentList:y,agents:H}}),Ao(d,{serverUrl:s.serverUrl??g,authToken:s.machineToken??s.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(H)&&c.sendEvent({type:"event",event:"machine.agents",payload:{agentList:y,agents:o}})}).catch(()=>{}),S&&c.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:S.from,to:S.to,...S.jobId?{jobId:S.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),P?.start();const e=++R,t=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),S){const o=S;S=null,o.jobId&&Ro(o.jobId,o.to),vo(),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"}),c.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"&&c.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}q||(q=!0,ao({clientVersion:h}).then(o=>{const n=o.filter(u=>!u.ok||!["current","registered","updated"].includes(u.result.status));n.length>0&&console.warn(`[skill] ${n.length} installed Skill copies need attention; run shennian setup for details`)}).catch(()=>console.warn("[skill] Official Skill refresh failed; Client remains available")).finally(()=>{q=!1})),e===R&&c.getState()==="connected"&&(E?.handleConnected(),O?.start())}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===R&&c.getState()==="connected"){if(o instanceof it)return;const n=o instanceof at?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{t()},n).unref?.()}}};t()},onDisconnected:e=>{R+=1,r?.revokeProjectFsHandles(),O?.stop(),P?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${lt(e)}`),D({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}`),D({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Bo(c,P,e);return}r.handleReq(e)}});E=To()?new Do(c):null,P=new Fo({registry:v,observations:U,actionClient:w,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new Wo().hasSession(e),onMessages:async(e,t,o,n)=>{await C?.processMessages(e,t,o,n)},onBindingPaused:e=>{e.lifecycleState==="closed"?(k.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new yo({currentVersion:h,machineId:s.machineId??"unknown-machine",serverUrl:s.serverUrl??g,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:fe,dataEpoch:ve,advertisedCapabilities:he,updaterProtocolVersion:1,startUpgrade:async(e,t)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(c,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:n=>r.prepareForUpgrade(n),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:t?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(te),r=new uo(c,E,h,x,new jo({registry:v,observations:U,bindingClient:w,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}),{prepare:()=>Fe(),isDisabled:()=>ie(),shutdownAfterResponse:()=>{setTimeout(()=>{Be(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>k.revokeToken(e),Z,null,fo(N),()=>L(d)),F=async e=>{const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof t.targetVersion=="string"?t.targetVersion:void 0,n=t.confirmedMajor===!0;return await eo(c,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:n,prepareForUpgrade:K=>r.prepareForUpgrade(K),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},B=e=>{const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n=`desktop-${(typeof t.targetVersion=="string"?t.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:n,...r.prepareForUpgrade(n)}},W=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),C=new Ko({registry:v,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const t=p().machineToken?.trim(),o=e.binding;if(!t||!o)throw new Error("managed_room_context_unavailable");const n=await w.status({machineToken:t,roomId:o.roomId,bindingId:o.bindingId});if(!qo(v,e)||!Go(e,n))throw new Error("managed_room_binding_unavailable");return{roomName:n.room.name,memberCount:n.room.memberCount,members:n.room.members,agentsByIdentityId:n.agent?{[n.agentIdentityId]:n.agent}:{},effectiveModelsBySessionId:n.effectiveModelId!==void 0?{[e.nianSessionId]:n.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:n.agent?{[n.agentIdentityId]:n.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,t,o,n)=>{const u=p().machineToken;if(!u)throw new Error("machine_not_authorized");return k.issue({registryKey:e,machineCredential:u,activationCauseMessageId:t,allowedActions:o,propagateWorkerRoomOrigin:n})},audit:new Qo,prepareUnboundDirectTurn:Jo,resolveManagedAgentPolicy:e=>zo({...e,runtime:d.find(t=>t.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>Ee({serverUrl:g,machineToken:p().machineToken,req:e,client:c,queue:r.getScheduleQueue(),registry:v}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(v.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await w.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>C.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,t)=>C.prepareDirectConversationTurn(e,t)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?C.prepareDirectConversationTurn(e.sessionId,tt):e.source==="manager_event"?C.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:C.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),z.mkdirSync(Ie,{recursive:!0}),ce(process.pid,m,{version:h}),de(process.pid,void 0,m),c.connect(),process.stdin.resume();let ne=!1;const $=async()=>{if(!ne){ne=!0,console.log(Te.gray(`
|
|
3
|
+
Disconnecting...`)),D({level:"info",wsEvent:"daemon.stop"}),clearInterval(f),O?.stop();try{await r.cleanup(),x.stop(),E?.stop(),P?.stop(),c.disconnect(),await Promise.allSettled([b.close(),A.close()]),re(process.pid,m),ae(m)}finally{process.exit(0)}}};G=$,X&&$(),process.on("SIGINT",()=>{$()}),process.on("SIGTERM",()=>{$()})}),a.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>Q()),a.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(()=>Q()),a.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>Q()),we(a),io(a),$e(a),Ve(a),Ge(a),Ce(a),qe(a),Ke(a,{clientVersion:T}),ze(a),Je(a),Qe(a),Ye(a),Ze(a),Xe(a),oo(a),to(a),no(a),ro(a),co(a,T),a.parse();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{getCanonicalSessionStore as l}from"../session/store.js";import{requestSessionIndexPublish as w}from"../session/index-publisher.js";import{commitStewardSay as k}from"../session/handlers/steward.js";function S(t,s,o,d){if(!o.stewardSessionId)throw new Error("personal_worker_context_invalid");for(const e of d){const r=s.getWorkerForManager(o.sessionId,e.workerSessionId);if(!r)throw new Error("personal_worker_context_invalid");const i=`worker-notify:${e.id}`;if(l().getMessageState(o.stewardSessionId,i)){w(t.client),s.removeInboxEvents(o.sessionId,[e.id]);continue}const n=r.title?.slice(0,500)||"\u540E\u53F0\u4EFB\u52A1",c=e.kind==="worker.final"?"\u6267\u884C\u7ED3\u675F":e.kind==="worker.error"?"\u6267\u884C\u5931\u8D25":e.kind==="worker.aborted"?"\u5DF2\u505C\u6B62":"\u8FDB\u5C55";k(t,o.stewardSessionId,`${n} \xB7 ${c}
|
|
2
|
+
${e.summary}`.slice(0,2e4),"say",i,{id:r.sessionId,title:n,scheduleId:o.scheduleId,state:e.kind}),s.removeInboxEvents(o.sessionId,[e.id])}}export{S as deliverDirectWorkerEvents};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import c from"node:fs";import w from"node:path";import{createHash as k,randomBytes as v,randomUUID as u}from"node:crypto";import{isAgentHiddenPayload as S,sessionPreviewFromPayload as A}from"@shennian/wire";import{resolveShennianPath as p}from"../config/index.js";import{readMessages as b}from"../session/store.js";const m=p("manager-registry.json");function i(){return new Date().toISOString()}function T(){return{managers:{},workers:{},replyTargets:{},inbox:{},workerGrants:{}}}function I(a){return k("sha256").update(a).digest("hex")}function y(a,e){try{c.chmodSync(a,e)}catch(s){if(!(s instanceof Error&&"code"in s&&s.code==="ENOENT"))throw s}}class E{load(){try{const e=JSON.parse(c.readFileSync(m,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{},workerGrants:e.workerGrants??{}}}catch{return T()}}save(e){const s=w.dirname(m);for(let r=0;r<3;r+=1)try{c.mkdirSync(s,{recursive:!0,mode:448}),process.platform!=="win32"&&y(s,448);const n=`${m}.${u()}.tmp`;try{c.writeFileSync(n,JSON.stringify(e,null,2),{mode:384,flag:"wx"}),c.renameSync(n,m)}finally{try{c.unlinkSync(n)}catch{}}process.platform!=="win32"&&y(m,384);return}catch(n){if(!(n instanceof Error&&"code"in n&&n.code==="ENOENT")||r===2)throw n}}upsertManager(e){const s=this.load(),r=s.managers[e.sessionId],n=i(),o={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,managedRoomBound:e.managedRoomBound??r?.managedRoomBound??!1,stewardSessionId:e.stewardSessionId??r?.stewardSessionId,stewardAccountNamespaceId:e.stewardAccountNamespaceId??r?.stewardAccountNamespaceId??null,notificationMode:e.notificationMode??r?.notificationMode,scheduleId:e.scheduleId??r?.scheduleId,requestFingerprint:e.requestFingerprint??r?.requestFingerprint,controllerKind:e.controllerKind??r?.controllerKind??"managed_session",status:e.status??r?.status??"idle",deliveringInboxEventIds:e.deliveringInboxEventIds??r?.deliveringInboxEventIds??[],managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??n,updatedAt:n};return s.managers[e.sessionId]=o,this.save(s),o}getManager(e){return this.load().managers[e]}issueWorkerRef(e){const s=this.load(),r=s.managers[e.managerSessionId],n=s.workers[e.workerSessionId];if(!r||r.controllerKind!=="ephemeral")throw new Error("ephemeral_controller_required");if(!n||n.managedBy!==r.sessionId||n.workDir!==e.workDir)throw new Error("worker_scope_mismatch");const o=`wr_${v(32).toString("base64url")}`,t=I(o),d=i(),l={refDigest:t,managerSessionId:r.sessionId,workerSessionId:n.sessionId,workDir:e.workDir,machineId:e.machineId,accountNamespaceId:e.accountNamespaceId??null,actions:e.actions??["read","send","wait","stop"],createdAt:d,expiresAt:new Date(Date.now()+(e.ttlMs??720*60*6e4)).toISOString(),revokedAt:null};return s.workerGrants[t]=l,this.save(s),{workerRef:o,grant:l}}resolveWorkerRef(e){const s=this.resolveWorkerRefDetailed(e);return s.ok?s.grant:void 0}resolveWorkerRefDetailed(e){if(!/^wr_[A-Za-z0-9_-]{40,}$/.test(e.workerRef))return{ok:!1,reason:"invalid"};const s=this.load(),r=s.workerGrants[I(e.workerRef)];if(!r)return{ok:!1,reason:"invalid"};if(r.revokedAt)return{ok:!1,reason:"revoked"};if(Date.parse(r.expiresAt)<=Date.now())return{ok:!1,reason:"expired"};if(r.machineId!==e.machineId)return{ok:!1,reason:"invalid"};if(r.accountNamespaceId!==(e.accountNamespaceId??null))return{ok:!1,reason:"invalid"};if(!r.actions.includes(e.action))return{ok:!1,reason:"invalid"};const n=s.managers[r.managerSessionId],o=s.workers[r.workerSessionId];return!n||n.controllerKind!=="ephemeral"?{ok:!1,reason:"revoked"}:!o||o.managedBy!==n.sessionId||o.workDir!==r.workDir?{ok:!1,reason:"revoked"}:{ok:!0,grant:r}}revokeWorkerRefsForWorker(e){const s=this.load(),r=i();let n=!1;for(const o of Object.values(s.workerGrants))o.workerSessionId!==e||o.revokedAt||(o.revokedAt=r,n=!0);n&&this.save(s)}enqueueInboxEvent(e){const s=this.load(),r=s.managers[e.managerSessionId],n=s.workers[e.workerSessionId];if(!r||!n||n.managedBy!==r.sessionId||n.workDir!==r.workDir)return null;const o=i(),t=s.inbox[e.managerSessionId]??[],d=t.findIndex(g=>!r.deliveringInboxEventIds?.includes(g.id)&&g.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"||e.kind==="worker.progress"?g.kind===e.kind:g.kind===e.kind&&g.runId===e.runId)),l=d>=0?{...t[d],...e,updatedAt:o}:{...e,id:e.kind!=="worker.health"&&e.kind!=="worker.progress"&&e.runId?`mie_${k("sha256").update(JSON.stringify([e.managerSessionId,e.workerSessionId,e.kind,e.runId])).digest("hex")}`:`mie_${u()}`,createdAt:o,updatedAt:o};return d>=0?t[d]=l:t.push(l),s.inbox[e.managerSessionId]=t,this.save(s),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((s,r)=>s.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,s){if(!s.length)return;const r=this.load(),n=new Set(s);r.inbox[e]=(r.inbox[e]??[]).filter(o=>!n.has(o.id)),this.save(r)}addWorker(e){const s=this.load(),r=s.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const n=e.sessionId??`sess_worker_${u()}`,o=i(),t={sessionId:n,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:o,updatedAt:o,lastActivityAt:o,title:e.title??null,userGoal:e.userGoal??null,summary:e.summary??null,agentSessionId:null,runId:null};return s.workers[n]=t,r.managedWorkerSessionIds.includes(n)||r.managedWorkerSessionIds.push(n),r.updatedAt=o,this.save(s),t}updateWorker(e,s){const r=this.load(),n=r.workers[e];if(!n)return;const o={...n,...s,updatedAt:i()};return r.workers[e]=o,this.save(r),o}clearWorkerRunOrigins(){const e=this.load();let s=!1;for(const r of Object.values(e.workers))r.runRoomOrigins&&(delete r.runRoomOrigins,s=!0);for(const r of Object.values(e.inbox))for(const n of r)n.roomOrigin&&(delete n.roomOrigin,s=!0);s&&this.save(e)}listWorkers(e,s={}){const r=this.load(),n=r.managers[e];return n?Object.values(r.workers).filter(o=>o.managedBy===n.sessionId).filter(o=>o.workDir===n.workDir).filter(o=>s.includeManagers||o.agentType!=="manager").map(o=>this.decorateManagedWorker(o,s.runningSessionIds)).sort((o,t)=>t.lastActivityAt.localeCompare(o.lastActivityAt)):[]}getWorkerForManager(e,s){const r=this.load(),n=r.managers[e],o=r.workers[s];if(n&&o)return o.managedBy!==n.sessionId||o.workDir!==n.workDir||o.agentType==="manager"?void 0:this.decorateManagedWorker(o)}retireManager(e){const s=this.load(),r=Object.values(s.workers).filter(o=>o.managedBy===e),n=i();for(const o of Object.values(s.workerGrants))o.managerSessionId===e&&!o.revokedAt&&(o.revokedAt=n);delete s.managers[e],delete s.inbox[e];for(const[o,t]of Object.entries(s.replyTargets))t.managerSessionId===e&&delete s.replyTargets[o];return this.save(s),r}completeRetiredWorker(e,s){const r=this.load(),n=r.workers[s];if(!n||n.managedBy!==e)return;const o=i();for(const t of Object.values(r.workerGrants))t.workerSessionId===s&&!t.revokedAt&&(t.revokedAt=o);delete r.workers[s],this.save(r)}decorateManagedWorker(e,s){const r=s?.has(e.sessionId)?"running":e.status,n=this.readTranscriptSummary(e.sessionId,e.summary??null),o=n.lastMessagePreview??e.lastMessagePreview??e.summary??null,t=e.userGoal??n.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??t??o??e.summary??e.sessionId,userGoal:t,lastMessagePreview:o,summary:e.summary??o,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}readTranscriptSummary(e,s){const r=b(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:s};const n=[...r].sort((d,l)=>d.ts-l.ts),o=n.find(d=>d.role==="user"),t=[...n].reverse().find(d=>f(d));return{userGoal:o?h(f(o),160):null,lastMessagePreview:h(f(t),180)??s}}createReplyTarget(e){const s=this.load(),r={replyTarget:`rt_${u()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:i()};return s.replyTargets[r.replyTarget]=r,this.save(s),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(s=>s.managerSessionId===e).sort((s,r)=>r.createdAt.localeCompare(s.createdAt))[0]}}function f(a){return!a||S(a.payload)?null:A(a.payload,180)}function h(a,e){return a?a.length>e?`${a.slice(0,e)}...`:a:null}export{E as ManagerRegistry};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
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(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
|
|
1
|
+
import{deliverDirectWorkerEvents as W}from"./direct-worker-notifications.js";import T from"node:http";import{createHash as O,randomBytes as q,randomUUID as y}from"node:crypto";import I from"node:fs";import b from"node:os";import S from"node:path";import{ManagerRegistry as D}from"./registry.js";import{loadConfig as m}from"../config/index.js";import{isStewardSessionId as R}from"../steward/session.js";import{appendMessage as P,listSessionRecords as C}from"../session/store.js";import{requestSessionIndexPublish as B}from"../session/index-publisher.js";import{RetiredChannelRuntime as H}from"./retired-external-channel-compat.js";import{resolveShennianPath as $}from"../config/index.js";import{buildExternalChannelInstructions as U}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as k}from"../personal-sync/epoch.js";import{EphemeralWorkerError as j,handleEphemeralWorkerIpcRoute as G,handleWorkerIpcRoute as _}from"./worker-ipc.js";import{deriveWorkerTitle as F,formatWorkerMarkdown as J}from"./worker-markdown.js";import{markTrustedManagerEvent as E}from"../session/manager-event-authority.js";const K=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),w=12*6e4;let x=null;function Ie(g){x=g}function ye(){return x}function p(g){return S.resolve(g||b.homedir())}function f(g,e,r){g.writeHead(e,{"content-type":"application/json; charset=utf-8"}),g.end(JSON.stringify(r))}function v(g){return g.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function V(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function z(g,e){return`manager-inbox:${O("sha256").update(g).update("\0").update(e.map(t=>t.id).join("\0")).digest("hex").slice(0,32)}`}async function Q(g){const e=[];let r=0;for await(const t of g){const s=Buffer.isBuffer(t)?t:Buffer.from(t);if(r+=s.length,r>K)throw new Error("Request body too large");e.push(s)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function L(g,e,r,t){g.client.sendRes({type:"res",id:e,ok:r,...r?{payload:t}:{error:String(t.error||"unknown error")}})}function N(){return Y()?S.join(b.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):$("runtime","manager-ipc.json")}function Y(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class ke{opts;managedPolicies=new Map;registry=new D;channelRuntime;server=null;ipcUrl=null;ipcToken=q(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 H}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=T.createServer((r,t)=>{this.handleIpc(r,t)}),this.server.requestTimeout=w,this.server.timeout=w,this.server.keepAliveTimeout=w,this.server.headersTimeout=w+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=N();I.mkdirSync(S.dirname(e),{recursive:!0}),I.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),I.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=N(),r=JSON.parse(I.readFileSync(e,"utf-8"));(r.url===this.ipcUrl||r.token===this.ipcToken)&&I.unlinkSync(e)}catch{}}getInjectedEnv(e,r,t,s){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:r??"",SHENNIAN_MANAGER_WORKDIR:p(t),SHENNIAN_MANAGER_MODEL:s,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:p(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),s=this.managedPolicies.get(e);return!t||!s||p(r)!==t.workDir?null:s}isExternalWorkerController(e,r){const t=this.registry.getManager(e);return t?.controllerKind==="ephemeral"&&p(r)===t.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}getWorkerControllerSessionId(e,r){const t=this.findWorker(e);if(!t||p(r)!==t.workDir)return null;const s=this.registry.getManager(t.managedBy);return!s||s.workDir!==t.workDir?null:s.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(),s=[],a=async(n,d)=>{const i=[];try{t.runJournal?.markSessionAborted(n)}catch(h){i.push(h)}try{t.recoveryBarrier?.end(n)}catch(h){i.push(h)}try{t.chatQueue?.purgeSession(n)}catch(h){i.push(h)}const o=t.sessions.get(n);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(n),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{d?.()}catch(h){i.push(h),s.push(h)}else s.push(...i)};await a(e);for(const n of r)await a(n.sessionId,()=>this.registry.completeRetiredWorker(e,n.sessionId));if(s.length)throw new AggregateError(s,"Failed to retire every Manager or Worker session");return r.map(n=>n.sessionId)}setManagerWorkerDefaults(e,r,t){const s=this.registry.getManager(e);s&&this.registry.upsertManager({...s,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,s){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:r,workDir:p(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:s,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,r){if(R(e)&&(r.state==="final"||r.state==="error"||r.state==="aborted"))for(const i of Object.values(this.registry.load().managers))i.stewardSessionId===e&&(this.updateManagerStatus(i.sessionId,"idle"),queueMicrotask(()=>{this.drainManagerInbox(i.sessionId)}));const t=this.findWorker(e);if(!t)return;const s={lastActivityAt:new Date().toISOString(),runId:r.runId};if(r.agentSessionId&&(s.agentSessionId=r.agentSessionId),r.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(s.runRoomOrigins={...t.runRoomOrigins??{},[r.runId]:i})}const a=`${e}:${r.runId}`;let n=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=v(i);o&&(s.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(r.state==="final"||r.state==="error"||r.state==="aborted"){s.status=r.state;const i=v(this.workerTextAcc.get(a)??"");if(i&&(s.summary=i.length>240?`${i.slice(0,240)}...`:i,n=r.state==="final"?i:r.message||i),this.workerTextAcc.delete(a),t.runRoomOrigins?.[r.runId]){const o={...t.runRoomOrigins};delete o[r.runId],s.runRoomOrigins=o}}else r.state==="start"&&(s.status="running");const d=this.registry.updateWorker(e,s);d&&(r.errorCode==="auth_retrying"||r.runPhase==="waiting_approval"&&this.registry.getManager(d.managedBy)?.stewardSessionId)&&(this.registry.enqueueInboxEvent({managerSessionId:d.managedBy,workerSessionId:d.sessionId,kind:r.runPhase==="waiting_approval"?"worker.progress":"worker.health",priority:"normal",runId:r.runId,roomOrigin:t.runRoomOrigins?.[r.runId]??null,summary:r.message||(r.runPhase==="waiting_approval"?"Worker \u7B49\u5F85\u6743\u9650\u786E\u8BA4\uFF0C\u8BF7\u6838\u5BF9\u539F\u6388\u6743\u5E76\u5411\u7528\u6237\u8BF4\u660E\u3002":"Worker authentication is retrying."),...r.errorCode==="auth_retrying"?{code:"auth_retrying"}:{}}),this.drainManagerInbox(d.managedBy)),d&&(r.state==="final"||r.state==="error"||r.state==="aborted")&&this.enqueueWorkerEvent(d.managedBy,d,r.state,r.runId,n,t.runRoomOrigins?.[r.runId]??null,r.errorCode==="auth_required"||r.errorCode==="acp_auth_required"?"auth_required":void 0)}reportWorkerProgress(e,r){const t=this.findWorker(e),s=t&&this.registry.getManager(t.managedBy),a=this.opts.getRuntime().sessions.get(e)?.currentRunId;if(!t||!s?.stewardSessionId||!a||s.machineId!==m().machineId||s.stewardAccountNamespaceId!==(m().accountNamespaceId??null)||!R(s.stewardSessionId))throw new Error("personal_worker_context_invalid");this.registry.enqueueInboxEvent({managerSessionId:s.sessionId,workerSessionId:e,kind:"worker.progress",priority:"normal",runId:a,summary:r}),this.drainManagerInbox(s.sessionId)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){L(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,r){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){f(r,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),s=await Q(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){f(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),reportWorkerProgress:this.reportWorkerProgress.bind(this)},n=await G(a,t.pathname,s)??await _(a,t.pathname,s,String(e.headers["x-shennian-manager-session-id"]||""));if(n){f(r,n.status,n.body);return}f(r,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){if(t instanceof j){f(r,t.status,{ok:!1,error:t.code,message:t.message,nextAction:t.nextAction});return}f(r,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async executeScopedWorkerAction(e,r,t,s){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"},n=await _({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:s});if(!n||n.status<200||n.status>=300)throw new Error(String(n?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(n.body))}async dispatchChatEnqueue(e,r,t,s,a,n,d,i){if((await this.opts.dispatchReq(E({type:"req",id:`manager-enqueue-${y()}`,method:"chat.enqueue",params:{...k(),sessionId:e,text:s,agentType:r,workDir:t,agentSessionId:a,modelId:n,managedWorkerParentSessionId:d,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,r,t){this.registry.getManager(e)?.controllerKind==="managed_session"&&(P(e,{id:t,sessionId:e,role:"agent",ts:Date.now(),payload:r},"committed"),B(this.opts.getRuntime().client))}enqueueWorkerEvent(e,r,t,s,a,n,d){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:r.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:s??null,roomOrigin:n??null,summary:a||r.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...d?{code:d}:{}}),o=this.registry.getManager(e);i&&(o?.controllerKind==="managed_session"||o?.stewardSessionId)&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),r=this.registry.load(),t=this.opts.getRuntime();for(const s of Object.values(r.managers))s.stewardSessionId&&this.registry.listInboxEvents(s.sessionId).length&&this.drainManagerInbox(s.sessionId);for(const s of Object.values(r.workers)){if(s.status!=="running")continue;const a=e-Date.parse(s.createdAt);if(a<10*6e4)continue;const n=e-Date.parse(s.lastActivityAt),d=t.sessions.get(s.sessionId)?.currentRunId,i=!!t.chatQueue?.getSnapshot(s.sessionId).pending.length;if(!d){if(!i&&n>=10*6e4){const A="\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",M=this.registry.updateWorker(s.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:A});M&&this.enqueueWorkerEvent(M.managedBy,M,"error",s.runId??void 0,A)}continue}if(r.managers[s.managedBy]?.stewardSessionId)continue;const o=s.healthNotifiedAt?Date.parse(s.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=r.managers[s.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(n/6e4)),u=v(s.runId?this.workerTextAcc.get(`${s.sessionId}:${s.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
|
-
- \u6700\u8FD1\u6587\u672C\uFF1A${
|
|
6
|
-
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${
|
|
7
|
-
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(
|
|
5
|
+
- \u6700\u8FD1\u6587\u672C\uFF1A${u||s.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
|
|
6
|
+
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${s.lastActivityAt}
|
|
7
|
+
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(s.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:s.sessionId,kind:"worker.health",priority:"normal",runId:s.runId??null,roomOrigin:s.runRoomOrigins?.[s.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,r,t,s){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${y()}`,method:"chat.abort",params:{...k(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,r,!1,t,s)}bindManagerAdapterEvents(e,r){r.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const s=this.registry.getManager(e);s&&this.noteManagerAgentSession(e,t.agentSessionId,s.workDir,s.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.controllerKind==="ephemeral"&&!r.stewardSessionId||r.stewardSessionId&&(!R(r.stewardSessionId)||r.machineId!==m().machineId||r.stewardAccountNamespaceId!==(m().accountNamespaceId??null))||r.deliveringInboxEventIds?.length)return;const t=this.registry.listInboxEvents(e);if(!t.length)return;if(r.notificationMode==="direct"&&r.stewardSessionId){try{W(this.opts.getRuntime(),this.registry,r,t)}catch{}return}const s=r.stewardSessionId?void 0:t.find(u=>u.priority==="high"),a=this.opts.getRuntime().sessions.get(r.stewardSessionId??e);if(r.status!=="idle"&&!s||a?.currentRunId&&!s)return;const n=s??t[0],d=n.roomOrigin??null,i=JSON.stringify(d),o=r.stewardSessionId?[n]:s?[s]:t.filter(u=>JSON.stringify(u.roomOrigin??null)===i).slice(0,20),c=o.map(u=>{const l=this.registry.getWorkerForManager(e,u.workerSessionId);return J({kind:V(u.kind),sessionId:u.workerSessionId,title:l?.title||F(l?.userGoal||l?.summary||u.summary),body:u.summary})}).join(`
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
`),h=
|
|
11
|
+
`),h=z(e,o);try{s&&a?.currentRunId?(this.registry.upsertManager({...r,status:"interrupting",deliveringInboxEventIds:o.map(l=>l.id)}),await this.interruptAndResumeManager(r,c,h,d)):(this.registry.upsertManager({...r,status:"running",deliveringInboxEventIds:o.map(l=>l.id)}),await this.dispatchManagerContinuation(r,c,!0,h,d)),this.registry.removeInboxEvents(e,o.map(l=>l.id));const u=this.registry.getManager(e);u&&this.registry.upsertManager({...u,deliveringInboxEventIds:[],...u.stewardSessionId?{status:"idle"}:{}})}catch(u){const l=this.registry.getManager(e);l&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${u instanceof Error?u.message:String(u)}`)}}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;if((t.status!=="idle"||t.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]}),t.stewardSessionId)for(const n of this.registry.listWorkers(t.sessionId)){if(!n.runId||!["final","error","aborted"].includes(n.status))continue;const d=`worker.${n.status}`;this.registry.listInboxEvents(t.sessionId).some(i=>i.workerSessionId===n.sessionId&&i.kind===d&&i.runId===n.runId)||this.registry.enqueueInboxEvent({managerSessionId:t.sessionId,workerSessionId:n.sessionId,kind:d,priority:"normal",runId:n.runId,summary:n.summary||"\u540E\u53F0\u4EFB\u52A1\u5DF2\u7EC8\u7ED3\uFF0C\u8BF7\u8BFB\u53D6\u539F Worker \u6838\u67E5\u7ED3\u679C\u3002"})}const a=this.registry.listInboxEvents(t.sessionId).filter(n=>n.kind==="worker.health"&&!r.sessions.get(n.workerSessionId)?.currentRunId).map(n=>n.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,s,a){if(e.stewardSessionId){const c=C().find(u=>u.sessionId===e.stewardSessionId&&u.sessionScope==="steward");if(!c||e.machineId!==m().machineId||e.stewardAccountNamespaceId!==(m().accountNamespaceId??null))throw new Error("personal_worker_context_invalid");if((await this.opts.dispatchReq(E({type:"req",id:`steward-worker-${y()}`,method:"chat.enqueue",params:{...k(),sessionId:c.sessionId,sessionScope:"steward",sessionMode:"standard",agentType:c.agentType,agentSessionId:c.agentSessionId,workDir:c.workDir,text:`\u540E\u53F0 Worker \u4E8B\u4EF6\uFF08\u4E0D\u662F\u7528\u6237\u7684\u65B0\u6388\u6743\uFF1B\u5148\u6838\u5BF9\u7ED3\u679C\u4E0E\u539F\u76EE\u6807\uFF0C\u518D\u7528 shennian steward say \u5411\u7528\u6237\u6C47\u62A5\uFF1B\u672A\u9A8C\u6536\u4E0D\u8981\u79F0\u5B8C\u6210\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u8BD5\uFF09\uFF1A
|
|
12
|
+
${r}`,clientMessageId:s,managerEventId:s}})))?.durableQueueHandoff===!1)throw new Error("personal_worker_queue_handoff_unconfirmed");return}const n=e.agentType??"manager",d=n==="manager"?e.modelId:e.providerModelId??void 0,i={...k(),sessionId:e.sessionId,text:r,agentType:n,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:d,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:s,managerEventId:s,...a?{managerEventRoomOrigin:a}:{}};if((await this.opts.dispatchReq(E({type:"req",id:`manager-${t?"enqueue":"send"}-${y()}`,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=>U(r,void 0,e,"manager")).join(`
|
|
12
13
|
|
|
13
|
-
`).trim()}}export{
|
|
14
|
+
`).trim()}}export{ke as ManagerRuntimeService,ye as getManagerRuntimeService,Ie as setManagerRuntimeService};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import S from"node:fs";import v from"node:path";import{randomUUID as _}from"node:crypto";import{extractPayloadText as D,isAvailableAgentType as $,isAgentHiddenPayload as P,isToolPayload as C}from"@shennian/wire";import{loadConfig as U}from"../config/index.js";import{getPersonalSyncIdentity as q}from"../personal-sync/epoch.js";import{readMessages as O,recordSession as N}from"../session/store.js";import{deriveWorkerTitle as p,formatWorkerMarkdown as h,normalizeWorkerTitle as G}from"./worker-markdown.js";class w extends Error{code;nextAction;status;constructor(a,r,u,k=400){super(r),this.code=a,this.nextAction=u,this.status=k}}function x(e){return v.resolve(e||process.cwd())}function K(e){const a=x(e);let r;try{r=S.statSync(a)}catch{throw new w("worker_workdir_invalid","The Worker working directory does not exist.","Choose an existing local project directory and retry worker start.")}if(!r.isDirectory())throw new w("worker_workdir_invalid","The Worker working directory is not a directory.","Choose an existing local project directory and retry worker start.");const u=S.realpathSync.native(a);try{S.accessSync(u,S.constants.R_OK|S.constants.X_OK)}catch{throw new w("worker_workdir_inaccessible","The Worker working directory is not readable and searchable by this user.","Choose a project directory this Shennian daemon user can access and retry worker start.")}return u}function b(e){return $(e)&&e!=="manager"&&e!=="pi"&&e!=="openclaw"}function j(e){return e==="worker.final"?"completed":e==="worker.error"?"failed":e==="worker.aborted"?"stopped":"progress"}function L(e,a){const r=[];for(const u of e)if(!P(u.payload))if(C(u.payload)){let k="tool",l="completed";try{const d=JSON.parse(u.payload);k=d.name||k,l=d.status||l}catch{}r.push({...u,payload:`[\u5DE5\u5177] ${k} \xB7 ${l}`})}else r.push({...u,payload:D(u.payload)});return r.slice(0,Math.max(1,Math.min(a,500))).reverse()}async function M(e,a,r,u,k){if(!a.startsWith("/sessions/")&&a!=="/memory/path")return null;const l=String(r.managerSessionId||u||"");if(!l)throw new Error("managerSessionId is required");const d=e.registry.getManager(l);if(!d)throw new Error("Manager runtime is not registered");if(a==="/sessions/list"){const s=new Set([...e.getRuntime().sessions.entries()].filter(([,i])=>!!i.currentRunId).map(([i])=>i));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(l,{runningSessionIds:s})}}}if(a==="/memory/path")return{status:200,body:{ok:!0,path:v.join(d.workDir,".shennian")}};if(a==="/sessions/start"){const s=d.controllerKind==="ephemeral",o=s?String(r.agentType||r.agent||""):d.defaultWorkerAgentType||d.agentType||d.modelId;if(!b(o))throw new Error(`Unsupported manager worker agent: ${o}`);const i=s?x(String(r.workDir||d.workDir)):d.workDir;if(i!==d.workDir)throw new Error("Manager can only start workers in the same workDir");const g=String(r.message||"");if(!g)throw new Error("message is required");const n=r.title?G(String(r.title)):p(g),m=e.registry.addWorker({managerSessionId:l,agentType:o,workDir:i,title:n,summary:g.slice(0,120)}),y=s?String(r.modelId||""):d.defaultWorkerModelId??d.providerModelId??"";N({sessionId:m.sessionId,agentType:o,workDir:i,agentSessionId:null,modelId:y||null,title:n,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(m.sessionId,o,i,g,null,y||void 0,l,k?.roomOrigin);const f=h({kind:"created",sessionId:m.sessionId,title:n,body:g});return e.appendManagerVisibleMarkdown(l,f,`worker-created:${m.sessionId}`),{status:200,body:{ok:!0,session:m,markdown:f}}}const c=String(r.sessionId||""),t=e.registry.getWorkerForManager(l,c);if(!t||t.role!=="managed_worker"||t.managedBy!==l)throw new Error("Worker not found in this manager scope");if(a==="/sessions/send"){const s=String(r.message||"");if(!s.trim())throw new Error("message is required");const o=r.enqueue===void 0?!0:!!r.enqueue,g=e.getRuntime().sessions.get(c)?.currentRunId?"worker_busy":t.status==="final"||t.status==="error"||t.status==="aborted"?"worker_terminal":void 0,n=String(r.modelId||(t.agentType===d.defaultWorkerAgentType?d.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(t.sessionId,t.agentType,t.workDir,s,t.agentSessionId??null,n||void 0,l,k?.roomOrigin);const m=h({kind:"message",sessionId:c,title:t.title||p(t.userGoal||t.summary||s),body:s});return e.appendManagerVisibleMarkdown(l,m,`worker-message:${_()}`),{status:200,body:{ok:!0,markdown:m,queued:!0,directRequested:!o,...g?{code:g}:{}}}}if(a==="/sessions/queue")return{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(c)}};if(a==="/sessions/queue/edit"||a==="/sessions/queue/delete"){const s=a.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${s?"edit":"delete"}-${_()}`,method:s?"chat.queue.edit":"chat.queue.delete",params:{...q(),sessionId:c,queueMessageId:String(r.queueMessageId||r.messageId||""),...s?{text:String(r.message||r.text||"")}:{}}}),{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(c)}}}if(a==="/sessions/stop"||a==="/sessions/terminate"){if(t.status==="final"||t.status==="error"||t.status==="aborted"){const s=t.status==="final"?"completed":t.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,code:"worker_terminal",status:t.status,markdown:h({kind:s,sessionId:c,title:t.title||p(t.userGoal||t.summary||""),body:t.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return await e.dispatchReq({type:"req",id:`manager-abort-${_()}`,method:"chat.abort",params:{...q(),sessionId:c}}),e.registry.updateWorker(c,{status:"aborted"}),{status:200,body:{ok:!0,markdown:h({kind:"stopped",sessionId:c,title:t.title||p(t.userGoal||t.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(a==="/sessions/read"){const s=Number(r.limit||200),o=O(c,{limit:Math.max(s*20,s)});return{status:200,body:{ok:!0,messages:L(o,s),rawMessageCount:o.length}}}if(a==="/sessions/wait"){const s=Math.max(1e3,Math.min(Number(r.timeoutMs||6e5),6e5)),o=Date.now()+s;let i;for(;Date.now()<o&&(i=e.registry.listInboxEvents(l).find(n=>n.workerSessionId===c),!i);)await new Promise(n=>setTimeout(n,1e3));if(!i){const n=e.registry.getWorkerForManager(l,c);if(n&&(n.status==="final"||n.status==="error"||n.status==="aborted")){const m=n.status==="final"?"completed":n.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,timedOut:!1,alreadyTerminal:!0,status:n.status,markdown:h({kind:m,sessionId:c,title:n.title||p(n.userGoal||n.summary||""),body:n.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return{status:200,body:{ok:!0,timedOut:!0,code:"wait_timed_out",status:n?.status??"running",nextAction:"Call worker read or worker wait again later with the same workerRef."}}}const g=h({kind:j(i.kind),sessionId:c,title:t.title||p(t.userGoal||t.summary||i.summary),body:i.summary});return e.registry.removeInboxEvents(l,[i.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:g,event:i}}}return null}const B={"/workers/read":{action:"read",managerPath:"/sessions/read"},"/workers/send":{action:"send",managerPath:"/sessions/send"},"/workers/wait":{action:"wait",managerPath:"/sessions/wait"},"/workers/stop":{action:"stop",managerPath:"/sessions/stop"}};async function Z(e,a,r){if(!a.startsWith("/workers/"))return null;const u=U();if(!u.machineId||!u.machineToken&&!u.accessToken)throw new w("auth_required","Shennian CLI must be paired and logged in.","Pair this installation with shennian pair, then retry the same worker command.",401);if(a==="/workers/start"){const s=String(r.message||"").trim();if(!s)throw new w("worker_message_required","A Worker task message is required.","Provide --message or --message-file and retry worker start.");const o=String(r.agentType||r.agent||"");if(!o||!b(o))throw new w("runtime_unavailable",`Unsupported Worker Agent: ${o||"(empty)"}`,"Run shennian agent discover and select a Worker-capable Agent type.");const i=e.getAgentSnapshot?.().find(I=>I.type===o);if(!i||i.supportsExternalWorkerTarget!==!0)throw new w("runtime_unavailable",`Worker Agent is not available on this machine: ${o}`,"Run shennian agent discover and select a discovered Agent with supportsExternalWorkerTarget=true.");if(i.authState==="required"||i.authState==="error")throw new w("auth_required",`Worker Agent requires provider sign-in: ${o}`,"Sign in with the provider\u2019s own CLI on this computer, refresh discovery, and retry worker start.");const g=typeof r.modelId=="string"?r.modelId.trim():"";if(g&&i.models.length>0&&!i.models.some(I=>I.id===g))throw new w("model_unavailable",`The selected model was not returned by discovery for ${o}.`,`Run shennian agent discover and choose a model id returned for ${o}.`);const n=K(String(r.workDir||process.cwd())),m=`ephemeral_controller_${_()}`;e.registry.upsertManager({sessionId:m,agentSessionId:null,workDir:n,machineId:u.machineId,modelId:"manager",agentType:"manager",controllerKind:"ephemeral",status:"idle"});let y;try{y=await M(e,"/sessions/start",{managerSessionId:m,agentType:o,workDir:n,title:r.title,modelId:g||void 0,message:s},"")}catch(I){const E=e.registry.retireManager(m);for(const A of E)e.registry.completeRetiredWorker(m,A.sessionId);throw I}const f=y.body.session,W=typeof f?.sessionId=="string"?f.sessionId:"";if(!W)throw new Error("worker_start_invalid_response");const{workerRef:T,grant:R}=e.registry.issueWorkerRef({managerSessionId:m,workerSessionId:W,workDir:n,machineId:u.machineId,accountNamespaceId:u.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:W,workerRef:T,resolvedWorkDir:n,agentType:o,status:typeof f?.status=="string"?f.status:"running",expiresAt:R.expiresAt}}}const k=B[a];if(!k)throw new w("worker_command_unavailable","Unknown external Worker command.","Use worker start, read, send, wait, or stop. External Worker history cannot be listed.",404);const l=String(r.workerRef||r.ref||""),d=e.registry.resolveWorkerRefDetailed({workerRef:l,action:k.action,machineId:u.machineId,accountNamespaceId:u.accountNamespaceId??null});if(!d.ok){const s=d.reason==="expired"?"worker_ref_expired":d.reason==="revoked"?"worker_ref_revoked":"worker_ref_invalid";throw new w(s,`The Worker reference is ${d.reason} or belongs to another local scope.`,d.reason==="invalid"?"Use the exact workerRef returned by worker start. Lost references cannot be rediscovered.":"Start a new Worker if the task still needs to continue.",403)}const c=d.grant;let t;try{t=await M(e,k.managerPath,{...r,managerSessionId:c.managerSessionId,sessionId:c.workerSessionId},"")}catch(s){const o=s instanceof Error?s.message:String(s);throw o==="message is required"?new w("worker_message_required","A non-empty Worker message is required.","Provide --message or --message-file and retry worker send."):o.includes("not found")||o.includes("not registered")?new w("worker_ref_revoked","The Worker is no longer available in this controller scope.","Start a new Worker if the task still needs to continue.",410):new w("worker_command_failed",o,"Read the Worker state, correct the request, and retry once.")}if(!t)throw new w("worker_command_unavailable","The requested Worker operation is unavailable.","Use worker start, read, send, wait, or stop.",404);return{status:t.status,body:{...t.body,workerId:c.workerSessionId}}}export{w as EphemeralWorkerError,L as compactWorkerTranscript,Z as handleEphemeralWorkerIpcRoute,M as handleWorkerIpcRoute};
|
|
1
|
+
import{getStewardDir as B}from"../steward/workspace.js";import _ from"node:fs";import $ from"node:path";import{createHash as N,randomUUID as x}from"node:crypto";import{extractPayloadText as F,isAvailableAgentType as L,isAgentHiddenPayload as Y,isToolPayload as z}from"@shennian/wire";import{loadConfig as H}from"../config/index.js";import{getPersonalSyncIdentity as P}from"../personal-sync/epoch.js";import{isStewardSessionId as C}from"../steward/session.js";import{readWorkerSettings as J}from"../steward/worker-settings.js";import{readMessages as Q,recordSession as V}from"../session/store.js";import{deriveWorkerTitle as I,formatWorkerMarkdown as y,normalizeWorkerTitle as X}from"./worker-markdown.js";class g extends Error{code;nextAction;status;constructor(u,r,a,k=400){super(r),this.code=u,this.nextAction=a,this.status=k}}function O(e){return $.resolve(e||process.cwd())}function Z(e){const u=O(e);let r;try{r=_.statSync(u)}catch{throw new g("worker_workdir_invalid","The Worker working directory does not exist.","Choose an existing local project directory and retry worker start.")}if(!r.isDirectory())throw new g("worker_workdir_invalid","The Worker working directory is not a directory.","Choose an existing local project directory and retry worker start.");const a=_.realpathSync.native(u);try{_.accessSync(a,_.constants.R_OK|_.constants.X_OK)}catch{throw new g("worker_workdir_inaccessible","The Worker working directory is not readable and searchable by this user.","Choose a project directory this Shennian daemon user can access and retry worker start.")}return a}function U(e){return L(e)&&e!=="manager"&&e!=="pi"&&e!=="openclaw"}function ee(e){return e==="worker.final"?"completed":e==="worker.error"?"failed":e==="worker.aborted"?"stopped":"progress"}function re(e,u){const r=[];for(const a of e)if(!Y(a.payload))if(z(a.payload)){let k="tool",l="completed";try{const c=JSON.parse(a.payload);k=c.name||k,l=c.status||l}catch{}r.push({...a,payload:`[\u5DE5\u5177] ${k} \xB7 ${l}`})}else r.push({...a,payload:F(a.payload)});return r.slice(0,Math.max(1,Math.min(u,500))).reverse()}async function G(e,u,r,a,k){if(!u.startsWith("/sessions/")&&u!=="/memory/path")return null;const l=String(r.managerSessionId||a||"");if(!l)throw new Error("managerSessionId is required");const c=e.registry.getManager(l);if(!c)throw new Error("Manager runtime is not registered");if(u==="/sessions/list"){const t=new Set([...e.getRuntime().sessions.entries()].filter(([,n])=>!!n.currentRunId).map(([n])=>n));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(l,{runningSessionIds:t})}}}if(u==="/memory/path")return{status:200,body:{ok:!0,path:$.join(c.workDir,".shennian")}};if(u==="/sessions/start"){const t=c.controllerKind==="ephemeral",o=t?String(r.agentType||r.agent||""):c.defaultWorkerAgentType||c.agentType||c.modelId;if(!U(o))throw new Error(`Unsupported manager worker agent: ${o}`);const n=t?O(String(r.workDir||c.workDir)):c.workDir;if(n!==c.workDir)throw new Error("Manager can only start workers in the same workDir");const d=String(r.message||"");if(!d)throw new Error("message is required");const i=r.title?X(String(r.title)):I(d),w=e.registry.addWorker({managerSessionId:l,agentType:o,workDir:n,title:i,userGoal:d,summary:d.slice(0,120)}),p=t?String(r.modelId||""):c.defaultWorkerModelId??c.providerModelId??"";V({sessionId:w.sessionId,agentType:o,workDir:n,agentSessionId:null,modelId:p||null,title:i,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(w.sessionId,o,n,c.stewardSessionId?`${d}
|
|
2
|
+
|
|
3
|
+
${c.notificationMode==="direct"?"\u672C\u4EFB\u52A1\u7684 worker report \u4F1A\u76F4\u63A5\u901A\u77E5\u7528\u6237\u5E76\u6807\u660E\u672C Worker \u6765\u6E90\uFF0C\u65E0\u9700\u4E3B Agent \u8F6C\u8FF0\u3002\u53EA\u62A5\u544A\u91CD\u8981\u8FDB\u5C55\u3001\u5931\u8D25\u6216\u9700\u8981\u7528\u6237\u5904\u7406\u7684\u95EE\u9898\uFF1B\u5B8C\u6210\u65F6\u5728\u6700\u7EC8\u56DE\u590D\u63D0\u4F9B\u7B80\u77ED\u7ED3\u679C\u4E0E\u53EF\u70B9\u51FB\u7684\u65E5\u8BB0\u94FE\u63A5\uFF0CClient \u4F1A\u81EA\u52A8\u901A\u77E5\uFF0C\u4E0D\u8981\u7528 report \u91CD\u590D\u53D1\u9001\u6700\u7EC8\u7ED3\u679C\u3002\u65E5\u8BB0\u5199\u5165\u540E\u7528 [\u67E5\u770B\u65E5\u8BB0](shennian://diary/YYYY-MM-DD)\uFF0C\u66FF\u6362\u6210\u5B9E\u9645\u65E5\u671F\uFF1B\u4E0D\u8981\u8BA9\u624B\u673A\u7528\u6237\u590D\u5236\u7535\u8111\u8DEF\u5F84\u3002":""}\u8FD9\u662F\u4E2A\u4EBA\u7BA1\u5BB6\u59D4\u6D3E\u7684\u540E\u53F0\u4EFB\u52A1\u3002\u9075\u5B88\u6B64\u5DE5\u4F5C\u76EE\u5F55\u89C4\u5219\u4E0E\u539F\u59D4\u6258\u8303\u56F4\uFF1B\u4E0D\u8981\u521B\u5EFA\u65B0\u6743\u9650\u6216\u81EA\u884C\u53D1\u5E03\u3002\u91CD\u8981\u8FDB\u5C55\u6216\u9700\u8981\u7528\u6237\u51B3\u7B56\u65F6\uFF0C\u53EF\u8FD0\u884C shennian worker report --worker-id ${w.sessionId} --message <\u8FDB\u5C55\u6216\u963B\u585E\u8BF4\u660E>\uFF1B\u901A\u77E5\u4EC5\u9650\u672C\u4EFB\u52A1\u7ED1\u5B9A\u7684\u672C\u4EBA\u5BF9\u8BDD\uFF0C\u4E0D\u5411\u7FA4\u804A\u53D1\u9001\uFF1B\u975E\u76F4\u63A5\u901A\u77E5\u4EFB\u52A1\u7531\u7BA1\u5BB6\u8F6C\u8FF0\u3002\u6700\u7EC8\u63D0\u4F9B\u7ED3\u679C\u3001\u9A8C\u8BC1\u8BC1\u636E\u3001\u9650\u5236\u53CA\u4E0B\u4E00\u6B65\u3002`:d,null,p||void 0,l,k?.roomOrigin);const S=y({kind:"created",sessionId:w.sessionId,title:i,body:d});return e.appendManagerVisibleMarkdown(l,S,`worker-created:${w.sessionId}`),{status:200,body:{ok:!0,session:w,markdown:S}}}const m=String(r.sessionId||""),s=e.registry.getWorkerForManager(l,m);if(!s||s.role!=="managed_worker"||s.managedBy!==l)throw new Error("Worker not found in this manager scope");if(u==="/sessions/send"){const t=String(r.message||"");if(!t.trim())throw new Error("message is required");const o=r.enqueue===void 0?!0:!!r.enqueue,d=e.getRuntime().sessions.get(m)?.currentRunId?"worker_busy":s.status==="final"||s.status==="error"||s.status==="aborted"?"worker_terminal":void 0,i=String(r.modelId||(s.agentType===c.defaultWorkerAgentType?c.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(s.sessionId,s.agentType,s.workDir,t,s.agentSessionId??null,i||void 0,l,k?.roomOrigin);const w=y({kind:"message",sessionId:m,title:s.title||I(s.userGoal||s.summary||t),body:t});return e.appendManagerVisibleMarkdown(l,w,`worker-message:${x()}`),{status:200,body:{ok:!0,markdown:w,queued:!0,directRequested:!o,...d?{code:d}:{}}}}if(u==="/sessions/queue")return{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(m)}};if(u==="/sessions/queue/edit"||u==="/sessions/queue/delete"){const t=u.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${t?"edit":"delete"}-${x()}`,method:t?"chat.queue.edit":"chat.queue.delete",params:{...P(),sessionId:m,queueMessageId:String(r.queueMessageId||r.messageId||""),...t?{text:String(r.message||r.text||"")}:{}}}),{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(m)}}}if(u==="/sessions/stop"||u==="/sessions/terminate"){if(s.status==="final"||s.status==="error"||s.status==="aborted"){const t=s.status==="final"?"completed":s.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,code:"worker_terminal",status:s.status,markdown:y({kind:t,sessionId:m,title:s.title||I(s.userGoal||s.summary||""),body:s.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return await e.dispatchReq({type:"req",id:`manager-abort-${x()}`,method:"chat.abort",params:{...P(),sessionId:m}}),e.registry.updateWorker(m,{status:"aborted"}),{status:200,body:{ok:!0,markdown:y({kind:"stopped",sessionId:m,title:s.title||I(s.userGoal||s.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(u==="/sessions/read"){const t=Number(r.limit||200),o=Q(m,{limit:Math.max(t*20,t)});return{status:200,body:{ok:!0,messages:re(o,t),rawMessageCount:o.length}}}if(u==="/sessions/wait"){const t=Math.max(1e3,Math.min(Number(r.timeoutMs||6e5),6e5)),o=Date.now()+t;let n;for(;Date.now()<o&&(n=e.registry.listInboxEvents(l).find(i=>i.workerSessionId===m),!n);)await new Promise(i=>setTimeout(i,1e3));if(!n){const i=e.registry.getWorkerForManager(l,m);if(i&&(i.status==="final"||i.status==="error"||i.status==="aborted")){const w=i.status==="final"?"completed":i.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,timedOut:!1,alreadyTerminal:!0,status:i.status,markdown:y({kind:w,sessionId:m,title:i.title||I(i.userGoal||i.summary||""),body:i.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return{status:200,body:{ok:!0,timedOut:!0,code:"wait_timed_out",status:i?.status??"running",nextAction:"Call worker read or worker wait again later with the same workerRef."}}}const d=y({kind:ee(n.kind),sessionId:m,title:s.title||I(s.userGoal||s.summary||n.summary),body:n.summary});return e.registry.removeInboxEvents(l,[n.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:d,event:n}}}return null}const te={"/workers/read":{action:"read",managerPath:"/sessions/read"},"/workers/send":{action:"send",managerPath:"/sessions/send"},"/workers/wait":{action:"wait",managerPath:"/sessions/wait"},"/workers/stop":{action:"stop",managerPath:"/sessions/stop"}};async function ge(e,u,r){if(!u.startsWith("/workers/"))return null;const a=H();if(!a.machineId||!a.machineToken&&!a.accessToken)throw new g("auth_required","Shennian CLI must be paired and logged in.","Pair this installation with shennian pair, then retry the same worker command.",401);if(u==="/workers/list"){const t=String(r.stewardSessionId||"");if(!t||!C(t))throw new g("worker_command_unavailable","Worker listing requires a personal steward context.","External Workers remain controlled by their original workerRef.",404);return{status:200,body:{ok:!0,workers:Object.values(e.registry.load().managers).filter(n=>n.stewardSessionId===t&&n.machineId===a.machineId&&n.stewardAccountNamespaceId===(a.accountNamespaceId??null)).flatMap(n=>e.registry.listWorkers(n.sessionId)).sort((n,d)=>d.updatedAt.localeCompare(n.updatedAt)).slice(0,100).map(n=>({...n,workerRef:e.registry.issueWorkerRef({managerSessionId:n.managedBy,workerSessionId:n.sessionId,workDir:n.workDir,machineId:a.machineId,accountNamespaceId:a.accountNamespaceId??null}).workerRef}))}}}if(u==="/workers/report"){const t=typeof r.message=="string"?r.message.trim():"";if(!t||t.length>8e3||!e.reportWorkerProgress)throw new Error("personal_worker_report_invalid");return e.reportWorkerProgress(String(r.workerId||""),t),{status:200,body:{ok:!0}}}if(u==="/workers/start"){const t=String(r.message||"").trim();if(!t)throw new g("worker_message_required","A Worker task message is required.","Provide --message or --message-file and retry worker start.");const o=typeof r.stewardSessionId=="string"?r.stewardSessionId:"";if(o&&!C(o))throw new Error("personal_worker_context_invalid");const n=o?J():null,d=String(r.agentType||r.agent||n?.agentType||(o?"shennian":""));if(!d||!U(d))throw new g("runtime_unavailable",`Unsupported Worker Agent: ${d||"(empty)"}`,"Run shennian agent discover and select a Worker-capable Agent type.");const i=e.getAgentSnapshot?.().find(h=>h.type===d);if(!i||i.supportsExternalWorkerTarget!==!0)throw new g("runtime_unavailable",`Worker Agent is not available on this machine: ${d}`,"Run shennian agent discover and select a discovered Agent with supportsExternalWorkerTarget=true.");if(i.authState==="required"||i.authState==="error")throw new g("auth_required",`Worker Agent requires provider sign-in: ${d}`,"Sign in with the provider\u2019s own CLI on this computer, refresh discovery, and retry worker start.");const w=typeof r.modelId=="string"?r.modelId.trim():!r.agentType&&!r.agent?n?.modelId??"":"";if(d==="shennian"&&w)throw new g("model_unavailable","Shennian Workers use the personal model settings.","Set the Shennian model in Main Agent system settings and omit --model.");if(w&&i.models.length>0&&!i.models.some(h=>h.id===w))throw new g("model_unavailable",`The selected model was not returned by discovery for ${d}.`,`Run shennian agent discover and choose a model id returned for ${d}.`);const p=Z(String(r.workDir||n?.workDir||(o?B():process.cwd()))),S=typeof r.requestId=="string"?r.requestId:"";if(o&&!/^[A-Za-z0-9_.:@-]{8,128}$/.test(S))throw new Error("personal_worker_request_id_required");const W=r.notificationMode==="direct"&&o?"direct":void 0,A=W&&typeof r.scheduleId=="string"?r.scheduleId:void 0,D=N("sha256").update(JSON.stringify({message:t,agentType:d,modelId:w,workDir:p,title:r.title??null,...W?{notificationMode:W,scheduleId:A}:{}})).digest("hex"),f=o?`steward_worker_${N("sha256").update(`${a.accountNamespaceId??""}:${a.machineId}:${o}:${S}`).digest("hex")}`:`ephemeral_controller_${x()}`,M=e.registry.getManager(f);if(M&&M.requestFingerprint!==D)throw new Error("personal_worker_request_conflict");const v=M?e.registry.listWorkers(f)[0]:void 0;if(v){const{workerRef:h,grant:T}=e.registry.issueWorkerRef({managerSessionId:f,workerSessionId:v.sessionId,workDir:p,machineId:a.machineId,accountNamespaceId:a.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:v.sessionId,workerRef:h,resolvedWorkDir:p,agentType:d,status:v.status,expiresAt:T.expiresAt,reused:!0}}}e.registry.upsertManager({sessionId:f,agentSessionId:null,workDir:p,machineId:a.machineId,modelId:"manager",agentType:"manager",controllerKind:"ephemeral",notificationMode:W,scheduleId:A,...o?{stewardSessionId:o,stewardAccountNamespaceId:a.accountNamespaceId??null,requestFingerprint:D}:{},status:"idle"});let b;try{b=await G(e,"/sessions/start",{managerSessionId:f,agentType:d,workDir:p,title:r.title,modelId:w||void 0,message:t},"")}catch(h){if(o){for(const E of e.registry.listWorkers(f))e.registry.updateWorker(E.sessionId,{status:"error",summary:"\u4EFB\u52A1\u5165\u961F\u672A\u786E\u8BA4\uFF1B\u8BF7\u8BFB\u53D6\u72B6\u6001\u6838\u5BF9\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u5EFA\u3002"});throw h}const T=e.registry.retireManager(f);for(const E of T)e.registry.completeRetiredWorker(f,E.sessionId);throw h}const q=b.body.session,R=typeof q?.sessionId=="string"?q.sessionId:"";if(!R)throw new Error("worker_start_invalid_response");const{workerRef:j,grant:K}=e.registry.issueWorkerRef({managerSessionId:f,workerSessionId:R,workDir:p,machineId:a.machineId,accountNamespaceId:a.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:R,workerRef:j,resolvedWorkDir:p,agentType:d,status:typeof q?.status=="string"?q.status:"running",expiresAt:K.expiresAt}}}const k=te[u];if(!k)throw new g("worker_command_unavailable","Unknown external Worker command.","Use worker start, read, send, wait, or stop. External Worker history cannot be listed.",404);const l=String(r.workerRef||r.ref||""),c=e.registry.resolveWorkerRefDetailed({workerRef:l,action:k.action,machineId:a.machineId,accountNamespaceId:a.accountNamespaceId??null});if(!c.ok){const t=c.reason==="expired"?"worker_ref_expired":c.reason==="revoked"?"worker_ref_revoked":"worker_ref_invalid";throw new g(t,`The Worker reference is ${c.reason} or belongs to another local scope.`,c.reason==="invalid"?"Use the exact workerRef returned by worker start. Lost references cannot be rediscovered.":"Start a new Worker if the task still needs to continue.",403)}const m=c.grant;let s;try{s=await G(e,k.managerPath,{...r,managerSessionId:m.managerSessionId,sessionId:m.workerSessionId},"")}catch(t){const o=t instanceof Error?t.message:String(t);throw o==="message is required"?new g("worker_message_required","A non-empty Worker message is required.","Provide --message or --message-file and retry worker send."):o.includes("not found")||o.includes("not registered")?new g("worker_ref_revoked","The Worker is no longer available in this controller scope.","Start a new Worker if the task still needs to continue.",410):new g("worker_command_failed",o,"Read the Worker state, correct the request, and retry once.")}if(!s)throw new g("worker_command_unavailable","The requested Worker operation is unavailable.","Use worker start, read, send, wait, or stop.",404);return{status:s.status,body:{...s.body,workerId:m.workerSessionId}}}export{g as EphemeralWorkerError,re as compactWorkerTranscript,ge as handleEphemeralWorkerIpcRoute,G as handleWorkerIpcRoute};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadConfig as p}from"../config/index.js";import{readPersonalModel as f,savePersonalModel as u,validatePersonalModel as h}from"./model-settings.js";const y={"https://dashscope.aliyuncs.com/compatible-mode/v1":"deepseek-v4.1-flash-1017","https://tokenhub.tencentmaas.com/v1":"deepseek-v4-flash","https://api.deepseek.com":"deepseek-flash","https://api.deepseek.com/v1":"deepseek-flash"},s=()=>{throw new Error("personal_model_settings_invalid")};async function b(o,r){const t=await fetch(`${o}/models`,{headers:{authorization:`Bearer ${r}`},redirect:"error",signal:AbortSignal.timeout(8e3)});if(!t.ok||!t.body)return await t.body?.cancel(),s();const i=t.body.getReader(),a=[];let n=0;try{for(;;){const{value:e,done:m}=await i.read();if(m)break;if(n+=e.byteLength,n>256*1024)return s();a.push(e)}}finally{await i.cancel().catch(()=>{}),i.releaseLock()}const d=JSON.parse(Buffer.concat(a).toString("utf8"));if(!Array.isArray(d.data))return s();const c=d.data.filter(e=>e&&typeof e.id=="string"&&e.id.trim()&&e.id.length<=2e3&&!/[\u0000-\u001f]/.test(e.id)&&!/embed|rerank|whisper|tts|dall-e|moderation/i.test(e.id)),l=c.find(e=>e.isDefault===!0||e.is_default===!0)??c.find(e=>/deepseek.*flash|deepseek-chat|qwen.*plus|gpt.*mini/i.test(String(e.id)))??c[0];return typeof l?.id=="string"?l.id.trim():s()}async function v(o){if(!o||typeof o!="object"||Array.isArray(o))return s();const r=o;if(r.mode!=="custom"||r.model!==void 0)return u(o);const t=f(),i=p().accountNamespaceId,a=typeof r.baseUrl=="string"?r.baseUrl.trim().replace(/\/$/,""):t.baseUrl;if(!a||a!==t.baseUrl&&!r.apiKey)return s();const n=h({...t,...r,baseUrl:a,model:t.model??"automatic"}),d=t.baseUrl===a&&t.model?t.model:y[a]??await b(a,n.apiKey);return p().accountNamespaceId!==i||JSON.stringify(f())!==JSON.stringify(t)?s():u({...n,model:d})}export{v as saveAutomaticPersonalModel};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import r from"node:fs";import m from"node:path";import{randomUUID as f,createHash as p}from"node:crypto";import{getShennianDir as s,loadConfig as u}from"../config/index.js";const y="deepseek-v4.1-flash-1017",c=()=>{const o=u().accountNamespaceId??"unpaired",e=p("sha256").update(o).digest("hex").slice(0,32);return m.join(s(),`personal-model-${e}.json`)},n=()=>{throw new Error("personal_model_settings_invalid")};function l(o){if(!o||typeof o!="object"||Array.isArray(o))return n();const e=o;if(Object.keys(e).some(t=>!["mode","model","baseUrl","apiKey","contextWindow"].includes(t))||!["official","custom"].includes(String(e.mode)))return n();for(const t of["model","baseUrl","apiKey"])if(e[t]!==void 0&&(typeof e[t]!="string"||!e[t].trim()||e[t].length>(t==="apiKey"?8192:2e3)||/[\u0000-\u001f]/.test(e[t])))return n();if(e.contextWindow!==void 0&&(!Number.isSafeInteger(e.contextWindow)||Number(e.contextWindow)<8192||Number(e.contextWindow)>2e6))return n();if(e.baseUrl){let t;try{t=new URL(String(e.baseUrl))}catch{return n()}const i=["localhost","127.0.0.1","[::1]"].includes(t.hostname);if(t.username||t.password||t.search||t.hash||t.protocol!=="https:"&&!(t.protocol==="http:"&&i))return n()}return e.mode==="custom"&&(!e.model||!e.baseUrl||!e.apiKey)?n():{mode:e.mode,...e.model?{model:String(e.model).trim()}:{},...e.baseUrl?{baseUrl:String(e.baseUrl).trim().replace(/\/$/,"")}:{},...e.apiKey?{apiKey:String(e.apiKey).trim()}:{},...e.contextWindow?{contextWindow:Number(e.contextWindow)}:{}}}function d(){try{const o=r.openSync(c(),r.constants.O_RDONLY|(r.constants.O_NOFOLLOW??0));try{return!r.fstatSync(o).isFile()||r.fstatSync(o).size>16384?n():l(JSON.parse(r.readFileSync(o,"utf8")))}finally{r.closeSync(o)}}catch(o){return o.code==="ENOENT"?{mode:"official"}:n()}}function h(o=d()){return{mode:o.mode,model:o.mode==="official"?y:o.model,baseUrl:o.baseUrl??"",hasApiKey:!!o.apiKey,contextWindow:o.contextWindow??
|
|
2
|
-
`,{mode:384,flag:"wx"}),r.renameSync(a,c())}finally{try{r.unlinkSync(a)}catch{}}return h(t)}function O(o=
|
|
1
|
+
import r from"node:fs";import m from"node:path";import{randomUUID as f,createHash as p}from"node:crypto";import{getShennianDir as s,loadConfig as u}from"../config/index.js";const y="deepseek-v4.1-flash-1017",c=()=>{const o=u().accountNamespaceId??"unpaired",e=p("sha256").update(o).digest("hex").slice(0,32);return m.join(s(),`personal-model-${e}.json`)},n=()=>{throw new Error("personal_model_settings_invalid")};function l(o){if(!o||typeof o!="object"||Array.isArray(o))return n();const e=o;if(Object.keys(e).some(t=>!["mode","model","baseUrl","apiKey","contextWindow"].includes(t))||!["official","custom"].includes(String(e.mode)))return n();for(const t of["model","baseUrl","apiKey"])if(e[t]!==void 0&&(typeof e[t]!="string"||!e[t].trim()||e[t].length>(t==="apiKey"?8192:2e3)||/[\u0000-\u001f]/.test(e[t])))return n();if(e.contextWindow!==void 0&&(!Number.isSafeInteger(e.contextWindow)||Number(e.contextWindow)<8192||Number(e.contextWindow)>2e6))return n();if(e.baseUrl){let t;try{t=new URL(String(e.baseUrl))}catch{return n()}const i=["localhost","127.0.0.1","[::1]"].includes(t.hostname);if(t.username||t.password||t.search||t.hash||t.protocol!=="https:"&&!(t.protocol==="http:"&&i))return n()}return e.mode==="custom"&&(!e.model||!e.baseUrl||!e.apiKey)?n():{mode:e.mode,...e.model?{model:String(e.model).trim()}:{},...e.baseUrl?{baseUrl:String(e.baseUrl).trim().replace(/\/$/,"")}:{},...e.apiKey?{apiKey:String(e.apiKey).trim()}:{},...e.contextWindow?{contextWindow:Number(e.contextWindow)}:{}}}function d(){try{const o=r.openSync(c(),r.constants.O_RDONLY|(r.constants.O_NOFOLLOW??0));try{return!r.fstatSync(o).isFile()||r.fstatSync(o).size>16384?n():l(JSON.parse(r.readFileSync(o,"utf8")))}finally{r.closeSync(o)}}catch(o){return o.code==="ENOENT"?{mode:"official"}:n()}}function h(o=d()){return{mode:o.mode,model:o.mode==="official"?y:o.model,baseUrl:o.baseUrl??"",hasApiKey:!!o.apiKey,contextWindow:o.contextWindow??1e6}}function w(o){if(!o||typeof o!="object"||Array.isArray(o))return n();const e=o,t=l({...d(),...e}),i=s();r.mkdirSync(i,{recursive:!0,mode:448});const a=`${c()}.${f()}.tmp`;try{r.writeFileSync(a,JSON.stringify(t)+`
|
|
2
|
+
`,{mode:384,flag:"wx"}),r.renameSync(a,c())}finally{try{r.unlinkSync(a)}catch{}}return h(t)}function O(o=1e6){return{enabled:!0,reserveTokens:Math.min(8192,Math.floor(o/4)),keepRecentTokens:Math.min(16e3,Math.floor(o/4))}}export{y as OFFICIAL_MODEL,O as personalCompactionSettings,h as personalModelSummary,d as readPersonalModel,w as savePersonalModel,l as validatePersonalModel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{loadConfig as
|
|
1
|
+
import{loadConfig as e}from"../config/index.js";import{regionToUrl as o}from"../region.js";function a(){return(e().serverUrl??o("cn")).replace(/\/$/,"")}class c{async get(){const r=e();if(!r.machineToken||!r.machineId)throw new Error("personal_model_pairing_required");return r.machineToken}}export{c as OfficialModelCredential,a as personalModelServerUrl};
|