shennian 0.4.11 → 0.4.13

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.
Files changed (122) hide show
  1. package/dist/publish-build-manifest.json +183 -53
  2. package/dist/src/agents/acp/adapter.d.ts +78 -0
  3. package/dist/src/agents/acp/adapter.js +3 -0
  4. package/dist/src/agents/acp/auth-state.d.ts +3 -0
  5. package/dist/src/agents/acp/auth-state.js +1 -0
  6. package/dist/src/agents/acp/auth.d.ts +7 -0
  7. package/dist/src/agents/acp/auth.js +1 -0
  8. package/dist/src/agents/acp/capability-cache.d.ts +14 -0
  9. package/dist/src/agents/acp/capability-cache.js +2 -0
  10. package/dist/src/agents/acp/catalog.d.ts +11 -0
  11. package/dist/src/agents/acp/catalog.js +2 -0
  12. package/dist/src/agents/acp/client-services.d.ts +32 -0
  13. package/dist/src/agents/acp/client-services.js +3 -0
  14. package/dist/src/agents/acp/discovery.d.ts +9 -0
  15. package/dist/src/agents/acp/discovery.js +1 -0
  16. package/dist/src/agents/acp/events.d.ts +4 -0
  17. package/dist/src/agents/acp/events.js +3 -0
  18. package/dist/src/agents/acp/manifests.d.ts +63 -0
  19. package/dist/src/agents/acp/manifests.js +2 -0
  20. package/dist/src/agents/acp/metrics.d.ts +24 -0
  21. package/dist/src/agents/acp/metrics.js +1 -0
  22. package/dist/src/agents/acp/package-commands.d.ts +4 -0
  23. package/dist/src/agents/acp/package-commands.js +1 -0
  24. package/dist/src/agents/acp/process-client.d.ts +39 -0
  25. package/dist/src/agents/acp/process-client.js +1 -0
  26. package/dist/src/agents/acp/process-tree.d.ts +2 -0
  27. package/dist/src/agents/acp/process-tree.js +1 -0
  28. package/dist/src/agents/acp/types.d.ts +31 -0
  29. package/dist/src/agents/acp/types.js +1 -0
  30. package/dist/src/agents/acp/watcher.d.ts +22 -0
  31. package/dist/src/agents/acp/watcher.js +1 -0
  32. package/dist/src/agents/adapter.d.ts +96 -2
  33. package/dist/src/agents/adapter.js +1 -1
  34. package/dist/src/agents/claude.d.ts +14 -0
  35. package/dist/src/agents/claude.js +5 -5
  36. package/dist/src/agents/codex.d.ts +6 -0
  37. package/dist/src/agents/codex.js +1 -1
  38. package/dist/src/agents/command-spec.d.ts +5 -0
  39. package/dist/src/agents/command-spec.js +3 -3
  40. package/dist/src/agents/cursor.d.ts +6 -0
  41. package/dist/src/agents/cursor.js +1 -1
  42. package/dist/src/agents/custom-recovery-conformance.d.ts +2 -0
  43. package/dist/src/agents/custom-recovery-conformance.js +2 -0
  44. package/dist/src/agents/custom.d.ts +9 -0
  45. package/dist/src/agents/custom.js +4 -4
  46. package/dist/src/agents/detect.d.ts +11 -2
  47. package/dist/src/agents/detect.js +1 -1
  48. package/dist/src/agents/gemini.d.ts +6 -0
  49. package/dist/src/agents/gemini.js +1 -1
  50. package/dist/src/agents/manager.d.ts +2 -0
  51. package/dist/src/agents/manager.js +5 -5
  52. package/dist/src/agents/model-registry/service.js +1 -1
  53. package/dist/src/agents/opencode.d.ts +6 -0
  54. package/dist/src/agents/opencode.js +1 -1
  55. package/dist/src/agents/pi-coding-agent.d.ts +6 -0
  56. package/dist/src/agents/pi-coding-agent.js +4 -4
  57. package/dist/src/agents/workbuddy.d.ts +6 -0
  58. package/dist/src/agents/workbuddy.js +2 -2
  59. package/dist/src/commands/agent.d.ts +2 -0
  60. package/dist/src/commands/agent.js +2 -0
  61. package/dist/src/commands/daemon.d.ts +6 -2
  62. package/dist/src/commands/daemon.js +19 -23
  63. package/dist/src/commands/pair.js +10 -10
  64. package/dist/src/commands/upgrade.d.ts +7 -1
  65. package/dist/src/commands/upgrade.js +1 -1
  66. package/dist/src/config/index.d.ts +1 -1
  67. package/dist/src/config/index.js +2 -2
  68. package/dist/src/daemon-ipc/control.d.ts +16 -0
  69. package/dist/src/daemon-ipc/control.js +1 -0
  70. package/dist/src/index.js +3 -3
  71. package/dist/src/log-reporter.d.ts +23 -0
  72. package/dist/src/log-reporter.js +1 -1
  73. package/dist/src/relay/client.d.ts +1 -0
  74. package/dist/src/relay/client.js +1 -1
  75. package/dist/src/session/handlers/agents.d.ts +1 -0
  76. package/dist/src/session/handlers/agents.js +1 -1
  77. package/dist/src/session/handlers/chat.d.ts +3 -0
  78. package/dist/src/session/handlers/chat.js +2 -2
  79. package/dist/src/session/manager.d.ts +20 -2
  80. package/dist/src/session/manager.js +3 -1
  81. package/dist/src/session/queue.js +2 -2
  82. package/dist/src/session/run-recovery/barrier.d.ts +12 -0
  83. package/dist/src/session/run-recovery/barrier.js +1 -0
  84. package/dist/src/session/run-recovery/coordinator.d.ts +32 -0
  85. package/dist/src/session/run-recovery/coordinator.js +1 -0
  86. package/dist/src/session/run-recovery/journal.d.ts +30 -0
  87. package/dist/src/session/run-recovery/journal.js +2 -0
  88. package/dist/src/session/run-recovery/schema.d.ts +43 -0
  89. package/dist/src/session/run-recovery/schema.js +1 -0
  90. package/dist/src/session/types.d.ts +10 -0
  91. package/dist/src/upgrade/engine.d.ts +6 -5
  92. package/dist/src/upgrade/engine.js +2 -2
  93. package/dist/src/upgrade/scheduler.d.ts +14 -0
  94. package/dist/src/upgrade/scheduler.js +1 -1
  95. package/dist/src/upgrade/updater-bootstrap.d.ts +12 -0
  96. package/dist/src/upgrade/updater-bootstrap.js +2 -0
  97. package/dist/src/upgrade/updater-launcher.d.ts +28 -0
  98. package/dist/src/upgrade/updater-launcher.js +3 -0
  99. package/dist/src/upgrade/updater-protocol.d.ts +37 -0
  100. package/dist/src/upgrade/updater-protocol.js +1 -0
  101. package/dist/src/upgrade/updater-worker.d.ts +11 -0
  102. package/dist/src/upgrade/updater-worker.js +2 -0
  103. package/node_modules/@shennian/wire/dist/events.d.ts +31 -0
  104. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  105. package/node_modules/@shennian/wire/dist/index.js +1 -0
  106. package/node_modules/@shennian/wire/dist/machine.d.ts +49 -1
  107. package/node_modules/@shennian/wire/dist/message-payload.d.ts +1 -0
  108. package/node_modules/@shennian/wire/dist/message-payload.js +4 -1
  109. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  110. package/node_modules/@shennian/wire/dist/relay-connectivity.d.ts +30 -0
  111. package/node_modules/@shennian/wire/dist/relay-connectivity.js +29 -0
  112. package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -2
  113. package/node_modules/@shennian/wire/dist/session-sync.js +2 -0
  114. package/node_modules/@shennian/wire/dist/session.d.ts +84 -1
  115. package/node_modules/@shennian/wire/dist/session.js +5 -1
  116. package/package.json +5 -3
  117. package/dist/native/macos-background-client/Shennian.app/Contents/CodeResources +0 -0
  118. package/dist/native/macos-background-client/Shennian.app/Contents/Info.plist +0 -17
  119. package/dist/native/macos-background-client/Shennian.app/Contents/MacOS/ShennianClientLauncher +0 -0
  120. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -2
  121. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +0 -2
  122. package/dist/native/macos-background-client/Shennian.app/Contents/_CodeSignature/CodeResources +0 -153
@@ -0,0 +1 @@
1
+ import{isAvailableAgentType as d}from"@shennian/wire";const a=1,m="Continue.",c=new Set(["dispatching","running","waiting_approval","recovering","terminal","interrupted_by_upgrade","recovery_failed","aborted"]),u=new Set(["personal","managed_room","managed_direct","manager_worker"]),p=new Set(["none","intent","accepted"]),g=new Set(["standard","manager"]);function t(n,e){if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function r(n,e){if(n==null)return null;if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function s(n,e){const o=t(n,e);if(!Number.isFinite(Date.parse(o)))throw new Error(`invalid ${e}`);return o}function v(n){if(!n||typeof n!="object"||Array.isArray(n))throw new Error("invalid run journal record");const e=n;if(e.schemaVersion!==a)throw new Error(`unsupported run journal schema: ${String(e.schemaVersion)}`);const o=t(e.agentType,"agentType");if(!d(o))throw new Error("invalid agentType");if(!c.has(e.phase))throw new Error("invalid phase");if(!u.has(e.source))throw new Error("invalid source");if(!p.has(e.continueDispatchState))throw new Error("invalid continueDispatchState");if(!g.has(e.sessionMode))throw new Error("invalid sessionMode");if(!Number.isSafeInteger(e.lastObservedEventSeq)||Number(e.lastObservedEventSeq)<-1)throw new Error("invalid lastObservedEventSeq");if(!Number.isSafeInteger(e.recoveryAttempts)||Number(e.recoveryAttempts)<0)throw new Error("invalid recoveryAttempts");if(e.activationHopCount!=null&&(!Number.isSafeInteger(e.activationHopCount)||Number(e.activationHopCount)<0))throw new Error("invalid activationHopCount");if(e.source==="managed_room"&&(!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount==null||!e.managedPolicyVersion||!e.managedRuntimePolicyHash))throw new Error("managed room recovery identity is incomplete");if(e.source==="manager_worker"&&(!e.parentSessionId||!e.managedRuntimePolicyHash))throw new Error("manager worker recovery identity is incomplete");const i=e.interruptionReason;if(i!=null&&i!=="daemon_crash"&&i!=="planned_upgrade"&&i!=="os_restart")throw new Error("invalid interruptionReason");return{schemaVersion:a,daemonInstallationId:t(e.daemonInstallationId,"daemonInstallationId"),sessionId:t(e.sessionId,"sessionId"),runId:t(e.runId,"runId"),clientMessageId:t(e.clientMessageId,"clientMessageId"),agentType:o,sessionMode:e.sessionMode,workDir:t(e.workDir,"workDir"),agentSessionId:r(e.agentSessionId,"agentSessionId"),modelId:r(e.modelId,"modelId"),reasoningEffort:r(e.reasoningEffort,"reasoningEffort"),nativeSessionVisibility:e.nativeSessionVisibility==="internal"?"internal":"user",providerRunId:r(e.providerRunId,"providerRunId"),lastObservedEventSeq:Number(e.lastObservedEventSeq),phase:e.phase,source:e.source,parentSessionId:r(e.parentSessionId,"parentSessionId"),roomContextRegistryKey:r(e.roomContextRegistryKey,"roomContextRegistryKey"),activationCauseMessageId:r(e.activationCauseMessageId,"activationCauseMessageId"),rootCauseMessageId:r(e.rootCauseMessageId,"rootCauseMessageId"),activationSequence:r(e.activationSequence,"activationSequence"),activationHopCount:e.activationHopCount==null?null:Number(e.activationHopCount),managedPolicyVersion:r(e.managedPolicyVersion,"managedPolicyVersion"),managedRuntimePolicyHash:r(e.managedRuntimePolicyHash,"managedRuntimePolicyHash"),upgradeJobId:r(e.upgradeJobId,"upgradeJobId"),interruptionReason:i,continueDispatchState:e.continueDispatchState,recoveryAttempts:Number(e.recoveryAttempts),recoveryReason:r(e.recoveryReason,"recoveryReason"),createdAt:s(e.createdAt,"createdAt"),updatedAt:s(e.updatedAt,"updatedAt")}}function I(n){return n==="dispatching"||n==="running"||n==="waiting_approval"||n==="recovering"}export{m as RECOVERY_CONTINUE_TEXT,a as RUN_JOURNAL_SCHEMA_VERSION,I as isRecoverableJournalPhase,v as parseRunJournalRecord};
@@ -5,6 +5,10 @@ import type { NativeSessionFusionService } from '../native-fusion/service.js';
5
5
  import type { ManagerRuntimeService } from '../manager/runtime.js';
6
6
  import type { ResolveAuthorizedPathResult } from '../fs/boundary.js';
7
7
  import type { ManagedRoomExecutor } from '../room/managed-room-ipc.js';
8
+ import type { ActiveRunJournal } from './run-recovery/journal.js';
9
+ import type { RunRecoveryBarrier } from './run-recovery/barrier.js';
10
+ import type { RunJournalRecord } from './run-recovery/schema.js';
11
+ import type { PreparedManagedRoomTurn } from './queue.js';
8
12
  export type ActiveSession = {
9
13
  adapter: AgentAdapter;
10
14
  workDir: string;
@@ -29,6 +33,8 @@ export type ActiveSession = {
29
33
  externalReplyTarget?: string | null;
30
34
  /** Opaque per-turn Room capability; revoked and cleared on every terminal path. */
31
35
  managedRoomContextToken?: string | null;
36
+ /** Stable Shennian recovery identity; provider run ids are stored separately. */
37
+ journalRunId?: string | null;
32
38
  };
33
39
  export type PendingTransfer = {
34
40
  transferId: string;
@@ -78,4 +84,8 @@ export type SessionManagerRuntime = {
78
84
  upgradePolicyController?: UpgradePolicyController | null;
79
85
  revokeManagedRoomContextToken?: ((token: string) => void) | null;
80
86
  managedRoomExecutor?: ManagedRoomExecutor | null;
87
+ runJournal?: ActiveRunJournal | null;
88
+ recoveryBarrier?: RunRecoveryBarrier | null;
89
+ /** Rebuilds short-lived Room authority from current server state after restart. */
90
+ prepareManagedRecovery?: (record: RunJournalRecord) => Promise<PreparedManagedRoomTurn | null>;
81
91
  };
@@ -26,11 +26,13 @@ export type UpgradeResult = {
26
26
  error: string;
27
27
  rolledBack?: boolean;
28
28
  };
29
- type UpgradeAttempt = {
29
+ export type UpgradeAttempt = {
30
30
  from: string;
31
31
  to: string;
32
32
  attemptCount: number;
33
33
  attemptAt: number;
34
+ jobId?: string;
35
+ operation?: 'upgrade' | 'rollback';
34
36
  };
35
37
  export type UpgradeFailureRecord = {
36
38
  version: string;
@@ -73,10 +75,10 @@ export declare function recordUpgradeFailure(version: string, error: string, now
73
75
  export declare function clearUpgradeFailure(version: string): void;
74
76
  /**
75
77
  * Called on daemon startup. Detects crash loops from a failed upgrade and
76
- * initiates rollback if crash count exceeds threshold.
77
- * Returns true if rollback was performed (caller should exit and let service manager restart).
78
+ * requests an external rollback if crash count exceeds threshold.
79
+ * Package replacement remains owned by the npm-package-independent Updater.
78
80
  */
79
- export declare function handleStartupCrashCheck(): Promise<boolean>;
81
+ export declare function handleStartupCrashCheck(): Promise<UpgradeAttempt | null>;
80
82
  export declare function performUpgrade(targetVersion: string, onProgress: (p: UpgradeProgress) => void, opts?: {
81
83
  currentVersion?: string;
82
84
  }): Promise<UpgradeResult>;
@@ -100,4 +102,3 @@ export type VersionCheckResult = {
100
102
  changeType: 'patch' | 'minor' | 'major';
101
103
  };
102
104
  export declare function checkForUpdate(currentVersion?: string): Promise<VersionCheckResult>;
103
- export {};
@@ -1,3 +1,3 @@
1
- import s from"node:fs";import c from"node:path";import{execSync as U,execFile as T}from"node:child_process";import{promisify as P}from"node:util";import{fileURLToPath as L}from"node:url";import{getShennianDir as _,resolveShennianPath as d}from"../config/index.js";const f=P(T),m=d("upgrade-attempt.json"),G=3,I="https://registry.npmjs.org",H=10*6e4,$=[5*6e4,30*6e4,120*6e4,360*6e4];function y(){try{const e=g();if(e)return JSON.parse(s.readFileSync(e,"utf-8")).version}catch{}return"0.0.0"}function g(e=L(import.meta.url)){let r=c.dirname(e);for(let n=0;n<6;n++){const t=c.join(r,"package.json");if(s.existsSync(t)&&JSON.parse(s.readFileSync(t,"utf-8")).name==="shennian")return t;r=c.dirname(r)}return null}async function B(){const e=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`npm registry returned ${e.status}`);return(await e.json()).version}function Y(e,r){const n=p=>p.replace(/^v/,"").split(".").map(Number),[t,i,o]=n(e),[a,u,l]=n(r);return a>t?"major":a===t&&u>i?"minor":a===t&&u===i&&l>o?"patch":"none"}function z(){return U("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function K(e,r=process.platform){const n=r==="win32"?c.win32:c.posix,t=n.dirname(e),i=n.dirname(t);if(n.basename(i)!=="node_modules")return null;const o=n.dirname(i);return n.basename(o)==="lib"?n.dirname(o):o}function N(){const e=g();return e?K(e):null}function X(){const e=g();return e?c.join(c.dirname(e),"dist","bin","shennian.js"):c.join(z(),"shennian","dist","bin","shennian.js")}function Z(){try{return JSON.parse(s.readFileSync(m,"utf-8"))}catch{return null}}function h(e){s.mkdirSync(_(),{recursive:!0}),s.writeFileSync(m,JSON.stringify(e,null,2))}function k(){try{s.unlinkSync(m)}catch{}}function R(){return d("upgrade-failures.json")}function q(){return d("upgrade.lock")}function j(e=q(),r={}){const n=r.now??Date.now(),t=r.pid??process.pid,i=r.isProcessAlive??(l=>{try{return process.kill(l,0),!0}catch{return!1}});s.mkdirSync(c.dirname(e),{recursive:!0});const o=()=>s.openSync(e,"wx",384);let a;try{a=o()}catch(l){if(l.code!=="EEXIST")throw l;let p=!1;try{const v=JSON.parse(s.readFileSync(e,"utf-8")),E=Number(v.pid),F=Number(v.createdAt);p=!Number.isInteger(E)||!Number.isFinite(F)||n-F>H||!i(E)}catch{p=!0}if(!p)throw new Error("Another Shennian upgrade is already running");s.rmSync(e,{force:!0}),a=o()}s.writeFileSync(a,JSON.stringify({pid:t,createdAt:n})),s.closeSync(a);let u=!1;return{release(){if(!u){u=!0;try{s.unlinkSync(e)}catch{}}}}}function w(e=R()){try{const r=JSON.parse(s.readFileSync(e,"utf-8"));return!r||typeof r!="object"||!r.versions?{versions:{}}:{versions:r.versions}}catch{return{versions:{}}}}function C(e){s.mkdirSync(_(),{recursive:!0}),s.writeFileSync(R(),JSON.stringify(e,null,2))}function Q(e){return $[Math.min(Math.max(e-1,0),$.length-1)]}function ie(e,r=Date.now(),n=w()){const t=n.versions[e];return!!(t&&t.nextRetryAt>r)}function se(e,r,n=Date.now()){const t=w(),i=t.versions[e],o=(i?.failureCount??0)+1,a={version:e,failureCount:o,firstFailedAt:i?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+Q(o),error:r};return t.versions[e]=a,C(t),a}function S(e){const r=w();r.versions[e]&&(delete r.versions[e],C(r))}async function ce(){const e=Z();if(!e)return!1;if(Date.now()-e.attemptAt>600*1e3)return k(),!1;if(e.attemptCount++,e.attemptCount>=G){console.error(`[upgrade] New version crashed ${e.attemptCount} times, rolling back to ${e.from}...`);let n;try{n=j()}catch(t){return console.error(`[upgrade] Rollback deferred: ${t instanceof Error?t.message:String(t)}`),!1}try{return await J(e.from),k(),console.error(`[upgrade] Rolled back to ${e.from}. Restarting...`),!0}catch(t){return console.error(`[upgrade] Rollback failed: ${t instanceof Error?t.message:String(t)}`),k(),!1}finally{n.release()}}return h(e),!1}async function le(e,r,n={}){let t;try{t=j()}catch(i){return{ok:!1,error:i instanceof Error?i.message:String(i)}}try{return await W(e,r,n)}finally{t.release()}}async function W(e,r,n={}){const t=n.currentVersion??y(),i=y();if(t===e)return S(e),{ok:!1,error:`Already on version ${e}`};r({step:"checking"});try{U("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(i===e){r({step:"verifying",version:e});try{await b(e)}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return h({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),S(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}r({step:"backing-up"}),r({step:"installing",version:e});try{await D(e)}catch(o){const a=await O(t);return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}r({step:"verifying",version:e});try{await b(e)}catch(o){const a=await O(t);return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}return h({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),S(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}function M(e){if(!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(e))throw new Error(`Invalid npm version: ${e}`)}function x(){return process.platform==="win32"?"npm.cmd":"npm"}function V(e,r){return{timeout:r,windowsHide:!0,shell:e==="win32"}}function A(e){return V(process.platform,e)}async function D(e){M(e);const r=N(),n=["install","-g",`shennian@${e}`,...r?["--prefix",r]:[]];try{await f(x(),n,A(12e4))}catch(t){const i=t instanceof Error?t.message:String(t);try{await f(x(),[...n,"--registry",I],{...A(12e4)})}catch(o){const a=o instanceof Error?o.message:String(o);throw new Error(`${i}
1
+ import s from"node:fs";import c from"node:path";import{execSync as F,execFile as D}from"node:child_process";import{promisify as J}from"node:util";import{fileURLToPath as O}from"node:url";import{getShennianDir as U,resolveShennianPath as m}from"../config/index.js";const f=J(D),y=m("upgrade-attempt.json"),T=3,P="https://registry.npmjs.org",L=10*6e4,_=[5*6e4,30*6e4,120*6e4,360*6e4];function h(){try{const e=g();if(e)return JSON.parse(s.readFileSync(e,"utf-8")).version}catch{}return"0.0.0"}function g(e=O(import.meta.url)){let r=c.dirname(e);for(let n=0;n<6;n++){const t=c.join(r,"package.json");if(s.existsSync(t)&&JSON.parse(s.readFileSync(t,"utf-8")).name==="shennian")return t;r=c.dirname(r)}return null}async function G(){const e=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`npm registry returned ${e.status}`);return(await e.json()).version}function I(e,r){const n=p=>p.replace(/^v/,"").split(".").map(Number),[t,a,o]=n(e),[i,u,l]=n(r);return i>t?"major":i===t&&u>a?"minor":i===t&&u===a&&l>o?"patch":"none"}function H(){return F("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function B(e,r=process.platform){const n=r==="win32"?c.win32:c.posix,t=n.dirname(e),a=n.dirname(t);if(n.basename(a)!=="node_modules")return null;const o=n.dirname(a);return n.basename(o)==="lib"?n.dirname(o):o}function N(){const e=g();return e?B(e):null}function Y(){const e=g();return e?c.join(c.dirname(e),"dist","bin","shennian.js"):c.join(H(),"shennian","dist","bin","shennian.js")}function z(){try{return JSON.parse(s.readFileSync(y,"utf-8"))}catch{return null}}function d(e){s.mkdirSync(U(),{recursive:!0}),s.writeFileSync(y,JSON.stringify(e,null,2))}function K(){try{s.unlinkSync(y)}catch{}}function j(){return m("upgrade-failures.json")}function X(){return m("upgrade.lock")}function Z(e=X(),r={}){const n=r.now??Date.now(),t=r.pid??process.pid,a=r.isProcessAlive??(l=>{try{return process.kill(l,0),!0}catch{return!1}});s.mkdirSync(c.dirname(e),{recursive:!0});const o=()=>s.openSync(e,"wx",384);let i;try{i=o()}catch(l){if(l.code!=="EEXIST")throw l;let p=!1;try{const v=JSON.parse(s.readFileSync(e,"utf-8")),b=Number(v.pid),E=Number(v.createdAt);p=!Number.isInteger(b)||!Number.isFinite(E)||n-E>L||!a(b)}catch{p=!0}if(!p)throw new Error("Another Shennian upgrade is already running");s.rmSync(e,{force:!0}),i=o()}s.writeFileSync(i,JSON.stringify({pid:t,createdAt:n})),s.closeSync(i);let u=!1;return{release(){if(!u){u=!0;try{s.unlinkSync(e)}catch{}}}}}function k(e=j()){try{const r=JSON.parse(s.readFileSync(e,"utf-8"));return!r||typeof r!="object"||!r.versions?{versions:{}}:{versions:r.versions}}catch{return{versions:{}}}}function $(e){s.mkdirSync(U(),{recursive:!0}),s.writeFileSync(j(),JSON.stringify(e,null,2))}function q(e){return _[Math.min(Math.max(e-1,0),_.length-1)]}function ae(e,r=Date.now(),n=k()){const t=n.versions[e];return!!(t&&t.nextRetryAt>r)}function se(e,r,n=Date.now()){const t=k(),a=t.versions[e],o=(a?.failureCount??0)+1,i={version:e,failureCount:o,firstFailedAt:a?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+q(o),error:r};return t.versions[e]=i,$(t),i}function w(e){const r=k();r.versions[e]&&(delete r.versions[e],$(r))}async function ce(){const e=z();return e?Date.now()-e.attemptAt>600*1e3?(K(),null):(e.attemptCount++,e.attemptCount>=T?(d(e),e):(d(e),null)):null}async function le(e,r,n={}){let t;try{t=Z()}catch(a){return{ok:!1,error:a instanceof Error?a.message:String(a)}}try{return await Q(e,r,n)}finally{t.release()}}async function Q(e,r,n={}){const t=n.currentVersion??h(),a=h();if(t===e)return w(e),{ok:!1,error:`Already on version ${e}`};r({step:"checking"});try{F("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(a===e){r({step:"verifying",version:e});try{await A(e)}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return d({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),w(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}r({step:"backing-up"}),r({step:"installing",version:e});try{await M(e)}catch(o){const i=await R(t);return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}${i.ok?"":`; rollback failed: ${i.error}`}`,rolledBack:i.ok}}r({step:"verifying",version:e});try{await A(e)}catch(o){const i=await R(t);return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}${i.ok?"":`; rollback failed: ${i.error}`}`,rolledBack:i.ok}}return d({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),w(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}function C(e){if(!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(e))throw new Error(`Invalid npm version: ${e}`)}function S(){return process.platform==="win32"?"npm.cmd":"npm"}function W(e,r){return{timeout:r,windowsHide:!0,shell:e==="win32"}}function x(e){return W(process.platform,e)}async function M(e){C(e);const r=N(),n=["install","-g",`shennian@${e}`,...r?["--prefix",r]:[]];try{await f(S(),n,x(12e4))}catch(t){const a=t instanceof Error?t.message:String(t);try{await f(S(),[...n,"--registry",P],{...x(12e4)})}catch(o){const i=o instanceof Error?o.message:String(o);throw new Error(`${a}
2
2
  --- npmjs fallback ---
3
- ${a}`)}}}async function b(e){M(e);const r=X(),{stdout:n}=await f(process.execPath,[r,"--version"],{timeout:1e4,windowsHide:!0});if(n.trim()!==e)throw new Error(`Version verification failed: expected ${e}, got ${n.trim()||"empty output"}`);const t=N();await f(x(),["ls","-g",`shennian@${e}`,"--omit=dev","--depth=0",...t?["--prefix",t]:[]],{...A(3e4)})}async function J(e,r={}){await(r.install??D)(e),await(r.verify??b)(e)}async function O(e){try{return await J(e),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}async function ue(e){const r=e??y(),n=await B(),t=Y(r,n);return t==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:r,latest:n,changeType:t}}export{j as acquireUpgradeLock,ue as checkForUpdate,k as clearUpgradeAttempt,S as clearUpgradeFailure,Y as compareVersions,B as fetchLatestVersion,g as findPackageJson,y as getCurrentVersion,Q as getUpgradeRetryDelayMs,ce as handleStartupCrashCheck,D as installShennianVersion,ie as isUpgradeVersionInCooldown,V as npmExecOptionsForPlatform,le as performUpgrade,Z as readUpgradeAttempt,w as readUpgradeFailures,se as recordUpgradeFailure,K as resolveNpmGlobalPrefixFromPackageJson,J as rollbackToExactVersion,b as verifyInstalledVersion,h as writeUpgradeAttempt};
3
+ ${i}`)}}}async function A(e){C(e);const r=Y(),{stdout:n}=await f(process.execPath,[r,"--version"],{timeout:1e4,windowsHide:!0});if(n.trim()!==e)throw new Error(`Version verification failed: expected ${e}, got ${n.trim()||"empty output"}`);const t=N();await f(S(),["ls","-g",`shennian@${e}`,"--omit=dev","--depth=0",...t?["--prefix",t]:[]],{...x(3e4)})}async function V(e,r={}){await(r.install??M)(e),await(r.verify??A)(e)}async function R(e){try{return await V(e),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}async function ue(e){const r=e??h(),n=await G(),t=I(r,n);return t==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:r,latest:n,changeType:t}}export{Z as acquireUpgradeLock,ue as checkForUpdate,K as clearUpgradeAttempt,w as clearUpgradeFailure,I as compareVersions,G as fetchLatestVersion,g as findPackageJson,h as getCurrentVersion,q as getUpgradeRetryDelayMs,ce as handleStartupCrashCheck,M as installShennianVersion,ae as isUpgradeVersionInCooldown,W as npmExecOptionsForPlatform,le as performUpgrade,z as readUpgradeAttempt,k as readUpgradeFailures,se as recordUpgradeFailure,B as resolveNpmGlobalPrefixFromPackageJson,V as rollbackToExactVersion,A as verifyInstalledVersion,d as writeUpgradeAttempt};
@@ -9,7 +9,15 @@ export type CliReleasePolicy = {
9
9
  rolloutPercentage: number;
10
10
  pausedVersions: string[];
11
11
  blockedVersions: string[];
12
+ blockedCurrentVersions: string[];
12
13
  killSwitch: boolean;
14
+ releaseLevel: 'normal' | 'required' | 'blocked';
15
+ maxIdleWaitMs: number;
16
+ minimumSessionSyncProtocolVersion?: number;
17
+ minimumDataEpoch?: number;
18
+ requiredCapabilities: string[];
19
+ externalUpdaterEnabled: boolean;
20
+ minimumUpdaterProtocolVersion?: number;
13
21
  };
14
22
  type AutoUpgradeSchedulerDeps = {
15
23
  currentVersion: string;
@@ -24,6 +32,11 @@ type AutoUpgradeSchedulerDeps = {
24
32
  intervalMs?: number;
25
33
  setTimeout?: (handler: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
26
34
  clearTimeout?: (timer: ReturnType<typeof setTimeout>) => void;
35
+ now?: () => number;
36
+ sessionSyncProtocolVersion?: number;
37
+ dataEpoch?: number;
38
+ advertisedCapabilities?: readonly string[];
39
+ updaterProtocolVersion?: number;
27
40
  };
28
41
  export declare function deterministicRolloutBucket(machineId: string): number;
29
42
  export declare function isAutomaticPatchAllowed(policy: CliReleasePolicy, machineId: string, currentVersion: string, targetVersion: string): boolean;
@@ -34,6 +47,7 @@ export declare class AutoUpgradeScheduler {
34
47
  private timer;
35
48
  private started;
36
49
  private upgradeInFlight;
50
+ private eligibleSince;
37
51
  constructor(deps: AutoUpgradeSchedulerDeps);
38
52
  start(): void;
39
53
  stop(): void;
@@ -1 +1 @@
1
- import{checkForUpdate as a,isUpgradeVersionInCooldown as l}from"./engine.js";const n={automaticPatchEnabled:!1,rolloutPercentage:0,pausedVersions:[],blockedVersions:[],killSwitch:!0};function o(s){let e=2166136261;for(let t=0;t<s.length;t++)e^=s.charCodeAt(t),e=Math.imul(e,16777619);return(e>>>0)%100}function c(s,e,t,i){if(s.killSwitch||!s.automaticPatchEnabled||s.blockedVersions.includes(i)||s.pausedVersions.includes(i)||s.pausedVersions.includes(t))return!1;const r=Math.max(0,Math.min(100,Math.floor(s.rolloutPercentage)));return o(e)<r}async function h(s){const e=new URL("/api/releases/cli-policy",s),t=await fetch(e,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!t.ok)throw new Error(`release policy returned ${t.status}`);const i=await t.json();return{automaticPatchEnabled:i.automaticPatchEnabled===!0,rolloutPercentage:typeof i.rolloutPercentage=="number"?i.rolloutPercentage:0,pausedVersions:Array.isArray(i.pausedVersions)?i.pausedVersions.filter(r=>typeof r=="string"):[],blockedVersions:Array.isArray(i.blockedVersions)?i.blockedVersions.filter(r=>typeof r=="string"):[],killSwitch:i.killSwitch!==!1}}class d{deps;policy="none";timer=null;started=!1;upgradeInFlight=!1;constructor(e){this.deps=e}start(){this.started||(this.started=!0,this.policy==="patch"&&this.arm(this.deps.initialDelayMs??3e4))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}setPolicy(e){if(this.policy=e,!!this.started){if(e==="none"){this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null;return}this.timer||this.arm(0)}}getPolicy(){return this.policy}async runOnce(){if(this.policy!=="patch"||this.upgradeInFlight||!this.deps.getIdleState().idle)return;const t=await(this.deps.checkForUpdate??a)(this.deps.currentVersion);if(!t.hasUpdate||t.changeType!=="patch"||(this.deps.inCooldown??l)(t.latest))return;let i=n;try{i=await(this.deps.fetchReleasePolicy??(()=>h(this.deps.serverUrl)))()}catch{return}if(c(i,this.deps.machineId,t.current,t.latest)){this.upgradeInFlight=!0;try{await this.deps.startUpgrade(t.latest)}finally{this.upgradeInFlight=!1}}}arm(e){if(!this.started||this.timer)return;const t=this.deps.setTimeout??setTimeout;this.timer=t(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.policy==="patch"&&this.arm(this.deps.intervalMs??5*6e4)})},e),this.timer.unref?.()}}export{d as AutoUpgradeScheduler,o as deterministicRolloutBucket,h as fetchCliReleasePolicy,c as isAutomaticPatchAllowed};
1
+ import{checkForUpdate as d,isUpgradeVersionInCooldown as u}from"./engine.js";const m={automaticPatchEnabled:!1,rolloutPercentage:0,pausedVersions:[],blockedVersions:[],blockedCurrentVersions:[],killSwitch:!0,releaseLevel:"normal",maxIdleWaitMs:30*6e4,requiredCapabilities:[],externalUpdaterEnabled:!1};function h(r){let i=2166136261;for(let t=0;t<r.length;t++)i^=r.charCodeAt(t),i=Math.imul(i,16777619);return(i>>>0)%100}function p(r,i,t,e){if(r.killSwitch||!r.automaticPatchEnabled||r.blockedVersions.includes(e)||r.pausedVersions.includes(e)||r.pausedVersions.includes(t))return!1;const s=Math.max(0,Math.min(100,Math.floor(r.rolloutPercentage)));return h(i)<s}async function f(r){const i=new URL("/api/releases/cli-policy",r),t=await fetch(i,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!t.ok)throw new Error(`release policy returned ${t.status}`);const e=await t.json();return{automaticPatchEnabled:e.automaticPatchEnabled===!0,rolloutPercentage:typeof e.rolloutPercentage=="number"?e.rolloutPercentage:0,pausedVersions:Array.isArray(e.pausedVersions)?e.pausedVersions.filter(s=>typeof s=="string"):[],blockedVersions:Array.isArray(e.blockedVersions)?e.blockedVersions.filter(s=>typeof s=="string"):[],blockedCurrentVersions:Array.isArray(e.blockedCurrentVersions)?e.blockedCurrentVersions.filter(s=>typeof s=="string"):[],killSwitch:e.killSwitch!==!1,releaseLevel:e.releaseLevel==="required"||e.releaseLevel==="blocked"?e.releaseLevel:"normal",maxIdleWaitMs:typeof e.maxIdleWaitMs=="number"&&Number.isFinite(e.maxIdleWaitMs)?Math.max(0,Math.min(1440*6e4,Math.floor(e.maxIdleWaitMs))):30*6e4,...typeof e.minimumSessionSyncProtocolVersion=="number"?{minimumSessionSyncProtocolVersion:e.minimumSessionSyncProtocolVersion}:{},...typeof e.minimumDataEpoch=="number"?{minimumDataEpoch:e.minimumDataEpoch}:{},requiredCapabilities:Array.isArray(e.requiredCapabilities)?e.requiredCapabilities.filter(s=>typeof s=="string"):[],externalUpdaterEnabled:e.externalUpdaterEnabled===!0,...typeof e.minimumUpdaterProtocolVersion=="number"?{minimumUpdaterProtocolVersion:e.minimumUpdaterProtocolVersion}:{}}}class y{deps;policy="none";timer=null;started=!1;upgradeInFlight=!1;eligibleSince=new Map;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??3e4))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}setPolicy(i){this.policy=i,this.started&&(this.timer||this.arm(0))}getPolicy(){return this.policy}async runOnce(){if(this.upgradeInFlight)return;const i=await(this.deps.checkForUpdate??d)(this.deps.currentVersion);if(!i.hasUpdate||(this.deps.inCooldown??u)(i.latest))return;let t=m;try{t=await(this.deps.fetchReleasePolicy??(()=>f(this.deps.serverUrl)))()}catch{return}if(t.killSwitch||!t.externalUpdaterEnabled||t.blockedVersions.includes(i.latest)||t.pausedVersions.includes(i.latest)||t.pausedVersions.includes(i.current))return;const e=t.blockedCurrentVersions.includes(i.current)?"blocked":t.releaseLevel,s=t.minimumSessionSyncProtocolVersion!==void 0&&(this.deps.sessionSyncProtocolVersion??0)<t.minimumSessionSyncProtocolVersion||t.minimumDataEpoch!==void 0&&(this.deps.dataEpoch??0)<t.minimumDataEpoch||t.requiredCapabilities.some(c=>!(this.deps.advertisedCapabilities??[]).includes(c))||t.minimumUpdaterProtocolVersion!==void 0&&(this.deps.updaterProtocolVersion??0)<t.minimumUpdaterProtocolVersion,a=e==="required"||e==="blocked"||s;if(!a&&(this.policy!=="patch"||i.changeType!=="patch"||!p(t,this.deps.machineId,i.current,i.latest)))return;const l=this.deps.getIdleState(),n=(this.deps.now??Date.now)(),o=this.eligibleSince.get(i.latest)??n;if(this.eligibleSince.set(i.latest,o),!(!a&&!l.idle&&n-o<t.maxIdleWaitMs)){this.upgradeInFlight=!0;try{await this.deps.startUpgrade(i.latest),this.eligibleSince.delete(i.latest)}finally{this.upgradeInFlight=!1}}}arm(i){if(!this.started||this.timer)return;const t=this.deps.setTimeout??setTimeout;this.timer=t(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5*6e4)})},i),this.timer.unref?.()}}export{y as AutoUpgradeScheduler,h as deterministicRolloutBucket,f as fetchCliReleasePolicy,p as isAutomaticPatchAllowed};
@@ -0,0 +1,12 @@
1
+ import { UPDATER_PROTOCOL_VERSION } from './updater-protocol.js';
2
+ export type UpdaterReadiness = {
3
+ protocolVersion: typeof UPDATER_PROTOCOL_VERSION;
4
+ workerPath: string;
5
+ installedAt: string;
6
+ };
7
+ export declare function ensureUpdaterInstalled(options?: {
8
+ root?: string;
9
+ assetPath?: string;
10
+ now?: Date;
11
+ }): UpdaterReadiness;
12
+ export declare function readUpdaterReadiness(root?: string): UpdaterReadiness | null;
@@ -0,0 +1,2 @@
1
+ import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as p}from"node:crypto";import{resolveShennianPath as d}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function f(o,t){r.mkdirSync(e.dirname(o),{recursive:!0,mode:448});const n=`${o}.tmp-${process.pid}-${p()}`;try{r.writeFileSync(n,`${JSON.stringify(t,null,2)}
2
+ `,{mode:384,flag:"wx"});const i=r.openSync(n,"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,o)}finally{try{r.unlinkSync(n)}catch{}}}function U(o={}){const t=o.root??d("runtime","updater"),n=e.join(t,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448);const i=o.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),m=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(m)){const c=`${s}.tmp-${process.pid}-${p()}`;try{r.writeFileSync(c,m,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(o.now??new Date).toISOString()};return f(e.join(t,"current.json"),a),f(e.join(t,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(o=d("runtime","updater")){try{const t=JSON.parse(r.readFileSync(e.join(o,"current.json"),"utf8"));if(t.protocolVersion!==l||!t.workerPath)return null;const n=r.lstatSync(t.workerPath);return n.isSymbolicLink()||!n.isFile()?null:t}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
@@ -0,0 +1,28 @@
1
+ import { spawn, type ChildProcess } from 'node:child_process';
2
+ import { ensureUpdaterInstalled } from './updater-bootstrap.js';
3
+ import { type UpdaterJob } from './updater-protocol.js';
4
+ export declare function createUpdaterJob(input: {
5
+ fromVersion: string;
6
+ targetVersion: string;
7
+ oldDaemonPid: number | null;
8
+ npmPrefix?: string;
9
+ npmExecutable?: string;
10
+ nodeExecutable?: string;
11
+ root?: string;
12
+ now?: Date;
13
+ jobId?: string;
14
+ operation?: 'upgrade' | 'rollback';
15
+ isolatedDaemonStart?: boolean;
16
+ }): UpdaterJob;
17
+ export declare function writeUpdaterJob(job: UpdaterJob, root?: string): string;
18
+ export declare function launchUpdater(input: Parameters<typeof createUpdaterJob>[0], deps?: {
19
+ root?: string;
20
+ spawn?: typeof spawn;
21
+ readiness?: ReturnType<typeof ensureUpdaterInstalled>;
22
+ now?: () => number;
23
+ isAlive?: (pid: number) => boolean;
24
+ }): {
25
+ job: UpdaterJob;
26
+ process: ChildProcess;
27
+ };
28
+ export declare function markUpdaterHealthy(jobId: string, effectiveVersion: string, root?: string): boolean;
@@ -0,0 +1,3 @@
1
+ import e from"node:fs";import l from"node:path";import{randomUUID as f}from"node:crypto";import{spawn as j}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as x,resolveNpmGlobalPrefixFromPackageJson as I}from"./engine.js";import{ensureUpdaterInstalled as P}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(r,n){e.mkdirSync(l.dirname(r),{recursive:!0,mode:448});const o=`${r}.tmp-${process.pid}-${f()}`;try{e.writeFileSync(o,`${JSON.stringify(n,null,2)}
2
+ `,{mode:384,flag:"wx"});const c=e.openSync(o,"r");try{e.fsyncSync(c)}finally{e.closeSync(c)}if(e.renameSync(o,r),process.platform!=="win32"){const t=e.openSync(l.dirname(r),"r");try{e.fsyncSync(t)}finally{e.closeSync(t)}}}finally{try{e.unlinkSync(o)}catch{}}}function O(r){const n=r.root??p("runtime","updater"),o=x(),c=r.npmPrefix??(o?I(o):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=r.jobId??f();return{protocolVersion:S,jobId:t,fromVersion:r.fromVersion,targetVersion:r.targetVersion,npmPrefix:c,npmExecutable:r.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:r.nodeExecutable??process.execPath,packageName:"shennian",operation:r.operation??"upgrade",oldDaemonPid:r.oldDaemonPid,platform:process.platform,createdAt:(r.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:l.join(n,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...r.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function E(r,n=p("runtime","updater")){const o=l.join(n,"jobs");e.mkdirSync(o,{recursive:!0,mode:448});const c=l.join(o,`${r.jobId}.json`),t=`${c}.tmp-${process.pid}-${f()}`;try{e.writeFileSync(t,`${JSON.stringify(r,null,2)}
3
+ `,{mode:384,flag:"wx"});const a=e.openSync(t,"r");try{e.fsyncSync(a)}finally{e.closeSync(a)}if(e.renameSync(t,c),process.platform!=="win32"){const s=e.openSync(o,"r");try{e.fsyncSync(s)}finally{e.closeSync(s)}}return c}finally{try{e.unlinkSync(t)}catch{}}}function U(r,n={}){const o=n.root??r.root??p("runtime","updater"),c=n.readiness??P({root:o}),t=O({...r,root:o}),a=E(t,o),s=l.join(o,"updater.lock");e.mkdirSync(o,{recursive:!0,mode:448});let i=null,u=!1;const w=()=>{if(u){try{JSON.parse(e.readFileSync(s,"utf8")).jobId===t.jobId&&e.unlinkSync(s)}catch{}u=!1}};try{try{i=e.openSync(s,"wx",384),u=!0}catch(m){if(m.code!=="EEXIST")throw m;let h=!1;try{const y=JSON.parse(e.readFileSync(s,"utf8")),k=n.isAlive??(b=>{try{return process.kill(b,0),!0}catch{return!1}});h=!!(y.pid&&k(y.pid)&&(n.now??Date.now)()-Number(y.createdAt)<30*6e4)}catch{}if(h)throw new Error("upgrade_locked");e.rmSync(s,{force:!0}),i=e.openSync(s,"wx",384),u=!0}e.writeFileSync(i,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(n.now??Date.now)()})),e.closeSync(i),i=null;const d=(n.spawn??j)(t.nodeExecutable,[c.workerPath,a],{detached:!0,stdio:"ignore",windowsHide:!0});return d.once?.("error",m=>{g(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:m instanceof Error?m.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),w()}),d.unref(),{job:t,process:d}}catch(d){i!==null&&e.closeSync(i),w();try{e.unlinkSync(a)}catch{}throw d}}function v(r,n,o=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(r))return!1;const c=l.join(o,"results",`${r}.json`);try{const t=e.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const a=JSON.parse(e.readFileSync(c,"utf8"));return a.protocolVersion!==S||a.jobId!==r||a.state!=="starting"||a.targetVersion!==n?!1:(g(c,{...a,state:a.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:n,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{O as createUpdaterJob,U as launchUpdater,v as markUpdaterHealthy,E as writeUpdaterJob};
@@ -0,0 +1,37 @@
1
+ export declare const UPDATER_PROTOCOL_VERSION: 1;
2
+ export type UpdaterJob = {
3
+ protocolVersion: typeof UPDATER_PROTOCOL_VERSION;
4
+ jobId: string;
5
+ fromVersion: string;
6
+ targetVersion: string;
7
+ npmPrefix: string;
8
+ npmExecutable: string;
9
+ nodeExecutable: string;
10
+ packageName: 'shennian';
11
+ operation: 'upgrade' | 'rollback';
12
+ oldDaemonPid: number | null;
13
+ platform: NodeJS.Platform;
14
+ createdAt: string;
15
+ daemonExitTimeoutMs: number;
16
+ commandTimeoutMs: number;
17
+ resultPath: string;
18
+ attemptPath: string;
19
+ /** Release-smoke only: bypass installed platform service and use exact-bin fallback. */
20
+ isolatedDaemonStart?: boolean;
21
+ };
22
+ export type UpdaterResultState = 'waiting_for_daemon' | 'installing' | 'verifying' | 'starting' | 'healthy' | 'rolled_back' | 'failed';
23
+ export type UpdaterErrorCode = 'invalid_job' | 'job_expired' | 'upgrade_locked' | 'daemon_exit_timeout' | 'npm_install_failed' | 'verification_failed' | 'rollback_failed' | 'daemon_start_failed' | 'internal_error';
24
+ export type UpdaterResult = {
25
+ protocolVersion: typeof UPDATER_PROTOCOL_VERSION;
26
+ jobId: string;
27
+ state: UpdaterResultState;
28
+ fromVersion: string;
29
+ targetVersion: string;
30
+ operation?: 'upgrade' | 'rollback';
31
+ effectiveVersion?: string;
32
+ errorCode?: UpdaterErrorCode;
33
+ errorSummary?: string;
34
+ updatedAt: string;
35
+ };
36
+ export declare function parseUpdaterJob(value: unknown): UpdaterJob;
37
+ export declare function sanitizeUpdaterError(error: unknown): string;
@@ -0,0 +1 @@
1
+ const p=1,m=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;function u(t){if(!t||typeof t!="object"||Array.isArray(t))throw new Error("invalid_job");const e=t,r=l=>{const i=e[l];if(typeof i!="string"||!i.trim())throw new Error("invalid_job");return i};if(e.protocolVersion!==1)throw new Error("invalid_job");const s=r("fromVersion"),c=r("targetVersion");if(!m.test(s)||!m.test(c))throw new Error("invalid_job");const d=r("createdAt");if(!Number.isFinite(Date.parse(d)))throw new Error("invalid_job");if(e.packageName!=="shennian")throw new Error("invalid_job");const o=e.oldDaemonPid==null?null:Number(e.oldDaemonPid);if(o!=null&&(!Number.isSafeInteger(o)||o<=0))throw new Error("invalid_job");const n=Number(e.daemonExitTimeoutMs),a=Number(e.commandTimeoutMs);if(!Number.isSafeInteger(n)||n<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:r("jobId"),fromVersion:s,targetVersion:c,npmPrefix:r("npmPrefix"),npmExecutable:r("npmExecutable"),nodeExecutable:r("nodeExecutable"),packageName:"shennian",operation:e.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:o,platform:r("platform"),createdAt:d,daemonExitTimeoutMs:n,commandTimeoutMs:a,resultPath:r("resultPath"),attemptPath:r("attemptPath"),isolatedDaemonStart:e.isolatedDaemonStart===!0}}function b(t){return(t instanceof Error?t.message:String(t)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}export{p as UPDATER_PROTOCOL_VERSION,u as parseUpdaterJob,b as sanitizeUpdaterError};
@@ -0,0 +1,11 @@
1
+ import { execFile as execFileCallback } from 'node:child_process';
2
+ declare const execFile: typeof execFileCallback.__promisify__;
3
+ type WorkerDeps = {
4
+ now?: () => number;
5
+ isAlive?: (pid: number) => boolean;
6
+ kill?: (pid: number, signal: NodeJS.Signals) => void;
7
+ sleep?: (ms: number) => Promise<void>;
8
+ execFile?: typeof execFile;
9
+ };
10
+ export declare function runUpdaterJob(jobPath: string, deps?: WorkerDeps): Promise<void>;
11
+ export {};
@@ -0,0 +1,2 @@
1
+ import a from"node:fs";import b from"node:os";import o from"node:path";import{execFile as k}from"node:child_process";import{promisify as D}from"node:util";import{fileURLToPath as P}from"node:url";const S=D(k),_=1,V="https://registry.npmjs.org",E=30*6e4;function p(e){return(e instanceof Error?e.message:String(e)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}function v(e,r){a.mkdirSync(o.dirname(e),{recursive:!0,mode:448});const t=`${e}.tmp-${process.pid}-${Date.now()}`;try{a.writeFileSync(t,`${JSON.stringify(r,null,2)}
2
+ `,{mode:384,flag:"wx"});const n=a.openSync(t,"r");try{a.fsyncSync(n)}finally{a.closeSync(n)}a.renameSync(t,e)}finally{try{a.unlinkSync(t)}catch{}}}function A(e){const r=a.lstatSync(e);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const t=JSON.parse(a.readFileSync(e,"utf8")),n=f=>{if(typeof t[f]!="string"||!String(t[f]).trim())throw new Error("invalid_job");return String(t[f])},c=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),i=n("targetVersion");if(t.protocolVersion!==_||t.packageName!=="shennian")throw new Error("invalid_job");if(!c.test(l)||!c.test(i))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const d=t.oldDaemonPid==null?null:Number(t.oldDaemonPid),w=Number(t.daemonExitTimeoutMs),s=Number(t.commandTimeoutMs);if(d!=null&&(!Number.isSafeInteger(d)||d<1))throw new Error("invalid_job");if(!Number.isSafeInteger(w)||w<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(s)||s<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:i,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:t.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:d,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:w,commandTimeoutMs:s,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:t.isolatedDaemonStart===!0}}function N(e,r){return r==="win32"?o.win32.join(e,"node_modules","shennian"):o.posix.join(e,"lib","node_modules","shennian")}function h(e){try{return process.kill(e,0),!0}catch{return!1}}async function F(e,r){if(!e.oldDaemonPid||!(r.isAlive??h)(e.oldDaemonPid))return;const t=r.kill??((l,i)=>process.kill(l,i)),n=r.sleep??(l=>new Promise(i=>setTimeout(i,l)));t(e.oldDaemonPid,"SIGTERM");const c=(r.now??Date.now)()+e.daemonExitTimeoutMs;for(;(r.now??Date.now)()<c;){if(!(r.isAlive??h)(e.oldDaemonPid))return;await n(100)}if(r.kill)t(e.oldDaemonPid,"SIGKILL");else if(e.platform==="win32")try{await(r.execFile??S)("taskkill",["/PID",String(e.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-e.oldDaemonPid,"SIGKILL")}catch{try{process.kill(e.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??h)(e.oldDaemonPid))throw new Error("daemon_exit_timeout")}function u(e,r=e.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:e.platform==="win32"}}async function x(e,r,t){const n=["install","-g",`shennian@${r}`,"--prefix",e.npmPrefix];try{await t(e.npmExecutable,n,u(e))}catch{await t(e.npmExecutable,[...n,"--registry",V],u(e))}}async function g(e,r,t){const n=N(e.npmPrefix,e.platform),c=o.join(n,"dist","bin","shennian.js"),{stdout:l}=await t(e.nodeExecutable,[c,"--version"],u(e,1e4));if(l.trim()!==r)throw new Error("verification_failed");return await t(e.npmExecutable,["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0","--prefix",e.npmPrefix],u(e,3e4)),c}async function y(e,r,t){try{if(!e.isolatedDaemonStart&&e.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():b.userInfo().uid;await t("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],u(e,15e3));return}if(!e.isolatedDaemonStart&&e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],u(e,15e3));return}if(!e.isolatedDaemonStart&&e.platform==="win32"){await t("schtasks",["/Run","/TN","ShennianAgent"],u(e,15e3));return}}catch{}await t(e.nodeExecutable,[r,"start","--json"],u(e,3e4))}function O(e){return(r,t={})=>v(e.resultPath,{protocolVersion:_,jobId:e.jobId,state:r,fromVersion:e.fromVersion,targetVersion:e.targetVersion,operation:e.operation,...t,updatedAt:new Date().toISOString()})}function T(e,r){const t=o.dirname(o.dirname(e)),n=o.join(t,"updater.lock");a.mkdirSync(t,{recursive:!0,mode:448});try{const c=a.openSync(n,"wx",384);a.writeFileSync(c,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(c)}catch(c){if(c.code!=="EEXIST")throw c;try{const i=JSON.parse(a.readFileSync(n,"utf8"));if(i.jobId!==r.jobId&&i.pid&&h(i.pid)&&Date.now()-Number(i.createdAt)<E)throw new Error("upgrade_locked")}catch(i){if(i instanceof Error&&i.message==="upgrade_locked")throw i}a.rmSync(n,{force:!0});const l=a.openSync(n,"wx",384);a.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(l)}return()=>{try{a.unlinkSync(n)}catch{}}}async function L(e,r={}){let t;try{t=A(e)}catch{throw new Error("invalid_job")}if((r.now??Date.now)()-Date.parse(t.createdAt)>E)throw new Error("job_expired");const n=o.dirname(o.dirname(o.resolve(e)));if(o.basename(o.dirname(o.resolve(e)))!=="jobs"||o.basename(o.resolve(e))!==`${t.jobId}.json`)throw new Error("invalid_job");if(o.dirname(o.resolve(t.resultPath))!==o.join(n,"results"))throw new Error("invalid_job");if(o.basename(o.resolve(t.resultPath))!==`${t.jobId}.json`)throw new Error("invalid_job");const c=o.dirname(o.dirname(n));if(o.resolve(t.attemptPath)!==o.join(c,"upgrade-attempt.json"))throw new Error("invalid_job");const l=T(e,t),i=O(t),d=r.execFile??S;let w=!1;try{let s=null;try{s=JSON.parse(a.readFileSync(t.resultPath,"utf8"))}catch{}if(s?.jobId===t.jobId&&s.targetVersion===t.targetVersion&&["healthy","rolled_back","failed"].includes(s.state??""))return;if(s?.jobId===t.jobId&&s.state==="starting"){const m=await g(t,t.targetVersion,d);await y(t,m,d);return}i("waiting_for_daemon"),await(r.sleep??(m=>new Promise(I=>setTimeout(I,m))))(250),await F(t,r),i("installing"),w=!0;try{await x(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"npm_install_failed"})}i("verifying");let f;try{f=await g(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"verification_failed"})}v(t.attemptPath,{from:t.fromVersion,to:t.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:t.jobId,operation:t.operation}),i("starting"),await y(t,f,d)}catch(s){const f=s.code??(s instanceof Error?s.message:"internal_error");if(!w){i("failed",{errorCode:f,errorSummary:p(s)});return}try{await x(t,t.fromVersion,d);const m=await g(t,t.fromVersion,d);await y(t,m,d),i("rolled_back",{effectiveVersion:t.fromVersion,errorCode:f,errorSummary:p(s)})}catch(m){throw i("failed",{errorCode:"rollback_failed",errorSummary:p(m)}),m}}finally{l()}}const $=process.argv[1]?o.resolve(process.argv[1]):"",j=o.resolve(P(import.meta.url));if($===j){const e=process.argv[2];e?L(e).catch(()=>{process.exitCode=1}):process.exitCode=2}export{L as runUpdaterJob};
@@ -49,6 +49,20 @@ export type AgentEventPayload = {
49
49
  runId: string;
50
50
  seq: number;
51
51
  message: string;
52
+ errorCode?: 'acp_auth_required';
53
+ } | {
54
+ state: 'interrupted_by_upgrade';
55
+ sessionId?: string;
56
+ runId: string;
57
+ seq: number;
58
+ reason: string;
59
+ } | {
60
+ state: 'recovery_failed';
61
+ sessionId?: string;
62
+ runId: string;
63
+ seq: number;
64
+ reason: string;
65
+ message?: string;
52
66
  } | {
53
67
  state: 'approval-pending';
54
68
  sessionId?: string;
@@ -60,12 +74,15 @@ export type AgentEventPayload = {
60
74
  source?: string;
61
75
  actionHint?: string;
62
76
  };
77
+ permission?: AcpPermissionPrompt;
63
78
  } | {
64
79
  state: 'tool-call';
65
80
  sessionId?: string;
66
81
  runId: string;
67
82
  seq: number;
68
83
  name: string;
84
+ toolCallId?: string;
85
+ toolStatus?: 'running' | 'completed' | 'failed';
69
86
  args?: Record<string, unknown>;
70
87
  detailRef?: ToolDetailRef;
71
88
  durationMs?: number;
@@ -75,6 +92,8 @@ export type AgentEventPayload = {
75
92
  runId: string;
76
93
  seq: number;
77
94
  name: string;
95
+ toolCallId?: string;
96
+ toolStatus?: 'running' | 'completed' | 'failed';
78
97
  result?: string;
79
98
  detailRef?: ToolDetailRef;
80
99
  durationMs?: number;
@@ -92,3 +111,15 @@ export type AgentEventPayload = {
92
111
  sessionId: string;
93
112
  total: number;
94
113
  };
114
+ export type AcpPermissionPrompt = {
115
+ id: string;
116
+ sessionId?: string;
117
+ agentSessionId?: string;
118
+ runId?: string;
119
+ toolCallId: string;
120
+ options: Array<{
121
+ id: string;
122
+ name: string;
123
+ kind: 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
124
+ }>;
125
+ };
@@ -15,3 +15,4 @@ export * from './project.ts';
15
15
  export * from './room.ts';
16
16
  export * from './agent-capabilities.ts';
17
17
  export * from './agent-workspace.ts';
18
+ export * from './relay-connectivity.ts';
@@ -15,3 +15,4 @@ export * from "./project.js";
15
15
  export * from "./room.js";
16
16
  export * from "./agent-capabilities.js";
17
17
  export * from "./agent-workspace.js";
18
+ export * from "./relay-connectivity.js";
@@ -22,6 +22,8 @@ export type Machine = {
22
22
  sessionSyncDataEpoch?: import('./session-sync.ts').SessionSyncDataEpoch;
23
23
  /** Full capability set required by the hard-cutover personal Session protocol. */
24
24
  sessionSyncCapabilities?: import('./session-sync.ts').SessionSyncCapability[];
25
+ /** External updater protocol advertised by the live daemon; absent means legacy engine. */
26
+ updaterProtocolVersion?: number;
25
27
  /** Auto-upgrade policy configured on this machine */
26
28
  autoUpgrade?: AutoUpgradePolicy;
27
29
  /** Set only after the daemon durably disables remote access on the machine. */
@@ -32,6 +34,10 @@ export type Machine = {
32
34
  permission?: MachinePermission;
33
35
  /** Active redeemed grants created by the owner for this machine. */
34
36
  sharedCount?: number;
37
+ /** Nicknames used by the owner-only machine detail summary. */
38
+ sharedWith?: string[];
39
+ /** Unredeemed, active invitations created by this owner. */
40
+ pendingShareCount?: number;
35
41
  /** Agents on this machine that have a non-missing provider config. */
36
42
  configuredAgentCount?: number;
37
43
  /** Present for machines shared by another user. */
@@ -79,6 +85,25 @@ export type UpgradeEventPayload = {
79
85
  state: 'installing';
80
86
  machineId: string;
81
87
  version: string;
88
+ } | {
89
+ state: 'starting';
90
+ machineId: string;
91
+ from: string;
92
+ to: string;
93
+ jobId?: string;
94
+ } | {
95
+ state: 'recovering';
96
+ machineId: string;
97
+ from: string;
98
+ to: string;
99
+ jobId?: string;
100
+ activeRunCount?: number;
101
+ } | {
102
+ state: 'healthy';
103
+ machineId: string;
104
+ from: string;
105
+ to: string;
106
+ jobId?: string;
82
107
  } | {
83
108
  state: 'restarting';
84
109
  machineId: string;
@@ -98,12 +123,35 @@ export type UpgradeEventPayload = {
98
123
  machineId: string;
99
124
  to: string;
100
125
  reason: string;
126
+ } | {
127
+ state: 'rolled_back';
128
+ machineId: string;
129
+ to: string;
130
+ reason: string;
131
+ jobId?: string;
132
+ } | {
133
+ state: 'interrupted_by_upgrade';
134
+ machineId: string;
135
+ runCount: number;
136
+ jobId?: string;
137
+ } | {
138
+ state: 'recovery_failed';
139
+ machineId: string;
140
+ runCount: number;
141
+ error?: string;
142
+ jobId?: string;
101
143
  };
102
144
  export type MachineGrant = {
103
145
  id: string;
104
146
  granteeId: string | null;
105
147
  granteeName: string | null;
106
- granteePhone: string | null;
148
+ /** @deprecated Kept optional for old-server compatibility; current UI never displays it. */
149
+ granteePhone?: string | null;
150
+ granteeAvatar?: {
151
+ version: string;
152
+ thumbUrl: string;
153
+ originalUrl: string;
154
+ } | null;
107
155
  permission: MachinePermission;
108
156
  expiresAt: string | null;
109
157
  revokedAt: string | null;
@@ -28,6 +28,7 @@ export type ApprovalPendingPayload = {
28
28
  content: string;
29
29
  source?: string;
30
30
  actionHint?: string;
31
+ permission?: import('./events.ts').AcpPermissionPrompt;
31
32
  };
32
33
  export type ChannelDeliveryStatusPayload = {
33
34
  v: 1;
@@ -180,7 +180,10 @@ export function buildApprovalPendingPayload(input) {
180
180
  title: input?.title?.trim() || '需要用户确认',
181
181
  content: input?.content?.trim() || 'Codex 正在等待用户确认后继续。',
182
182
  source: input?.source?.trim() || 'codex',
183
- actionHint: input?.actionHint?.trim() || '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
183
+ actionHint: input?.permission
184
+ ? input?.actionHint?.trim()
185
+ : input?.actionHint?.trim() || '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
186
+ ...(input?.permission ? { permission: input.permission } : {}),
184
187
  });
185
188
  }
186
189
  export function isSystemEnvironmentContextPayload(payload) {
@@ -1 +1 @@
1
- export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.sync' | 'session.history.page' | 'session.history.close' | 'session.body.replica.subscribe' | 'session.body.replica.unsubscribe' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'machine.remote-access.pause' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.workspace.validate' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
1
+ export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.sync' | 'session.history.page' | 'session.history.close' | 'session.body.replica.subscribe' | 'session.body.replica.unsubscribe' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'machine.remote-access.pause' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.workspace.validate' | 'agent.capabilities.list' | 'agent.permission.respond' | 'agent.auth.start' | 'agent.auth.logout' | 'agent.session-options.get' | 'agent.external-sessions.list' | 'agent.external-sessions.import' | 'agent.external-sessions.close' | 'agent.external-sessions.delete' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
@@ -0,0 +1,30 @@
1
+ export type RelayConnectivityPhase = 'connecting' | 'connected' | 'recovering' | 'blocked' | 'offline';
2
+ export type RelayConnectivityReason = 'initial_connect' | 'network_offline' | 'transient_transport' | 'server_unavailable' | 'auth_required' | 'version_unsupported' | 'permission_revoked' | 'plan_required' | 'machine_unavailable' | 'unknown';
3
+ export type RelayConnectivitySnapshot = {
4
+ phase: RelayConnectivityPhase;
5
+ reason: RelayConnectivityReason;
6
+ closeCode: number | null;
7
+ closeReason: string | null;
8
+ retryable: boolean;
9
+ retryAttempt: number;
10
+ nextRetryAt: number | null;
11
+ disconnectedAt: number | null;
12
+ lastConnectedAt: number | null;
13
+ connectionId: string | null;
14
+ };
15
+ export type RelayConnectivityEventName = 'relay_connect_started' | 'relay_connected' | 'relay_disconnected' | 'relay_retry_scheduled' | 'relay_watchdog_rebuild' | 'relay_manual_retry' | 'relay_recovery_succeeded' | 'relay_recovery_failed';
16
+ export type RelayConnectivityEvent = {
17
+ name: RelayConnectivityEventName;
18
+ snapshot: RelayConnectivitySnapshot;
19
+ at: number;
20
+ durationMs?: number;
21
+ };
22
+ export type RelayCloseClassification = {
23
+ reason: RelayConnectivityReason;
24
+ retryable: boolean;
25
+ };
26
+ /**
27
+ * The server owns close-code meaning. Keeping this map in Wire prevents App,
28
+ * Shared and Server from drifting into different user-facing explanations.
29
+ */
30
+ export declare function classifyRelayClose(code: number, closeReason?: string): RelayCloseClassification;
@@ -0,0 +1,29 @@
1
+ // @arch docs/features/app-relay-reconnect-and-connectivity-feedback-todo.md#五、目标连接模型
2
+ // @test packages/shared/src/__tests__/relay.test.ts
3
+ /**
4
+ * The server owns close-code meaning. Keeping this map in Wire prevents App,
5
+ * Shared and Server from drifting into different user-facing explanations.
6
+ */
7
+ export function classifyRelayClose(code, closeReason = '') {
8
+ if (code === 4001 || code === 4009)
9
+ return { reason: 'auth_required', retryable: false };
10
+ if (code === 4003 || code === 4403 || code === 4411) {
11
+ return { reason: 'permission_revoked', retryable: false };
12
+ }
13
+ if (code === 4402)
14
+ return { reason: 'plan_required', retryable: false };
15
+ if (code === 4406)
16
+ return { reason: 'version_unsupported', retryable: false };
17
+ if (code === 4004)
18
+ return { reason: 'machine_unavailable', retryable: true };
19
+ if (code === 4008 || code === 1012 || code === 1013) {
20
+ return { reason: 'server_unavailable', retryable: true };
21
+ }
22
+ if (code === 0 || code === 1006 || code === 1011) {
23
+ return { reason: 'transient_transport', retryable: true };
24
+ }
25
+ if (/upgrade|required|protocol|epoch|capabilit/i.test(closeReason)) {
26
+ return { reason: 'version_unsupported', retryable: false };
27
+ }
28
+ return { reason: 'unknown', retryable: true };
29
+ }
@@ -21,8 +21,8 @@ export declare const SESSION_SYNC_REQUIRED_CAPABILITIES: readonly ["session_inde
21
21
  * epoch-wide minimum gate. This lets the App roll out background replication
22
22
  * while old daemons keep serving the compatible visible-session snapshot path.
23
23
  */
24
- export declare const SESSION_SYNC_ADDITIVE_CAPABILITIES: readonly ["session_body_replica_v1", "session_body_replica_stream_v1"];
25
- export declare const SESSION_SYNC_ADVERTISED_CAPABILITIES: readonly ["session_index_v1", "session_body_revision_v1", "session_history_subscription_v1", "local_canonical_v1", "session_title_authority_v1", "session_title_seed_v1", "session_tool_detail_v1", "voice_session_body_relay_v1", "native_source_tombstone_v1", "session_durable_send_v1", "daemon_installation_identity_v1", "session_user_boundary_v1", "session_terminal_outbox_v1", "session_list_preview_v1", "session_attention_v1", "session_data_epoch_v2", "session_structured_rejection_v2", "machine_personal_authority_v2", "session_body_replica_v1", "session_body_replica_stream_v1"];
24
+ export declare const SESSION_SYNC_ADDITIVE_CAPABILITIES: readonly ["session_body_replica_v1", "session_body_replica_stream_v1", "active_run_recovery_v1", "external_updater_v1"];
25
+ export declare const SESSION_SYNC_ADVERTISED_CAPABILITIES: readonly ["session_index_v1", "session_body_revision_v1", "session_history_subscription_v1", "local_canonical_v1", "session_title_authority_v1", "session_title_seed_v1", "session_tool_detail_v1", "voice_session_body_relay_v1", "native_source_tombstone_v1", "session_durable_send_v1", "daemon_installation_identity_v1", "session_user_boundary_v1", "session_terminal_outbox_v1", "session_list_preview_v1", "session_attention_v1", "session_data_epoch_v2", "session_structured_rejection_v2", "machine_personal_authority_v2", "session_body_replica_v1", "session_body_replica_stream_v1", "active_run_recovery_v1", "external_updater_v1"];
26
26
  export declare const SESSION_SYNC_EVENT_NAMES: readonly ["session.index.updated", "session.body.delta", "session.body.replica.delta", "session.body.replica.error"];
27
27
  export declare const SESSION_HISTORY_LIMITS: {
28
28
  readonly defaultSnapshotMessages: 100;
@@ -38,6 +38,8 @@ export const SESSION_SYNC_REQUIRED_CAPABILITIES = [
38
38
  export const SESSION_SYNC_ADDITIVE_CAPABILITIES = [
39
39
  'session_body_replica_v1',
40
40
  'session_body_replica_stream_v1',
41
+ 'active_run_recovery_v1',
42
+ 'external_updater_v1',
41
43
  ];
42
44
  export const SESSION_SYNC_ADVERTISED_CAPABILITIES = [
43
45
  ...SESSION_SYNC_REQUIRED_CAPABILITIES,