shennian 0.4.10 → 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 (125) hide show
  1. package/dist/publish-build-manifest.json +185 -55
  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/room/activation-service.d.ts +2 -0
  76. package/dist/src/room/activation-service.js +5 -5
  77. package/dist/src/session/handlers/agents.d.ts +1 -0
  78. package/dist/src/session/handlers/agents.js +1 -1
  79. package/dist/src/session/handlers/chat.d.ts +3 -0
  80. package/dist/src/session/handlers/chat.js +2 -2
  81. package/dist/src/session/manager.d.ts +23 -2
  82. package/dist/src/session/manager.js +3 -1
  83. package/dist/src/session/queue.d.ts +7 -1
  84. package/dist/src/session/queue.js +8 -8
  85. package/dist/src/session/run-recovery/barrier.d.ts +12 -0
  86. package/dist/src/session/run-recovery/barrier.js +1 -0
  87. package/dist/src/session/run-recovery/coordinator.d.ts +32 -0
  88. package/dist/src/session/run-recovery/coordinator.js +1 -0
  89. package/dist/src/session/run-recovery/journal.d.ts +30 -0
  90. package/dist/src/session/run-recovery/journal.js +2 -0
  91. package/dist/src/session/run-recovery/schema.d.ts +43 -0
  92. package/dist/src/session/run-recovery/schema.js +1 -0
  93. package/dist/src/session/types.d.ts +10 -0
  94. package/dist/src/upgrade/engine.d.ts +6 -5
  95. package/dist/src/upgrade/engine.js +2 -2
  96. package/dist/src/upgrade/scheduler.d.ts +14 -0
  97. package/dist/src/upgrade/scheduler.js +1 -1
  98. package/dist/src/upgrade/updater-bootstrap.d.ts +12 -0
  99. package/dist/src/upgrade/updater-bootstrap.js +2 -0
  100. package/dist/src/upgrade/updater-launcher.d.ts +28 -0
  101. package/dist/src/upgrade/updater-launcher.js +3 -0
  102. package/dist/src/upgrade/updater-protocol.d.ts +37 -0
  103. package/dist/src/upgrade/updater-protocol.js +1 -0
  104. package/dist/src/upgrade/updater-worker.d.ts +11 -0
  105. package/dist/src/upgrade/updater-worker.js +2 -0
  106. package/node_modules/@shennian/wire/dist/events.d.ts +31 -0
  107. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  108. package/node_modules/@shennian/wire/dist/index.js +1 -0
  109. package/node_modules/@shennian/wire/dist/machine.d.ts +49 -1
  110. package/node_modules/@shennian/wire/dist/message-payload.d.ts +1 -0
  111. package/node_modules/@shennian/wire/dist/message-payload.js +4 -1
  112. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  113. package/node_modules/@shennian/wire/dist/relay-connectivity.d.ts +30 -0
  114. package/node_modules/@shennian/wire/dist/relay-connectivity.js +29 -0
  115. package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -2
  116. package/node_modules/@shennian/wire/dist/session-sync.js +2 -0
  117. package/node_modules/@shennian/wire/dist/session.d.ts +86 -1
  118. package/node_modules/@shennian/wire/dist/session.js +5 -1
  119. package/package.json +5 -3
  120. package/dist/native/macos-background-client/Shennian.app/Contents/CodeResources +0 -0
  121. package/dist/native/macos-background-client/Shennian.app/Contents/Info.plist +0 -17
  122. package/dist/native/macos-background-client/Shennian.app/Contents/MacOS/ShennianClientLauncher +0 -0
  123. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -2
  124. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +0 -2
  125. package/dist/native/macos-background-client/Shennian.app/Contents/_CodeSignature/CodeResources +0 -153
@@ -0,0 +1,12 @@
1
+ export declare class RunRecoveryBarrier {
2
+ private recovering;
3
+ private releases;
4
+ private dispatchFrozen;
5
+ freezeDispatch(): void;
6
+ unfreezeDispatch(): void;
7
+ isDispatchFrozen(): boolean;
8
+ begin(sessionId: string): void;
9
+ end(sessionId: string): void;
10
+ isRecovering(sessionId: string): boolean;
11
+ wait(sessionId: string): Promise<void>;
12
+ }
@@ -0,0 +1 @@
1
+ class a{recovering=new Map;releases=new Map;dispatchFrozen=!1;freezeDispatch(){this.dispatchFrozen=!0}unfreezeDispatch(){this.dispatchFrozen=!1}isDispatchFrozen(){return this.dispatchFrozen}begin(e){if(this.recovering.has(e))return;let r;const t=new Promise(i=>{r=i});this.recovering.set(e,t),this.releases.set(e,r)}end(e){this.releases.get(e)?.(),this.releases.delete(e),this.recovering.delete(e)}isRecovering(e){return this.recovering.has(e)}async wait(e){await this.recovering.get(e)}}export{a as RunRecoveryBarrier};
@@ -0,0 +1,32 @@
1
+ import type { AgentRecoveryResult } from '../../agents/adapter.js';
2
+ import type { ActiveRunJournal } from './journal.js';
3
+ import type { RunRecoveryBarrier } from './barrier.js';
4
+ import type { RunJournalRecord } from './schema.js';
5
+ export type RecoveryTerminal = Extract<AgentRecoveryResult, {
6
+ result: 'interrupted_by_upgrade' | 'recovery_failed';
7
+ }>;
8
+ export declare class RunRecoveryCoordinator {
9
+ private readonly deps;
10
+ private pendingBySession;
11
+ constructor(deps: {
12
+ journal: ActiveRunJournal;
13
+ barrier: RunRecoveryBarrier;
14
+ recover(record: RunJournalRecord, beforeContinueDispatch: () => void): Promise<AgentRecoveryResult>;
15
+ commitTerminal(record: RunJournalRecord, result: RecoveryTerminal): Promise<void>;
16
+ /** True only when the local canonical transaction for this run already committed. */
17
+ isCanonicalTerminal?: (record: RunJournalRecord) => boolean;
18
+ onOutcome?: (outcome: {
19
+ adapterType: string;
20
+ source: RunJournalRecord['source'];
21
+ result: AgentRecoveryResult['result'];
22
+ attempts: number;
23
+ durationMs: number;
24
+ reason?: string;
25
+ }) => void;
26
+ maxAttempts?: number;
27
+ });
28
+ recoverAll(): Promise<void>;
29
+ completeFromProvider(sessionId: string, runId: string): void;
30
+ private recoverOne;
31
+ private finishTerminal;
32
+ }
@@ -0,0 +1 @@
1
+ const d=new Set(["continue_dispatch_outcome_unknown","provider_session_missing","provider_identity_mismatch","authentication_required","permission_context_unavailable","provider_error"]);function l(p){return typeof p=="string"&&d.has(p)}class h{deps;pendingBySession=new Map;constructor(e){this.deps=e}async recoverAll(){const e=this.deps.journal.loadPendingReconciliation().records,t=new Map;for(const s of e){this.deps.barrier.begin(s.sessionId);const i=this.pendingBySession.get(s.sessionId)??new Set;i.add(s.runId),this.pendingBySession.set(s.sessionId,i);const a=t.get(s.sessionId)??[];a.push(s),t.set(s.sessionId,a)}const r=[...t.values()].filter(s=>s.some(i=>i.source!=="manager_worker")),o=[...t.values()].filter(s=>s.every(i=>i.source==="manager_worker")),n=s=>Promise.all(s.map(async i=>{for(const a of i)await this.recoverOne(a)}));await n(r),await n(o)}completeFromProvider(e,t){this.deps.journal.remove(e,t);const r=this.pendingBySession.get(e);r?.delete(t),(!r||r.size===0)&&(this.pendingBySession.delete(e),this.deps.barrier.end(e))}async recoverOne(e){if(this.deps.isCanonicalTerminal?.(e)){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="aborted"){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="interrupted_by_upgrade"||e.phase==="recovery_failed"){const s=e.phase==="interrupted_by_upgrade"?{result:"interrupted_by_upgrade",reason:e.recoveryReason==="missing_agent_session"?"missing_agent_session":"resume_unsupported"}:{result:"recovery_failed",reason:l(e.recoveryReason)?e.recoveryReason:"provider_error"};await this.deps.commitTerminal(e,s),this.completeFromProvider(e.sessionId,e.runId);return}const t=e.recoveryAttempts+1,r=Date.now();let o=this.deps.journal.update(e.sessionId,e.runId,{phase:"recovering",recoveryAttempts:t})??e,n;try{n=await this.deps.recover(o,()=>{o=this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"intent"})??o})}catch(s){const i=this.deps.journal.get(e.sessionId,e.runId);if(i?.phase==="interrupted_by_upgrade"||i?.phase==="recovery_failed")throw s;const a=this.deps.maxAttempts??3;if(t<a){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});const u=this.deps.journal.get(e.sessionId,e.runId);if(u)return this.recoverOne(u)}this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:"recovery_failed",attempts:t,durationMs:Date.now()-r,reason:"provider_error"}),await this.finishTerminal(e,{result:"recovery_failed",reason:"provider_error",message:s instanceof Error?s.message.slice(0,500):String(s).slice(0,500)});return}if(this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:n.result,attempts:t,durationMs:Date.now()-r,..."reason"in n?{reason:n.reason}:{}}),n.result==="continued"){this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"accepted",phase:"running"});return}if(n.result==="reattached"||n.result==="resumed"){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});return}if(n.result==="terminal_reconciled"){this.completeFromProvider(e.sessionId,e.runId);return}await this.finishTerminal(e,n)}async finishTerminal(e,t){this.deps.journal.update(e.sessionId,e.runId,{phase:t.result,recoveryReason:t.reason}),await this.deps.commitTerminal(e,t),this.completeFromProvider(e.sessionId,e.runId)}}export{h as RunRecoveryCoordinator};
@@ -0,0 +1,30 @@
1
+ import { type RunJournalRecord } from './schema.js';
2
+ type JournalInput = Omit<RunJournalRecord, 'schemaVersion' | 'lastObservedEventSeq' | 'phase' | 'upgradeJobId' | 'interruptionReason' | 'continueDispatchState' | 'recoveryAttempts' | 'createdAt' | 'updatedAt' | 'recoveryReason'>;
3
+ export type RunJournalLoadResult = {
4
+ records: RunJournalRecord[];
5
+ quarantined: string[];
6
+ };
7
+ export declare class ActiveRunJournal {
8
+ readonly daemonInstallationId: string;
9
+ readonly directory: string;
10
+ readonly quarantineDirectory: string;
11
+ constructor(daemonInstallationId: string, options?: {
12
+ directory?: string;
13
+ });
14
+ begin(input: JournalInput): RunJournalRecord;
15
+ update(sessionId: string, runId: string, patch: Partial<Omit<RunJournalRecord, 'schemaVersion' | 'daemonInstallationId' | 'sessionId' | 'runId' | 'createdAt'>>): RunJournalRecord | null;
16
+ get(sessionId: string, runId: string): RunJournalRecord | null;
17
+ loadRecoverable(): RunJournalLoadResult;
18
+ /** Records that still require either provider recovery or terminal cleanup. */
19
+ loadPendingReconciliation(): RunJournalLoadResult;
20
+ private load;
21
+ markPlannedUpgrade(upgradeJobId: string): number;
22
+ markAborted(sessionId: string, runId: string): void;
23
+ markSessionAborted(sessionId: string): number;
24
+ remove(sessionId: string, runId: string): void;
25
+ sync(): void;
26
+ private recordPath;
27
+ private write;
28
+ private quarantine;
29
+ }
30
+ export {};
@@ -0,0 +1,2 @@
1
+ import n from"node:fs";import s from"node:path";import{createHash as p,randomUUID as u}from"node:crypto";import{resolveShennianPath as S}from"../../config/index.js";import{RUN_JOURNAL_SCHEMA_VERSION as w,isRecoverableJournalPhase as y,parseRunJournalRecord as h}from"./schema.js";function f(a,t){return p("sha256").update(`${a}\0${t}`).digest("hex")+".json"}function c(a){try{const t=n.lstatSync(a);if(t.isSymbolicLink()||!t.isFile())throw new Error("journal target is not a regular file")}catch(t){if(t.code!=="ENOENT")throw t}}function l(a){if(process.platform==="win32")return;const t=n.openSync(a,"r");try{n.fsyncSync(t)}finally{n.closeSync(t)}}function m(a){for(let t=0;t<3;t+=1){n.mkdirSync(a,{recursive:!0,mode:448});try{const r=n.lstatSync(a);if(r.isSymbolicLink()||!r.isDirectory())throw new Error("journal directory is unsafe");process.platform!=="win32"&&n.chmodSync(a,448);return}catch(r){if(r.code!=="ENOENT"||t===2)throw r}}}class N{daemonInstallationId;directory;quarantineDirectory;constructor(t,r={}){this.daemonInstallationId=t,this.directory=r.directory??S("runtime","run-recovery","active"),this.quarantineDirectory=s.join(s.dirname(this.directory),"quarantine"),m(this.directory),m(this.quarantineDirectory)}begin(t){const r=new Date().toISOString(),e={...t,schemaVersion:w,lastObservedEventSeq:-1,phase:"dispatching",upgradeJobId:null,interruptionReason:null,continueDispatchState:"none",recoveryAttempts:0,recoveryReason:null,createdAt:r,updatedAt:r};return this.write(e),e}update(t,r,e){const o=this.get(t,r);if(!o)return null;const i=h({...o,...e,updatedAt:new Date().toISOString()});return this.write(i),i}get(t,r){const e=this.recordPath(t,r);try{c(e);const o=h(JSON.parse(n.readFileSync(e,"utf8")));if(o.sessionId!==t||o.runId!==r)throw new Error("journal identity mismatch");return o}catch(o){return o.code==="ENOENT"||this.quarantine(e),null}}loadRecoverable(){return this.load(t=>y(t.phase))}loadPendingReconciliation(){return this.load(t=>y(t.phase)||t.phase==="interrupted_by_upgrade"||t.phase==="recovery_failed"||t.phase==="aborted")}load(t){const r=[],e=[];for(const o of n.readdirSync(this.directory)){if(!o.endsWith(".json"))continue;const i=s.join(this.directory,o);try{c(i);const d=h(JSON.parse(n.readFileSync(i,"utf8")));if(d.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");if(f(d.sessionId,d.runId)!==o)throw new Error("journal filename identity mismatch");t(d)&&r.push(d)}catch{e.push(this.quarantine(i))}}return r.sort((o,i)=>o.createdAt.localeCompare(i.createdAt)),{records:r,quarantined:e}}markPlannedUpgrade(t){const{records:r}=this.loadRecoverable();for(const e of r)this.update(e.sessionId,e.runId,{upgradeJobId:t,interruptionReason:"planned_upgrade"});return l(this.directory),r.length}markAborted(t,r){this.update(t,r,{phase:"aborted"})}markSessionAborted(t){const r=this.loadRecoverable().records.filter(e=>e.sessionId===t);for(const e of r)this.markAborted(e.sessionId,e.runId);return this.sync(),r.length}remove(t,r){const e=this.recordPath(t,r);c(e);try{n.unlinkSync(e)}catch(o){if(o.code!=="ENOENT")throw o}l(this.directory)}sync(){for(const t of n.readdirSync(this.directory)){if(!t.endsWith(".json"))continue;const r=s.join(this.directory,t);c(r);const e=n.openSync(r,"r");try{n.fsyncSync(e)}finally{n.closeSync(e)}}l(this.directory)}recordPath(t,r){return s.join(this.directory,f(t,r))}write(t){if(t.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");const r=h(t),e=this.recordPath(r.sessionId,r.runId);c(e);const o=`${e}.tmp-${process.pid}-${u()}`;try{n.writeFileSync(o,`${JSON.stringify(r,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&n.chmodSync(o,384);const i=n.openSync(o,"r");try{n.fsyncSync(i)}finally{n.closeSync(i)}n.renameSync(o,e),l(this.directory)}finally{try{n.unlinkSync(o)}catch{}}}quarantine(t){const r=s.join(this.quarantineDirectory,`${s.basename(t)}.${Date.now()}.${u()}.invalid`);try{return c(t),n.renameSync(t,r),process.platform!=="win32"&&n.chmodSync(r,384),l(this.quarantineDirectory),r}catch{return t}}}export{N as ActiveRunJournal};
@@ -0,0 +1,43 @@
1
+ import { type AgentType, type SessionMode } from '@shennian/wire';
2
+ export declare const RUN_JOURNAL_SCHEMA_VERSION: 1;
3
+ export declare const RECOVERY_CONTINUE_TEXT: "Continue.";
4
+ export type RunJournalPhase = 'dispatching' | 'running' | 'waiting_approval' | 'recovering' | 'terminal' | 'interrupted_by_upgrade' | 'recovery_failed' | 'aborted';
5
+ export type RunRecoverySource = 'personal' | 'managed_room' | 'managed_direct' | 'manager_worker';
6
+ export type ContinueDispatchState = 'none' | 'intent' | 'accepted';
7
+ export type RunJournalRecord = {
8
+ schemaVersion: typeof RUN_JOURNAL_SCHEMA_VERSION;
9
+ daemonInstallationId: string;
10
+ sessionId: string;
11
+ runId: string;
12
+ clientMessageId: string;
13
+ agentType: AgentType;
14
+ sessionMode: SessionMode;
15
+ workDir: string;
16
+ agentSessionId: string | null;
17
+ modelId: string | null;
18
+ reasoningEffort: string | null;
19
+ nativeSessionVisibility: 'user' | 'internal';
20
+ providerRunId: string | null;
21
+ lastObservedEventSeq: number;
22
+ phase: RunJournalPhase;
23
+ source: RunRecoverySource;
24
+ parentSessionId: string | null;
25
+ roomContextRegistryKey: string | null;
26
+ activationCauseMessageId: string | null;
27
+ rootCauseMessageId: string | null;
28
+ activationSequence: string | null;
29
+ activationHopCount: number | null;
30
+ managedPolicyVersion?: string | null;
31
+ managedRuntimePolicyHash?: string | null;
32
+ upgradeJobId: string | null;
33
+ interruptionReason: 'daemon_crash' | 'planned_upgrade' | 'os_restart' | null;
34
+ continueDispatchState: ContinueDispatchState;
35
+ recoveryAttempts: number;
36
+ /** Non-sensitive stable reason used to replay a pending terminal commit. */
37
+ recoveryReason?: string | null;
38
+ createdAt: string;
39
+ updatedAt: string;
40
+ };
41
+ /** Strict parsing keeps corrupt or future records out of the recovery path. */
42
+ export declare function parseRunJournalRecord(value: unknown): RunJournalRecord;
43
+ export declare function isRecoverableJournalPhase(phase: RunJournalPhase): boolean;
@@ -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;