@moretea-labs/forge 1.5.0-rc.1 → 1.5.0

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 (259) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +19 -0
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +50 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/templates/helpers/archive-workflow.sh +0 -1
  28. package/assets/templates/helpers/capture-plan.sh +3 -3
  29. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  30. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  31. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  34. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  35. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  36. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  37. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  38. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  39. package/assets/templates/helpers/verify-sprint.sh +7 -7
  40. package/assets/templates/helpers/workstream-sync.sh +1 -1
  41. package/assets/templates/plan.template.md +1 -1
  42. package/assets/workflow-contract.v1.json +6 -3
  43. package/docs/architecture/current/README.md +10 -9
  44. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  45. package/docs/architecture/current/architecture-invariants.md +2 -2
  46. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  47. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  48. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  49. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  50. package/docs/architecture/current/entity-model.md +0 -27
  51. package/docs/architecture/current/failure-recovery.md +0 -24
  52. package/docs/architecture/current/governance.md +1 -1
  53. package/docs/architecture/current/implementation-status.md +1 -1
  54. package/docs/architecture/current/legacy-governance.md +58 -0
  55. package/docs/architecture/current/multi-repository-execution.md +1 -1
  56. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  57. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  58. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  59. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  60. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  61. package/docs/architecture/current/thin-harness-v1.md +9 -11
  62. package/docs/architecture/domains/runtime-harness.md +1 -1
  63. package/docs/forge-autonomous-goal-loop.md +3 -1
  64. package/docs/forge-chatgpt-mcp-setup.md +51 -184
  65. package/docs/images/forge-banner-cn.svg +26 -1
  66. package/docs/images/forge-banner.svg +26 -1
  67. package/docs/operations/features.md +1 -1
  68. package/docs/operations/homebrew.md +3 -3
  69. package/docs/operations/mcp-tool-exposure.md +5 -5
  70. package/docs/operations/platform-support.md +45 -37
  71. package/docs/operations/platform-support.zh-CN.md +40 -38
  72. package/docs/operations/releasing.md +3 -3
  73. package/docs/operations/releasing.zh-CN.md +3 -3
  74. package/docs/operations/troubleshooting.md +11 -6
  75. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  76. package/docs/public-usage-guide.md +28 -21
  77. package/docs/public-usage-guide.zh-CN.md +28 -21
  78. package/docs/tutorials/01-install-and-start.md +56 -35
  79. package/docs/tutorials/01-install-and-start.zh-CN.md +56 -35
  80. package/docs/tutorials/02-connect-chatgpt.md +112 -46
  81. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +113 -45
  82. package/docs/wiki/Architecture.md +1 -1
  83. package/docs/wiki/Installation.md +2 -2
  84. package/docs/wiki/Quick-Start.md +1 -1
  85. package/docs/wiki/Work-Lifecycle.md +1 -1
  86. package/install.ps1 +4 -5
  87. package/install.sh +4 -6
  88. package/package.json +15 -5
  89. package/scripts/archive-workflow.sh +0 -1
  90. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  91. package/scripts/build-controller-ui.ts +28 -0
  92. package/scripts/capture-plan.sh +4 -4
  93. package/scripts/check-agent-tooling.sh +12 -1
  94. package/scripts/check-platform-support.mjs +2 -2
  95. package/scripts/check-public-docs.sh +16 -5
  96. package/scripts/check-release-readiness.sh +5 -11
  97. package/scripts/check-runtime-architecture.mjs +14 -22
  98. package/scripts/check-tarball-install-smoke.sh +3 -2
  99. package/scripts/check-task-workflow.sh +2 -2
  100. package/scripts/codex-handoff-resume.sh +5 -5
  101. package/scripts/create-project-dirs.sh +3 -3
  102. package/scripts/ensure-task-workflow.sh +45 -35
  103. package/scripts/init-project.sh +4 -4
  104. package/scripts/install-desktop-operator-registration.ts +2 -2
  105. package/scripts/lib/project-init-lib.sh +46 -44
  106. package/scripts/migrate-project-template.sh +7 -4
  107. package/scripts/new-plan.sh +1 -1
  108. package/scripts/plan-to-todo.sh +2 -2
  109. package/scripts/prepare-codex-handoff.sh +2 -2
  110. package/scripts/prepare-handoff.sh +9 -9
  111. package/scripts/refresh-current-status.sh +2 -2
  112. package/scripts/setup-plugins.sh +3 -3
  113. package/scripts/ship-worktrees.sh +1 -1
  114. package/scripts/sprint-backlog.sh +5 -5
  115. package/scripts/sync-codex-installed-copies.sh +2 -3
  116. package/scripts/verify-sprint.sh +7 -7
  117. package/scripts/workstream-sync.sh +1 -1
  118. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  119. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  120. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  121. package/src/cli/commands/chatgpt.ts +51 -0
  122. package/src/cli/commands/doctor.ts +1 -1
  123. package/src/cli/commands/init-hook.ts +211 -70
  124. package/src/cli/commands/init.ts +5 -6
  125. package/src/cli/commands/mcp.ts +29 -5
  126. package/src/cli/commands/runtime.ts +60 -8
  127. package/src/cli/commands/setup-profile.ts +441 -0
  128. package/src/cli/controller/check-runner.ts +20 -3
  129. package/src/cli/controller/codex-continuation.ts +4 -7
  130. package/src/cli/controller/context-pack.ts +118 -14
  131. package/src/cli/controller/operational-plan.ts +4 -4
  132. package/src/cli/controller/task-ledger.ts +10 -10
  133. package/src/cli/controller/work-mode.ts +6 -13
  134. package/src/cli/index.ts +14 -35
  135. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  136. package/src/cli/local-bridge/console-automations.ts +160 -0
  137. package/src/cli/local-bridge/dashboard.ts +8 -1303
  138. package/src/cli/local-bridge/facade-api.ts +16 -7
  139. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  140. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  141. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  142. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  144. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  146. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  147. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  148. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  149. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  150. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  152. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  153. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  155. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  156. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  157. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  158. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  159. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  160. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  161. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  162. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  163. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  164. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  165. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  166. package/src/cli/local-bridge/server.ts +44 -0
  167. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  168. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  169. package/src/cli/mcp/instructions.ts +3 -3
  170. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  171. package/src/cli/mcp/multi-repository.ts +2 -0
  172. package/src/cli/mcp/policy.ts +7 -5
  173. package/src/cli/mcp/repository-tools.ts +53 -14
  174. package/src/cli/mcp/server.ts +5 -10
  175. package/src/cli/mcp/setup.ts +211 -275
  176. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  177. package/src/cli/mcp/toolset-names.ts +1 -1
  178. package/src/cli/mcp/toolset.ts +0 -1
  179. package/src/cli/mcp/transports/http.ts +24 -4
  180. package/src/cli/repositories/branch-name-policy.ts +0 -7
  181. package/src/cli/repositories/controller-home.ts +110 -3
  182. package/src/cli/repositories/safe-patch.ts +1 -1
  183. package/src/cli/repositories/selected-path-actions.ts +26 -24
  184. package/src/core/adoption/gitignore-plan.ts +2 -3
  185. package/src/core/adoption/plan.ts +4 -1
  186. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  187. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  188. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  189. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  190. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  191. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  192. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  193. package/src/runtime/control-plane/facade/types.ts +0 -1
  194. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  195. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  196. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  197. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  198. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  199. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  200. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  201. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  202. package/src/runtime/evidence/event-ledger.ts +1 -1
  203. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  204. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  205. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  206. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  207. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  208. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  209. package/src/runtime/execution/process-runtime/index.ts +3 -0
  210. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  211. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  212. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  213. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  214. package/src/runtime/execution/process-runtime/store.ts +1 -19
  215. package/src/runtime/execution/process-runtime/types.ts +4 -0
  216. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  217. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  218. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  219. package/src/runtime/execution/thin-harness/types.ts +0 -1
  220. package/src/runtime/execution/workers/executor.ts +1 -1
  221. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  222. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  223. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  224. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  225. package/src/runtime/gateway/mcp/router.ts +117 -182
  226. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  227. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  228. package/src/runtime/plugins/browser-adapter.ts +5 -0
  229. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  230. package/src/runtime/plugins/store.ts +14 -6
  231. package/src/runtime/plugins/types.ts +1 -1
  232. package/src/runtime/projections/materialized-view.ts +1 -23
  233. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  234. package/src/runtime/resources/ownership.ts +0 -1
  235. package/src/runtime/root/gateway-adapter.ts +2 -0
  236. package/src/runtime/root/mcp-transport.ts +10 -2
  237. package/src/runtime/root/package-runtime-release.ts +161 -0
  238. package/src/runtime/root/package-runtime-service.ts +172 -0
  239. package/src/runtime/root/runtime.ts +2 -1
  240. package/src/runtime/root/scheduler.ts +5 -0
  241. package/src/runtime/root/service.ts +18 -5
  242. package/src/runtime/standalone-recovery/core.ts +65 -16
  243. package/src/runtime/workflow/findings/store.ts +145 -106
  244. package/src/runtime/workflow/findings/types.ts +22 -0
  245. package/src/runtime/workflow/schedules/engine.ts +132 -2
  246. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  247. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  248. package/docs/CHANGELOG.md +0 -978
  249. package/docs/README.zh-CN.md +0 -58
  250. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  251. package/src/cli/commands/controller.ts +0 -680
  252. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  253. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  254. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  255. package/src/runtime/workflow/campaigns/review.ts +0 -315
  256. package/src/runtime/workflow/campaigns/store.ts +0 -526
  257. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  258. package/src/runtime/workflow/campaigns/types.ts +0 -290
  259. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -0,0 +1,172 @@
1
+ import { spawn, spawnSync, type SpawnSyncReturns } from 'child_process';
2
+ import { closeSync, mkdirSync, openSync, renameSync, writeFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { dirname, join, resolve } from 'path';
5
+ import { resolveControllerHome } from '../../cli/repositories/controller-home';
6
+ import { RUNTIME_WRITE_CLAIM_ENV } from './write-fence';
7
+ import {
8
+ activeRuntimeEntrypoint,
9
+ activeRuntimeLaunchSpec,
10
+ forgeRuntimeServicePaths,
11
+ installForgeRuntimeService,
12
+ syncForgeRuntimeActiveEntrypoint,
13
+ writeForgeRuntimeServiceConfig,
14
+ type ForgeRuntimeServiceConfig,
15
+ } from './service';
16
+ import { materializePackageRuntimeRelease, type PackageRuntimeRelease } from './package-runtime-release';
17
+
18
+ export type PackageRuntimeServiceMode = 'launchd' | 'systemd-user' | 'portable';
19
+
20
+ export interface PackageRuntimeServiceInstallResult {
21
+ status: 'installed';
22
+ mode: PackageRuntimeServiceMode;
23
+ persistent: boolean;
24
+ controllerHome: string;
25
+ release: PackageRuntimeRelease;
26
+ servicePath?: string;
27
+ pid?: number;
28
+ warnings: string[];
29
+ }
30
+
31
+ export interface PackageRuntimeServiceOptions {
32
+ controllerHome?: string;
33
+ packageRoot?: string;
34
+ host?: string;
35
+ port?: number;
36
+ authTokenFile: string;
37
+ exclusiveWorkId?: string;
38
+ platform?: NodeJS.Platform;
39
+ env?: NodeJS.ProcessEnv;
40
+ forcePortable?: boolean;
41
+ }
42
+
43
+ function atomicWrite(path: string, content: string, mode = 0o600): void {
44
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
45
+ const temporary = `${path}.${process.pid}.tmp`;
46
+ writeFileSync(temporary, content, { encoding: 'utf8', mode });
47
+ renameSync(temporary, path);
48
+ }
49
+
50
+ function systemdEscape(value: string): string {
51
+ return `"${value.replaceAll('%', '%%').replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
52
+ }
53
+
54
+ export function renderForgeRuntimeSystemdUserUnit(input: {
55
+ description?: string;
56
+ executable: string;
57
+ args: string[];
58
+ environment: Record<string, string>;
59
+ }): string {
60
+ const environment = Object.entries(input.environment)
61
+ .sort(([left], [right]) => left.localeCompare(right))
62
+ .map(([key, value]) => `Environment=${systemdEscape(`${key}=${value}`)}`)
63
+ .join('\n');
64
+ return [
65
+ '[Unit]',
66
+ `Description=${input.description ?? 'Forge Runtime'}`,
67
+ 'After=network-online.target',
68
+ '',
69
+ '[Service]',
70
+ 'Type=simple',
71
+ `ExecStart=${[input.executable, ...input.args].map(systemdEscape).join(' ')}`,
72
+ ...(environment ? [environment] : []),
73
+ 'Restart=on-failure',
74
+ 'RestartSec=5',
75
+ '',
76
+ '[Install]',
77
+ 'WantedBy=default.target',
78
+ '',
79
+ ].join('\n');
80
+ }
81
+
82
+ function commandSucceeded(command: string, args: string[], env: NodeJS.ProcessEnv): SpawnSyncReturns<string> {
83
+ return spawnSync(command, args, { encoding: 'utf8', env, timeout: 30_000 });
84
+ }
85
+
86
+ export function systemdUserAvailable(env: NodeJS.ProcessEnv = process.env): boolean {
87
+ const result = commandSucceeded('systemctl', ['--user', 'show-environment'], env);
88
+ return result.status === 0;
89
+ }
90
+
91
+ function cleanRuntimeInstallerEnvironment(env: NodeJS.ProcessEnv, releaseEnvironment: Record<string, string>): NodeJS.ProcessEnv {
92
+ const next = { ...env };
93
+ for (const key of Object.values(RUNTIME_WRITE_CLAIM_ENV)) delete next[key];
94
+ delete next.FORGE_CONTROLLER_LIFECYCLE_OWNER;
95
+ delete next.FORGE_RELEASE_PATH;
96
+ delete next.FORGE_RELEASE_REVISION;
97
+ delete next.FORGE_RELEASE_SOURCE_COMMIT;
98
+ delete next.FORGE_RELEASE_CLEAN_WORKSPACE;
99
+ return { ...next, ...releaseEnvironment };
100
+ }
101
+
102
+ function installSystemdUserService(controllerHome: string, env: NodeJS.ProcessEnv): string {
103
+ const entrypoint = activeRuntimeEntrypoint(controllerHome);
104
+ const launch = activeRuntimeLaunchSpec(controllerHome);
105
+ if (!entrypoint || !launch) throw new Error('FORGE_PACKAGE_RUNTIME_LAUNCH_SPEC_MISSING');
106
+ const label = forgeRuntimeServicePaths(controllerHome).label;
107
+ const unitName = `${label}.service`;
108
+ const unitPath = join(env.HOME ?? homedir(), '.config', 'systemd', 'user', unitName);
109
+ atomicWrite(unitPath, renderForgeRuntimeSystemdUserUnit({ executable: entrypoint, args: launch.args, environment: launch.environment }), 0o644);
110
+ for (const args of [['--user', 'daemon-reload'], ['--user', 'enable', '--now', unitName]]) {
111
+ const result = commandSucceeded('systemctl', args, env);
112
+ if (result.status !== 0) throw new Error(`FORGE_RUNTIME_SYSTEMD_INSTALL_FAILED: systemctl ${args.join(' ')}: ${(result.stderr || result.stdout || '').trim()}`);
113
+ }
114
+ return unitPath;
115
+ }
116
+
117
+ function startPortableRuntime(controllerHome: string, env: NodeJS.ProcessEnv): number {
118
+ const entrypoint = activeRuntimeEntrypoint(controllerHome);
119
+ const launch = activeRuntimeLaunchSpec(controllerHome);
120
+ if (!entrypoint || !launch) throw new Error('FORGE_PACKAGE_RUNTIME_LAUNCH_SPEC_MISSING');
121
+ const paths = forgeRuntimeServicePaths(controllerHome);
122
+ mkdirSync(join(paths.serviceRoot, 'logs'), { recursive: true, mode: 0o700 });
123
+ const stdout = openSync(paths.stdoutPath, 'a', 0o600);
124
+ const stderr = openSync(paths.stderrPath, 'a', 0o600);
125
+ try {
126
+ const child = spawn(entrypoint, launch.args, {
127
+ detached: true,
128
+ stdio: ['ignore', stdout, stderr],
129
+ env: cleanRuntimeInstallerEnvironment(env, launch.environment),
130
+ });
131
+ if (!child.pid) throw new Error('FORGE_PORTABLE_RUNTIME_START_FAILED: child pid unavailable');
132
+ child.unref();
133
+ atomicWrite(join(paths.serviceRoot, 'portable.json'), `${JSON.stringify({ schemaVersion: 1, pid: child.pid, startedAt: new Date().toISOString() }, null, 2)}\n`);
134
+ return child.pid;
135
+ } finally {
136
+ closeSync(stdout);
137
+ closeSync(stderr);
138
+ }
139
+ }
140
+
141
+ export async function installPackageRuntimeService(options: PackageRuntimeServiceOptions): Promise<PackageRuntimeServiceInstallResult> {
142
+ const controllerHome = resolveControllerHome(options.controllerHome);
143
+ const release = materializePackageRuntimeRelease({ controllerHome, packageRoot: options.packageRoot });
144
+ const config: ForgeRuntimeServiceConfig = {
145
+ schemaVersion: 1,
146
+ controllerHome,
147
+ repositoryRoot: release.packageRoot,
148
+ host: options.host ?? '127.0.0.1',
149
+ port: options.port ?? 8765,
150
+ authTokenFile: resolve(options.authTokenFile),
151
+ ...(options.exclusiveWorkId?.trim() ? { exclusiveWorkId: options.exclusiveWorkId.trim() } : {}),
152
+ };
153
+ writeForgeRuntimeServiceConfig(config);
154
+ syncForgeRuntimeActiveEntrypoint(controllerHome);
155
+ const platform = options.platform ?? process.platform;
156
+ const env = options.env ?? process.env;
157
+ if (!options.forcePortable && platform === 'darwin') {
158
+ const paths = await installForgeRuntimeService({ config, runnerPath: join(release.packageRoot, 'bin', 'forge-runtime-service.mjs'), nodeExecutable: process.execPath });
159
+ return { status: 'installed', mode: 'launchd', persistent: true, controllerHome, release, servicePath: paths.installedPlistPath, warnings: [] };
160
+ }
161
+ if (!options.forcePortable && platform === 'linux' && systemdUserAvailable(env)) {
162
+ const unitPath = installSystemdUserService(controllerHome, env);
163
+ return { status: 'installed', mode: 'systemd-user', persistent: true, controllerHome, release, servicePath: unitPath, warnings: [] };
164
+ }
165
+ const pid = startPortableRuntime(controllerHome, env);
166
+ return {
167
+ status: 'installed', mode: 'portable', persistent: false, controllerHome, release, pid,
168
+ warnings: [platform === 'win32'
169
+ ? 'Native Windows is preview: Forge started a detached user process for this session; automatic login/reboot persistence is not yet claimed.'
170
+ : 'systemd --user is unavailable: Forge started a detached user process; enable a user service manager for reboot persistence.'],
171
+ };
172
+ }
@@ -228,11 +228,12 @@ export class CanonicalForgeRuntime {
228
228
  port: this.config.port,
229
229
  authToken: this.config.authToken,
230
230
  readiness: () => this.readiness(),
231
- createServer: (principalId, sessionId) => createRuntimeGatewayServer(this.controller!, principalId, {
231
+ createServer: (principalId, sessionId, controllerType) => createRuntimeGatewayServer(this.controller!, principalId, {
232
232
  controllerHome: this.config.controllerHome,
233
233
  runtimeInstanceId: this.runtimeInstanceId,
234
234
  runtimeSourceRoot,
235
235
  sessionId,
236
+ controllerType,
236
237
  }),
237
238
  onFatal: (error) => this.failCore('MCP_TRANSPORT_FAILED', error.message),
238
239
  });
@@ -1,4 +1,5 @@
1
1
  import { GlobalScheduler, readSchedulerHealthSnapshot } from '../control-plane/global-scheduler/scheduler';
2
+ import { applyPendingContinuationActivations } from '../workflow/schedules/pending-activation';
2
3
 
3
4
  export interface RuntimeSchedulerHandle {
4
5
  ready: Promise<void>;
@@ -34,6 +35,10 @@ export function startInProcessScheduler(
34
35
  readyTimeoutMs = 5_000,
35
36
  ): RuntimeSchedulerHandle {
36
37
  const abort = new AbortController();
38
+ // Old Runtime releases ignore pending activation markers entirely. A naturally
39
+ // restarted Runtime that contains this code consumes them once, before its
40
+ // sole Scheduler starts, so no second scheduler or rollout hook is required.
41
+ applyPendingContinuationActivations(controllerHome);
37
42
  const startedAfterMs = Date.now();
38
43
  const scheduler = new GlobalScheduler(controllerHome, {}, {
39
44
  controllerPid: process.pid,
@@ -97,6 +97,7 @@ interface RuntimeReleaseManifestRecord {
97
97
  controllerHome: string;
98
98
  artifactIdentity: string;
99
99
  arguments?: string[];
100
+ diagnosticEntrypoint?: string;
100
101
  browserAutomationHelperEntrypoint?: string;
101
102
  browserAutomationHelperArtifactIdentity?: string;
102
103
  browserAutomationHelperContractIdentity?: string;
@@ -113,7 +114,7 @@ interface ActiveRuntimeReleaseRecord {
113
114
  manifest: RuntimeReleaseManifestRecord;
114
115
  }
115
116
 
116
- interface ActiveRuntimeLaunchSpec {
117
+ export interface ActiveRuntimeLaunchSpec {
117
118
  args: string[];
118
119
  environment: Record<string, string>;
119
120
  }
@@ -168,7 +169,7 @@ async function retireLegacyBrowserAutomationLaunchAgent(controllerHome: string):
168
169
  rmSync(legacy.installedPlistPath, { force: true });
169
170
  }
170
171
 
171
- function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpec | undefined {
172
+ export function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpec | undefined {
172
173
  const home = resolveControllerHome(controllerHome);
173
174
  const active = readActiveRuntimeRelease(home);
174
175
  if (!active) return undefined;
@@ -178,6 +179,12 @@ function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpe
178
179
  if (!Array.isArray(manifestArguments) || !manifestArguments.every((argument) => typeof argument === 'string')) {
179
180
  throw new Error('FORGE_RUNTIME_RELEASE_ARGUMENTS_INVALID');
180
181
  }
182
+ const diagnosticEntrypoint = typeof active.manifest.diagnosticEntrypoint === 'string' && active.manifest.diagnosticEntrypoint.trim()
183
+ ? resolve(active.releaseRoot, active.manifest.diagnosticEntrypoint)
184
+ : undefined;
185
+ if (diagnosticEntrypoint && (!isInside(active.releaseRoot, diagnosticEntrypoint) || !existsSync(diagnosticEntrypoint))) {
186
+ throw new Error('FORGE_RUNTIME_RELEASE_DIAGNOSTIC_ENTRYPOINT_INVALID');
187
+ }
181
188
  return {
182
189
  args: [
183
190
  '--controller-home', home,
@@ -192,6 +199,7 @@ function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpe
192
199
  environment: {
193
200
  FORGE_CONTROLLER_HOME: home,
194
201
  FORGE_CONTROLLER_RUNTIME_SOURCE_ROOT: active.releaseRoot,
202
+ ...(diagnosticEntrypoint ? { FORGE_CLI_EXECUTABLE: diagnosticEntrypoint } : {}),
195
203
  FORGE_RELEASE_PATH: active.releaseRoot,
196
204
  FORGE_RELEASE_ID: active.releaseId,
197
205
  FORGE_RELEASE_REVISION: active.manifest.releaseRevision ?? active.releaseId,
@@ -261,12 +269,17 @@ export function renderForgeRuntimeLaunchAgent(input: { paths: ForgeRuntimeServic
261
269
  return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key>\n <string>${xml(input.paths.label)}</string>\n <key>ProgramArguments</key>\n <array>\n${args.map((arg) => ` <string>${xml(arg)}</string>`).join('\n')}\n </array>\n${environmentXml} <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>5</integer>\n <key>ProcessType</key>\n <string>Interactive</string>\n <key>StandardOutPath</key>\n <string>${xml(input.paths.stdoutPath)}</string>\n <key>StandardErrorPath</key>\n <string>${xml(input.paths.stderrPath)}</string>\n</dict>\n</plist>\n`;
262
270
  }
263
271
 
264
- export async function installForgeRuntimeService(input: { config: ForgeRuntimeServiceConfig; runnerPath: string; nodeExecutable?: string }): Promise<ForgeRuntimeServicePaths> {
265
- if (process.platform !== 'darwin') throw new Error('FORGE_RUNTIME_SERVICE_PLATFORM_UNSUPPORTED: launchd requires macOS');
266
- const config = validateForgeRuntimeServiceConfig(input.config);
272
+ export function writeForgeRuntimeServiceConfig(input: ForgeRuntimeServiceConfig): { config: ForgeRuntimeServiceConfig; paths: ForgeRuntimeServicePaths } {
273
+ const config = validateForgeRuntimeServiceConfig(input);
267
274
  const paths = forgeRuntimeServicePaths(config.controllerHome);
268
275
  mkdirSync(join(paths.serviceRoot, 'logs'), { recursive: true, mode: 0o700 });
269
276
  atomicWrite(paths.configPath, `${JSON.stringify(config, null, 2)}\n`);
277
+ return { config, paths };
278
+ }
279
+
280
+ export async function installForgeRuntimeService(input: { config: ForgeRuntimeServiceConfig; runnerPath: string; nodeExecutable?: string }): Promise<ForgeRuntimeServicePaths> {
281
+ if (process.platform !== 'darwin') throw new Error('FORGE_RUNTIME_SERVICE_PLATFORM_UNSUPPORTED: launchd requires macOS');
282
+ const { config, paths } = writeForgeRuntimeServiceConfig(input.config);
270
283
  ensureForgeRuntimeLaunchAgentContract({
271
284
  controllerHome: config.controllerHome,
272
285
  bootstrapNodeExecutable: input.nodeExecutable ?? process.execPath,
@@ -16,6 +16,12 @@ import {
16
16
  } from '../root/release-store';
17
17
  import type { RuntimeReleaseManifest } from '../root/types';
18
18
  import { createRecoveryHttpTransport, type RecoveryHttpTransport } from './http-transport';
19
+ import {
20
+ migrateStoppedRepoLocalControllerHomeStorage,
21
+ repoLocalControllerHomeStorageNeedsMigration,
22
+ rollbackStoppedRepoLocalControllerHomeStorage,
23
+ type ControllerHomeStorageMigration,
24
+ } from '../../cli/repositories/controller-home';
19
25
 
20
26
  /** Standalone recovery reads only canonical Runtime observation and whole-release authority. */
21
27
  export interface PublicTunnelServiceConfig {
@@ -1590,7 +1596,8 @@ export async function activateRuntimeRelease(
1590
1596
  audit(config, 'runtime_release_activation_candidate_invalid', { detail, ...(guard.requestId ? { requestId: guard.requestId } : {}) });
1591
1597
  return { ok: false, attempted: false, noOp: true, detail };
1592
1598
  }
1593
- if ((dependencies.platform ?? process.platform) !== 'darwin') {
1599
+ const platform = dependencies.platform ?? process.platform;
1600
+ if (platform !== 'darwin') {
1594
1601
  return { ok: false, attempted: false, noOp: true, detail: 'Runtime release activation currently requires the configured macOS launchd service' };
1595
1602
  }
1596
1603
  const uid = await (dependencies.currentUid ?? currentUid)();
@@ -1638,7 +1645,19 @@ export async function activateRuntimeRelease(
1638
1645
  });
1639
1646
  return { ok: false, attempted: false, noOp: true, detail, operationId } satisfies RuntimeReleaseActivationResult;
1640
1647
  }
1648
+ let storageMigrationRequired = false;
1649
+ try {
1650
+ storageMigrationRequired = repoLocalControllerHomeStorageNeedsMigration(config.controllerHome, platform);
1651
+ } catch (error) {
1652
+ const detail = error instanceof Error ? error.message : 'Controller Home storage migration preflight failed';
1653
+ audit(config, 'runtime_release_activation_storage_preflight_failed', { operationId, requestId: lockRequestId, detail });
1654
+ return { ok: false, attempted: false, noOp: true, detail, operationId } satisfies RuntimeReleaseActivationResult;
1655
+ }
1641
1656
  if (current && current.active.releaseId === candidate.manifest.releaseId) {
1657
+ if (storageMigrationRequired) {
1658
+ const detail = 'CONTROLLER_HOME_STORAGE_MIGRATION_REQUIRES_STAGED_RELEASE: stage a fresh immutable Runtime release so Recovery can migrate storage inside the activation transaction';
1659
+ return { ok: false, attempted: false, noOp: true, detail, operationId } satisfies RuntimeReleaseActivationResult;
1660
+ }
1642
1661
  return {
1643
1662
  ok: true,
1644
1663
  attempted: false,
@@ -1649,7 +1668,7 @@ export async function activateRuntimeRelease(
1649
1668
  } satisfies RuntimeReleaseActivationResult;
1650
1669
  }
1651
1670
  if (current) {
1652
- if (runtimeBehaviorEquivalent(current.active.manifestPath, candidate.manifestPath)) {
1671
+ if (runtimeBehaviorEquivalent(current.active.manifestPath, candidate.manifestPath) && !storageMigrationRequired) {
1653
1672
  const verify = await verifyLocal(config);
1654
1673
  audit(config, 'runtime_release_activation_behavior_identical', {
1655
1674
  operationId,
@@ -1727,24 +1746,44 @@ export async function activateRuntimeRelease(
1727
1746
  audit(config, 'runtime_release_activation_launchd_contract_failed', { serviceTarget: service.target, operationId, detail });
1728
1747
  return { ok: false, attempted: true, detail, serviceTarget: service.target, verify: await verifyLocal(config) } satisfies RuntimeReleaseActivationResult;
1729
1748
  }
1730
- const started = await ensureLaunchdServiceStarted(service, runCommand);
1749
+ let storageMigration: ControllerHomeStorageMigration | undefined;
1731
1750
  let after: VerifyResult;
1732
1751
  let activationFailureDetail: string | undefined;
1733
- if (!started.ok) {
1734
- activationFailureDetail = started.detail;
1735
- audit(config, 'runtime_release_activation_start_failed', { serviceTarget: service.target, operationId, detail: started.detail });
1752
+ try {
1753
+ storageMigration = migrateStoppedRepoLocalControllerHomeStorage(config.controllerHome, platform);
1754
+ if (storageMigration.migrated) {
1755
+ audit(config, 'runtime_controller_home_noindex_migrated', {
1756
+ serviceTarget: service.target,
1757
+ operationId,
1758
+ logicalHome: storageMigration.logicalHome,
1759
+ physicalHome: storageMigration.physicalHome,
1760
+ });
1761
+ }
1762
+ } catch (error) {
1763
+ activationFailureDetail = `Controller Home .noindex migration failed: ${error instanceof Error ? error.message : String(error)}`;
1764
+ audit(config, 'runtime_controller_home_noindex_migration_failed', { serviceTarget: service.target, operationId, detail: activationFailureDetail });
1765
+ }
1766
+
1767
+ if (activationFailureDetail) {
1736
1768
  after = await verifyLocal(config);
1737
1769
  } else {
1738
- after = await verifyPrimaryRuntimeAfterStart({
1739
- config,
1740
- timeoutMs: configuredPrimaryRuntimeService(config).postRestartVerifyTimeoutMs ?? 60_000,
1741
- now,
1742
- wait,
1743
- verifyLocal,
1744
- });
1745
- if (after.ok && after.releases.active?.revision === candidate.manifest.releaseId) {
1746
- audit(config, 'runtime_release_activation_succeeded', { serviceTarget: service.target, operationId, requestId: lockRequestId, activeRevision: after.releases.active?.revision, expectedAuthorityRevision: guard.expectedAuthorityRevision, expectedActiveReleaseId: guard.expectedActiveReleaseId });
1747
- return { ok: true, attempted: true, detail: 'requested Runtime release activated and passed whole-Runtime verification', serviceTarget: service.target, operationId, verify: after } satisfies RuntimeReleaseActivationResult;
1770
+ const started = await ensureLaunchdServiceStarted(service, runCommand);
1771
+ if (!started.ok) {
1772
+ activationFailureDetail = started.detail;
1773
+ audit(config, 'runtime_release_activation_start_failed', { serviceTarget: service.target, operationId, detail: started.detail });
1774
+ after = await verifyLocal(config);
1775
+ } else {
1776
+ after = await verifyPrimaryRuntimeAfterStart({
1777
+ config,
1778
+ timeoutMs: configuredPrimaryRuntimeService(config).postRestartVerifyTimeoutMs ?? 60_000,
1779
+ now,
1780
+ wait,
1781
+ verifyLocal,
1782
+ });
1783
+ if (after.ok && after.releases.active?.revision === candidate.manifest.releaseId) {
1784
+ audit(config, 'runtime_release_activation_succeeded', { serviceTarget: service.target, operationId, requestId: lockRequestId, activeRevision: after.releases.active?.revision, expectedAuthorityRevision: guard.expectedAuthorityRevision, expectedActiveReleaseId: guard.expectedActiveReleaseId, controllerHomeStorageMigrated: storageMigration?.migrated === true });
1785
+ return { ok: true, attempted: true, detail: storageMigration?.migrated ? 'requested Runtime release activated, Controller Home migrated to .noindex storage, and whole-Runtime verification passed' : 'requested Runtime release activated and passed whole-Runtime verification', serviceTarget: service.target, operationId, verify: after } satisfies RuntimeReleaseActivationResult;
1786
+ }
1748
1787
  }
1749
1788
  }
1750
1789
 
@@ -1772,6 +1811,16 @@ export async function activateRuntimeRelease(
1772
1811
  try {
1773
1812
  const rollbackOperationId = `recovery-activate-runtime-rollback-${Date.now()}-${randomUUID().slice(0, 8)}`;
1774
1813
  const restored = rollbackRuntimeRelease(config.controllerHome, rollbackOperationId);
1814
+ if (storageMigration?.migrated) {
1815
+ rollbackStoppedRepoLocalControllerHomeStorage(storageMigration);
1816
+ audit(config, 'runtime_controller_home_noindex_migration_rolled_back', {
1817
+ serviceTarget: service.target,
1818
+ operationId,
1819
+ rollbackOperationId,
1820
+ logicalHome: storageMigration.logicalHome,
1821
+ physicalHome: storageMigration.physicalHome,
1822
+ });
1823
+ }
1775
1824
  ensureForgeRuntimeLaunchAgentContract({ controllerHome: config.controllerHome });
1776
1825
  const previousRevision = previousActive?.releaseId ?? '';
1777
1826
  const previousIdentity = previousActive?.artifactIdentity;