@agent-relay/factory 0.1.50 → 0.1.51

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 (173) hide show
  1. package/README.md +93 -11
  2. package/dist/cli/fleet.d.ts +4 -1
  3. package/dist/cli/fleet.d.ts.map +1 -1
  4. package/dist/cli/fleet.js +87 -42
  5. package/dist/cli/fleet.js.map +1 -1
  6. package/dist/config/schema.d.ts +1671 -10
  7. package/dist/config/schema.d.ts.map +1 -1
  8. package/dist/config/schema.js +83 -0
  9. package/dist/config/schema.js.map +1 -1
  10. package/dist/dispatch/templates.d.ts +6 -0
  11. package/dist/dispatch/templates.d.ts.map +1 -1
  12. package/dist/dispatch/templates.js +16 -0
  13. package/dist/dispatch/templates.js.map +1 -1
  14. package/dist/environments/connection-registry.d.ts +617 -0
  15. package/dist/environments/connection-registry.d.ts.map +1 -0
  16. package/dist/environments/connection-registry.js +174 -0
  17. package/dist/environments/connection-registry.js.map +1 -0
  18. package/dist/environments/index.d.ts +21 -0
  19. package/dist/environments/index.d.ts.map +1 -0
  20. package/dist/environments/index.js +11 -0
  21. package/dist/environments/index.js.map +1 -0
  22. package/dist/environments/k6-job.d.ts +64 -0
  23. package/dist/environments/k6-job.d.ts.map +1 -0
  24. package/dist/environments/k6-job.js +376 -0
  25. package/dist/environments/k6-job.js.map +1 -0
  26. package/dist/environments/kubernetes-client.d.ts +72 -0
  27. package/dist/environments/kubernetes-client.d.ts.map +1 -0
  28. package/dist/environments/kubernetes-client.js +337 -0
  29. package/dist/environments/kubernetes-client.js.map +1 -0
  30. package/dist/environments/kubernetes-command.d.ts +30 -0
  31. package/dist/environments/kubernetes-command.d.ts.map +1 -0
  32. package/dist/environments/kubernetes-command.js +108 -0
  33. package/dist/environments/kubernetes-command.js.map +1 -0
  34. package/dist/environments/kubernetes-environment.d.ts +38 -0
  35. package/dist/environments/kubernetes-environment.d.ts.map +1 -0
  36. package/dist/environments/kubernetes-environment.js +333 -0
  37. package/dist/environments/kubernetes-environment.js.map +1 -0
  38. package/dist/environments/kubernetes-provider.d.ts +83 -0
  39. package/dist/environments/kubernetes-provider.d.ts.map +1 -0
  40. package/dist/environments/kubernetes-provider.js +956 -0
  41. package/dist/environments/kubernetes-provider.js.map +1 -0
  42. package/dist/environments/load-harness.d.ts +199 -0
  43. package/dist/environments/load-harness.d.ts.map +1 -0
  44. package/dist/environments/load-harness.js +259 -0
  45. package/dist/environments/load-harness.js.map +1 -0
  46. package/dist/environments/load-profile.d.ts +294 -0
  47. package/dist/environments/load-profile.d.ts.map +1 -0
  48. package/dist/environments/load-profile.js +125 -0
  49. package/dist/environments/load-profile.js.map +1 -0
  50. package/dist/environments/stack-deployer.d.ts +22 -0
  51. package/dist/environments/stack-deployer.d.ts.map +1 -0
  52. package/dist/environments/stack-deployer.js +27 -0
  53. package/dist/environments/stack-deployer.js.map +1 -0
  54. package/dist/environments/stack-descriptor.d.ts +243 -0
  55. package/dist/environments/stack-descriptor.d.ts.map +1 -0
  56. package/dist/environments/stack-descriptor.js +88 -0
  57. package/dist/environments/stack-descriptor.js.map +1 -0
  58. package/dist/environments/verification-pipeline.d.ts +117 -0
  59. package/dist/environments/verification-pipeline.d.ts.map +1 -0
  60. package/dist/environments/verification-pipeline.js +643 -0
  61. package/dist/environments/verification-pipeline.js.map +1 -0
  62. package/dist/environments/verification-stack-deployer.d.ts +73 -0
  63. package/dist/environments/verification-stack-deployer.d.ts.map +1 -0
  64. package/dist/environments/verification-stack-deployer.js +484 -0
  65. package/dist/environments/verification-stack-deployer.js.map +1 -0
  66. package/dist/environments/verification-stack-descriptor.d.ts +1134 -0
  67. package/dist/environments/verification-stack-descriptor.d.ts.map +1 -0
  68. package/dist/environments/verification-stack-descriptor.js +275 -0
  69. package/dist/environments/verification-stack-descriptor.js.map +1 -0
  70. package/dist/environments/verification-stack.d.ts +29 -0
  71. package/dist/environments/verification-stack.d.ts.map +1 -0
  72. package/dist/environments/verification-stack.js +43 -0
  73. package/dist/environments/verification-stack.js.map +1 -0
  74. package/dist/featuremap/generate.d.ts +72 -0
  75. package/dist/featuremap/generate.d.ts.map +1 -0
  76. package/dist/featuremap/generate.js +854 -0
  77. package/dist/featuremap/generate.js.map +1 -0
  78. package/dist/featuremap/index.d.ts +2 -0
  79. package/dist/featuremap/index.d.ts.map +1 -1
  80. package/dist/featuremap/index.js +1 -0
  81. package/dist/featuremap/index.js.map +1 -1
  82. package/dist/featuremap/validate.d.ts +6 -0
  83. package/dist/featuremap/validate.d.ts.map +1 -1
  84. package/dist/featuremap/validate.js +95 -13
  85. package/dist/featuremap/validate.js.map +1 -1
  86. package/dist/fleet/create-fleet.d.ts +2 -0
  87. package/dist/fleet/create-fleet.d.ts.map +1 -1
  88. package/dist/fleet/create-fleet.js +1 -0
  89. package/dist/fleet/create-fleet.js.map +1 -1
  90. package/dist/fleet/internal-fleet-client.d.ts +8 -1
  91. package/dist/fleet/internal-fleet-client.d.ts.map +1 -1
  92. package/dist/fleet/internal-fleet-client.js +31 -6
  93. package/dist/fleet/internal-fleet-client.js.map +1 -1
  94. package/dist/fleet/relay-fleet-client.d.ts +4 -1
  95. package/dist/fleet/relay-fleet-client.d.ts.map +1 -1
  96. package/dist/fleet/relay-fleet-client.js +142 -1
  97. package/dist/fleet/relay-fleet-client.js.map +1 -1
  98. package/dist/git/agent-worktree.d.ts +4 -1
  99. package/dist/git/agent-worktree.d.ts.map +1 -1
  100. package/dist/git/agent-worktree.js +151 -8
  101. package/dist/git/agent-worktree.js.map +1 -1
  102. package/dist/index.d.ts +32 -8
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +15 -3
  105. package/dist/index.js.map +1 -1
  106. package/dist/mount/local-mount-preflight.d.ts.map +1 -1
  107. package/dist/mount/local-mount-preflight.js +6 -2
  108. package/dist/mount/local-mount-preflight.js.map +1 -1
  109. package/dist/mount/relayfile-cloud-mount-client.d.ts +8 -2
  110. package/dist/mount/relayfile-cloud-mount-client.d.ts.map +1 -1
  111. package/dist/mount/relayfile-cloud-mount-client.js +11 -1
  112. package/dist/mount/relayfile-cloud-mount-client.js.map +1 -1
  113. package/dist/node/factory-node.d.ts +2 -0
  114. package/dist/node/factory-node.d.ts.map +1 -1
  115. package/dist/node/factory-node.js +107 -2
  116. package/dist/node/factory-node.js.map +1 -1
  117. package/dist/node/preview-process.d.ts +65 -0
  118. package/dist/node/preview-process.d.ts.map +1 -0
  119. package/dist/node/preview-process.js +431 -0
  120. package/dist/node/preview-process.js.map +1 -0
  121. package/dist/node/tailscale-preview.d.ts +45 -0
  122. package/dist/node/tailscale-preview.d.ts.map +1 -0
  123. package/dist/node/tailscale-preview.js +748 -0
  124. package/dist/node/tailscale-preview.js.map +1 -0
  125. package/dist/observability/events.d.ts +1838 -28
  126. package/dist/observability/events.d.ts.map +1 -1
  127. package/dist/observability/events.js +52 -0
  128. package/dist/observability/events.js.map +1 -1
  129. package/dist/orchestrator/factory.d.ts.map +1 -1
  130. package/dist/orchestrator/factory.js +1317 -61
  131. package/dist/orchestrator/factory.js.map +1 -1
  132. package/dist/orchestrator/index.d.ts +2 -1
  133. package/dist/orchestrator/index.d.ts.map +1 -1
  134. package/dist/orchestrator/index.js +1 -1
  135. package/dist/orchestrator/index.js.map +1 -1
  136. package/dist/orchestrator/reaper.d.ts +24 -0
  137. package/dist/orchestrator/reaper.d.ts.map +1 -1
  138. package/dist/orchestrator/reaper.js +117 -0
  139. package/dist/orchestrator/reaper.js.map +1 -1
  140. package/dist/ports/environment.d.ts +83 -0
  141. package/dist/ports/environment.d.ts.map +1 -0
  142. package/dist/ports/environment.js +2 -0
  143. package/dist/ports/environment.js.map +1 -0
  144. package/dist/ports/fleet.d.ts +68 -1
  145. package/dist/ports/fleet.d.ts.map +1 -1
  146. package/dist/ports/index.d.ts +3 -2
  147. package/dist/ports/index.d.ts.map +1 -1
  148. package/dist/ports/mount.d.ts +9 -0
  149. package/dist/ports/mount.d.ts.map +1 -1
  150. package/dist/ports/state.d.ts +17 -1
  151. package/dist/ports/state.d.ts.map +1 -1
  152. package/dist/ports/worktree.d.ts +14 -0
  153. package/dist/ports/worktree.d.ts.map +1 -1
  154. package/dist/state/file-state-store.js +28 -1
  155. package/dist/state/file-state-store.js.map +1 -1
  156. package/dist/subscriptions/index.d.ts +2 -0
  157. package/dist/subscriptions/index.d.ts.map +1 -1
  158. package/dist/subscriptions/index.js +1 -0
  159. package/dist/subscriptions/index.js.map +1 -1
  160. package/dist/subscriptions/resource-subscriptions.d.ts +77 -0
  161. package/dist/subscriptions/resource-subscriptions.d.ts.map +1 -0
  162. package/dist/subscriptions/resource-subscriptions.js +91 -0
  163. package/dist/subscriptions/resource-subscriptions.js.map +1 -0
  164. package/dist/testing/fakes.d.ts +9 -1
  165. package/dist/testing/fakes.d.ts.map +1 -1
  166. package/dist/testing/fakes.js +40 -0
  167. package/dist/testing/fakes.js.map +1 -1
  168. package/dist/triage/schema.d.ts +32 -32
  169. package/dist/types.d.ts +4 -1
  170. package/dist/types.d.ts.map +1 -1
  171. package/package.json +18 -4
  172. package/schemas/kubernetes-environment-stack.schema.json +86 -0
  173. package/schemas/verification-stack.schema.json +292 -0
package/README.md CHANGED
@@ -124,13 +124,15 @@ From a source checkout instead of an npm install, run
124
124
  | `factory dispatch <KEY\|path>` | Triage + dispatch one issue. Honors `--dry-run`. |
125
125
  | `factory babysit <PR\|PR-URL>` | Spawn a one-shot babysitter for an existing open PR, even when it was not created by Factory. |
126
126
  | `factory canary <KEY\|path>` | Assert a known "Ready for Agent" issue is dispatch-ready by the real dry-run triage path. Prints `{ok,issue,status,reason}`; exits non-zero (with the skip reason) if it isn't. |
127
- | `factory featuremap check [--base <ref>]` | Validate the repository feature/test manifest and optionally report advisory drift for unchanged entries whose locations changed. |
127
+ | `factory featuremap check [--manifest <path>] [--base <ref>]` | Validate the repository feature/test manifest and optionally report advisory drift for unchanged entries whose locations changed. |
128
128
 
129
129
  Global options work anywhere in the args: `--config <path>`, `--dry-run`,
130
130
  `--backend <internal|relay>`, and `--agent-exit-timeout <ms>`. The internal
131
131
  backend reuses a relay broker that's already running for your workspace, and
132
132
  starts one if none is. For self-started brokers, the agent-exit timeout defaults
133
- to 30 minutes and can also be set with `FACTORY_AGENT_EXIT_TIMEOUT_MS`.
133
+ to 30 minutes and can also be set with `FACTORY_AGENT_EXIT_TIMEOUT_MS`. Set
134
+ `FACTORY_LOG_LEVEL=debug` to include per-checkout details for summarized local
135
+ mount refreshes.
134
136
 
135
137
  Integration connection prompts only run for commands that need provider data or
136
138
  GitHub write access. Maintenance commands such as `status`, `loop-status`,
@@ -146,9 +148,9 @@ instruction to rerun the Factory command in an interactive terminal instead.
146
148
 
147
149
  Repositories with `.agentworkforce/features/manifest.yaml` can run
148
150
  `factory featuremap check` in CI. The command rejects malformed or duplicate
149
- entries, invalid verification tiers, catalog-summary drift, and locations that
150
- do not exist. The same checker is published as `@agent-relay/factory/featuremap`
151
- for programmatic use.
151
+ entries, invalid verification tiers, catalog-summary drift, locations that do
152
+ not exist, and incomplete v1.1 category-to-procedure routing. The same checker
153
+ is published as `@agent-relay/factory/featuremap` for programmatic use.
152
154
 
153
155
  During review, pass the PR base ref with `--base <ref>`. A changed file named by
154
156
  an existing manifest entry produces an advisory when that entry's description,
@@ -156,12 +158,20 @@ verification tier, and locations are unchanged. The reviewer must re-confirm
156
158
  that metadata; the advisory does not itself fail the command because a covered
157
159
  file can change without changing the feature contract.
158
160
 
159
- An hourly per-repository sweep or Slack confirmation bot is explicitly outside
160
- this feature's scope. Factory's own guardian cycle is useful for tier-5/6 checks
161
- that need live or human confirmation, but duplicating it for every customer repo
162
- would add standing noise and infrastructure without evidence that those entries
163
- rot between PRs. Revisit that only if usage data demonstrates silent tier-5/6
164
- drift that PR checks do not catch.
161
+ Factory's agent-facing runbook is `.claude/skills/verify-features.md`. It tells
162
+ an agent how to resolve a manifest category to its named end-to-end procedure,
163
+ run the applicable tier with safe fixtures and cleanup, assert provider/fleet
164
+ state, and report unexercised live tiers explicitly. The deterministic full gate
165
+ is `npm run build && npm run featuremap:check && npm test && npm run verify:e2e`;
166
+ `workflows/verify-features.ts` adds opt-in provider, fleet, and cloud checks.
167
+
168
+ The checked-in `factory-feature-guardian` proactive persona mirrors Relay's
169
+ guardian operating model. Hourly, it reads the catalog from a scoped Factory
170
+ repository mount, selects one unchecked feature from exact revisioned cycle
171
+ state, posts an idempotent Slack question, requires a provider timestamp, and
172
+ only then checkpoints progress. Manifest/state/delivery ambiguity fails closed
173
+ instead of silently skipping a feature. Configure its Slack channel and deploy
174
+ the persona through the normal Agent Workforce proactive-agent path.
165
175
 
166
176
  ### Cloud progress and trace correlation
167
177
 
@@ -279,6 +289,78 @@ The definition reads its node config from `./factory.node.json` (or
279
289
  mapped repo is advertised as a `repo:<label>` tag so placement can route
280
290
  repo-scoped spawns to it. Spawns for unadvertised paths are refused on the node.
281
291
 
292
+ ### Tailnet live previews
293
+
294
+ Factory can attach an issue-lifetime [Tailscale Serve](https://tailscale.com/docs/features/tailscale-serve)
295
+ route to a repository's local development port. Configure the same service on
296
+ the control plane and execution node (a combined local config needs it only
297
+ once):
298
+
299
+ ```json
300
+ {
301
+ "preview": {
302
+ "provider": "tailscale-serve",
303
+ "access": "tailnet",
304
+ "services": {
305
+ "AgentWorkforce/pear": {
306
+ "port": 3000,
307
+ "portSpan": 25,
308
+ "startCommand": "npm ci && exec npm run dev"
309
+ }
310
+ }
311
+ }
312
+ }
313
+ ```
314
+
315
+ The node then advertises `preview:tailscale-serve`; Factory places the preview
316
+ first and pins the issue's agents to that node. The URL is included in agent
317
+ tasks, the Slack dispatch root, and the pull-request description. Factory uses
318
+ Serve—not Funnel—so the URL remains inside the configured tailnet and normal
319
+ tailnet grants/ACLs apply. Factory checks the live Serve status and refuses to
320
+ surface a route marked as Funnel.
321
+
322
+ `port` is the preferred node-local app port. Factory reserves the first free
323
+ port in `port..port + portSpan - 1` (the span defaults to 100), places that
324
+ allocated port in every agent task, and reserves a separate HTTPS port from
325
+ `preview.httpsPortRange`. Keep that HTTPS range dedicated to Factory previews.
326
+ Preview provisioning happens immediately after Factory creates the isolated
327
+ issue worktree, before an agent has had a chance to install ignored dependencies
328
+ such as `node_modules`. `startCommand` must therefore include any bounded,
329
+ non-interactive bootstrap the fresh checkout needs, followed by a foreground
330
+ development command that honors `PORT` (for example,
331
+ `npm ci && exec npm run dev`).
332
+ The node starts it in the issue checkout, waits for the allocated local HTTP
333
+ port to respond, and verifies on Linux or macOS that the listener belongs to the
334
+ supervised process tree before returning the URL. It persists an exact process
335
+ identity so the command survives agent handoffs and can be safely recovered or
336
+ stopped. The command must not daemonize or bind a different port. Active sweeps
337
+ repeat listener ownership verification and disable the exact Factory route if
338
+ the port is taken over by an unrelated process.
339
+ For safety, preview commands receive only `PORT` plus basic shell, locale, home,
340
+ and temporary-directory variables; they do not inherit arbitrary Factory,
341
+ Relay, or provider credentials from the node process. Load intentional
342
+ application settings through a checkout-local environment mechanism. Do not put
343
+ secrets directly in `startCommand`, because lifecycle recovery persists the
344
+ command as metadata.
345
+
346
+ Terminal lifecycle completion is withheld until routes and their supervised
347
+ commands have been removed at Human Review or Done. If the source-state
348
+ writeback wins a crash race, startup recovery observes that terminal source
349
+ state and finishes preview teardown before terminalizing the durable lifecycle.
350
+ A startup and periodic sweep reaps only orphaned resources in the current
351
+ Factory workspace whose persisted route and process identities still match.
352
+ See the [provider evaluation](planning/preview-provider-evaluation.md)
353
+ for the decision and lifecycle contract.
354
+
355
+ To exercise the real provider lifecycle on a signed-in node, build first and
356
+ run `TAILSCALE_BIN=/path/to/tailscale node scripts/verify-tailscale-preview-e2e.mjs`.
357
+ The check drives the node's advertised `preview:tailscale-serve` action to start
358
+ a detached HTTP service, reaches it through Serve, recovers it through a fresh
359
+ node-action instance, tears it down, then proves a startup orphan sweep reaps a
360
+ second abandoned route and process while preserving unrelated Serve
361
+ configuration. Override its dedicated ports with
362
+ `FACTORY_PREVIEW_E2E_HTTPS_PORT` and `FACTORY_PREVIEW_E2E_TARGET_PORT`.
363
+
282
364
  ### Dispatching to nodes (`--backend relay`)
283
365
 
284
366
  With `--backend relay`, the factory orchestrator dispatches work through the
@@ -1,6 +1,6 @@
1
1
  import { ensureCloudSession, type CloudSession } from '@agent-relay/cloud';
2
2
  import { type LocalClonePathOptions } from '../config/local-clone-paths.js';
3
- import { createFactory, createFleet, ensureRelayBroker, type Capability, type Factory, type FactoryEventReporter, type FactoryConfig, type FleetBackend, type FleetClient, type GhRunner, type FactoryStateResolution, type LocalMountOptions, type MountClient, type ProbeCloser, type RelayfileCloudMountClientConfig, type ResolvedFactoryWorkspace } from '../index.js';
3
+ import { createFactory, createFleet, ensureRelayBroker, reapFactoryEnvironmentsOnce, type Capability, type Factory, type FactoryEventReporter, type FactoryConfig, type FleetBackend, type FleetClient, type GhRunner, type FactoryStateResolution, type LocalMountOptions, type MountClient, type ProbeCloser, type RelayfileCloudMountClientConfig, type ResolvedFactoryWorkspace } from '../index.js';
4
4
  import { type CheckFeatureMapOptions, type FeatureMapCheckReport } from '../featuremap/index.js';
5
5
  import type { FactoryIntegrationProvider } from '../ports/index.js';
6
6
  interface FleetCliDeps {
@@ -33,6 +33,8 @@ interface FleetCliDeps {
33
33
  confirmIntegrationConnect?: (provider: FactoryIntegrationProvider) => Promise<boolean>;
34
34
  openIntegrationUrl?: (url: string) => void | Promise<void>;
35
35
  featureMapCheck?: (options?: CheckFeatureMapOptions) => Promise<FeatureMapCheckReport>;
36
+ /** Hermetic verification-environment sweep for CLI tests and alternate runtimes. */
37
+ reapEnvironments?: typeof reapFactoryEnvironmentsOnce;
36
38
  }
37
39
  interface GlobalOptions {
38
40
  backend: FleetBackend;
@@ -47,6 +49,7 @@ type ParsedCommand = {
47
49
  name?: string;
48
50
  node?: 'self' | string;
49
51
  task?: string;
52
+ workflow?: string;
50
53
  model?: string;
51
54
  sessionRef?: string;
52
55
  cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}
package/dist/cli/fleet.js CHANGED
@@ -6,13 +6,14 @@ import readline from 'node:readline/promises';
6
6
  import { ensureCloudSession } from '@agent-relay/cloud';
7
7
  import { stringifyLogValue } from '../logging.js';
8
8
  import { resolveLocalFactoryConfig } from '../config/local-clone-paths.js';
9
- import { FileStateStore, RelayfileCloudMountClient, checkFactoryLoopLiveness, closeProbePr, createFactory, createFleet, createFactoryCloudEventV1, ensureRelayBroker, FactoryCloudReporter, FileFactoryCloudEventOutbox, defaultGhRunner, explicitLinkedIssueKey, githubIssuePathParts, githubWatchStatePath, isInFactoryScope, parseGithubFactoryIssue, publishFactoryMountHealth, parseOwnedBrokerAgentExitTimeoutMs, parseStandaloneBabysitTarget, readStandalonePullRequest, readLinearIssueWithCanonicalFallback, reapFactoryOrphansOnce, readFactoryLoopHeartbeat, resolveFactoryStates, stateResolutionFromIds, standaloneBabysitterAgentName, renderAgentTask, resolveFactoryWorkspace, } from '../index.js';
9
+ import { FileStateStore, RelayfileCloudMountClient, checkFactoryLoopLiveness, closeProbePr, createFactory, createFleet, createFactoryCloudEventV1, ensureRelayBroker, FactoryCloudReporter, FileFactoryCloudEventOutbox, defaultGhRunner, explicitLinkedIssueKey, githubIssuePathParts, githubWatchStatePath, isInFactoryScope, parseGithubFactoryIssue, publishFactoryMountHealth, parseOwnedBrokerAgentExitTimeoutMs, parseStandaloneBabysitTarget, readStandalonePullRequest, readLinearIssueWithCanonicalFallback, reapFactoryOrphansOnce, reapFactoryEnvironmentsOnce, readFactoryLoopHeartbeat, resolveFactoryStates, stateResolutionFromIds, standaloneBabysitterAgentName, renderAgentTask, resolveFactoryWorkspace, } from '../index.js';
10
10
  import { resolveTestGuidance } from '../dispatch/test-guidance.js';
11
11
  import { FakeFleetClient, FakeMountClient } from '../testing/index.js';
12
12
  import { GitAgentWorktreeManager } from '../git/agent-worktree.js';
13
13
  import { checkFeatureMap } from '../featuremap/index.js';
14
14
  import { loadOrCreateFactoryInstanceId, resolveFactoryInstanceName } from '../observability/instance-identity.js';
15
15
  import { ensureFactoryIntegrations, inspectFactoryIntegration, openIntegrationUrl, } from '../mount/relayfile-integration-preflight.js';
16
+ import { checkMountStaleness } from '../mount/relayfile-binary.js';
16
17
  const autoDetectedIssueSources = new WeakSet();
17
18
  const CLONE_MOUNT_PREFLIGHT_CONCURRENCY = 4;
18
19
  export async function runFleetCli(argv, deps = {}) {
@@ -89,6 +90,7 @@ export async function runFleetCli(argv, deps = {}) {
89
90
  capability: command.input.capability,
90
91
  node: command.input.node ?? 'self',
91
92
  task: command.input.task,
93
+ workflow: command.input.workflow,
92
94
  model: command.input.model,
93
95
  cwd: command.input.cwd,
94
96
  }));
@@ -109,12 +111,13 @@ export async function runFleetCli(argv, deps = {}) {
109
111
  if (!loaded)
110
112
  throw new Error('factory command requires config');
111
113
  if (command.kind === 'factory' && command.action === 'reap-orphans') {
112
- writeJson(out, await reapFactoryOrphansOnce({
114
+ const processes = await reapFactoryOrphansOnce({
113
115
  heartbeatPath: loaded.config.loop.heartbeatPath,
114
116
  registryPath: loaded.config.loop.registryPath,
115
117
  staleMs: loaded.config.loop.heartbeatStaleMs,
116
118
  fleet,
117
- }));
119
+ });
120
+ writeJson(out, { ...processes, environments: await safeEnvironmentReap(deps.reapEnvironments) });
118
121
  return 0;
119
122
  }
120
123
  // Derive the workspace from the cloud session when it isn't pinned in
@@ -299,6 +302,9 @@ function parseFleetSubcommand(args) {
299
302
  throw new Error('factory fleet spawn requires capability spawn:codex, spawn:claude, or workflow:run');
300
303
  }
301
304
  const parsed = parseFlags(flags);
305
+ if (capability === 'workflow:run' && !parsed.workflow) {
306
+ throw new Error('factory fleet spawn workflow:run requires --workflow <path>');
307
+ }
302
308
  return {
303
309
  kind: 'spawn',
304
310
  input: {
@@ -306,6 +312,7 @@ function parseFleetSubcommand(args) {
306
312
  name: parsed.name,
307
313
  node: parsed.node,
308
314
  task: parsed.task,
315
+ ...(parsed.workflow ? { workflow: parsed.workflow } : {}),
309
316
  model: parsed.model,
310
317
  sessionRef: parsed.resume,
311
318
  cwd: parsed.cwd,
@@ -361,16 +368,18 @@ export function parseGlobalOptions(argv) {
361
368
  }
362
369
  async function runFactoryCommand(command, factory, mount, fleet, config, globals, out, deps = {}, workspaceId = config.workspaceId ?? '', acceptableMountIds) {
363
370
  const mountFn = resolveLocalMountFn(deps, mount);
371
+ const mountStderr = deps.stderr ?? process.stderr;
372
+ const debugMountRefreshes = (deps.env ?? process.env).FACTORY_LOG_LEVEL?.toLowerCase() === 'debug';
364
373
  if (command.kind === 'factory') {
365
374
  if (command.action === 'start') {
366
375
  // Local mirrors are a writeback aid, not the source of truth for remote
367
376
  // issue discovery. Start their SDK-backed supervisors immediately, but
368
377
  // do not serialize durable recovery behind a stale checkout's readiness
369
378
  // timeout. The mount client reports degradation and keeps retrying.
370
- void warmStartPathMounts(mountFn, workspaceId, config, acceptableMountIds)
379
+ void warmStartPathMounts(mountFn, workspaceId, config, acceptableMountIds, mountStderr, debugMountRefreshes)
371
380
  .catch((error) => {
372
381
  const message = error instanceof Error ? error.message : String(error);
373
- process.stderr.write(`[factory] warning: background relayfile mount warmup failed: ${message}\n`);
382
+ mountStderr.write(`[factory] warning: background relayfile mount warmup failed: ${message}\n`);
374
383
  });
375
384
  const waiter = createStopSignalWaiter();
376
385
  let stoppedBySignal = false;
@@ -405,7 +414,7 @@ async function runFactoryCommand(command, factory, mount, fleet, config, globals
405
414
  }
406
415
  }
407
416
  if (command.action === 'run-once') {
408
- await ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds);
417
+ await ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds, mountStderr, debugMountRefreshes);
409
418
  writeJson(out, await factory.runOnce({ dryRun: globals.dryRun }));
410
419
  return 0;
411
420
  }
@@ -427,16 +436,7 @@ async function runFactoryCommand(command, factory, mount, fleet, config, globals
427
436
  writeJson(out, { killed: heartbeat.pid, signal: 'SIGTERM' });
428
437
  return 0;
429
438
  }
430
- if (command.action === 'reap-orphans') {
431
- writeJson(out, await reapFactoryOrphansOnce({
432
- heartbeatPath: config.loop.heartbeatPath,
433
- registryPath: config.loop.registryPath,
434
- staleMs: config.loop.heartbeatStaleMs,
435
- fleet,
436
- }));
437
- return 0;
438
- }
439
- await ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds);
439
+ await ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds, mountStderr, debugMountRefreshes);
440
440
  const removeSignalHandlers = installFactoryStopSignalHandlers(factory, {
441
441
  processLike: deps.stopSignalProcessLike,
442
442
  });
@@ -480,22 +480,13 @@ async function runFactoryCommand(command, factory, mount, fleet, config, globals
480
480
  writeJson(out, await factory.dispatch(decision, { dryRun: globals.dryRun }));
481
481
  return 0;
482
482
  }
483
- async function warmStartPathMounts(mountFn, workspaceId, config, acceptableMountIds) {
484
- const daemonMount = (async () => {
485
- try {
486
- await mountFn(workspaceId, process.cwd(), {
487
- acceptableWorkspaceIds: acceptableMountIds,
488
- });
489
- }
490
- catch (error) {
491
- const message = error instanceof Error ? error.message : String(error);
492
- process.stderr.write(`[factory] warning: could not start relayfile mount at ${process.cwd()}: ${message}\n`);
493
- }
494
- })();
495
- await Promise.all([
496
- daemonMount,
497
- ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds),
483
+ async function warmStartPathMounts(mountFn, workspaceId, config, acceptableMountIds, stderr = process.stderr, debug = process.env.FACTORY_LOG_LEVEL?.toLowerCase() === 'debug') {
484
+ const mountOpts = { acceptableWorkspaceIds: acceptableMountIds };
485
+ const [daemonRefresh, cloneRefreshes] = await Promise.all([
486
+ ensureMountPath(mountFn, workspaceId, process.cwd(), mountOpts, stderr),
487
+ ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds, stderr, debug, false),
498
488
  ]);
489
+ writeMountRefreshSummary([...(daemonRefresh ? [daemonRefresh] : []), ...cloneRefreshes], stderr, debug);
499
490
  }
500
491
  async function runStandaloneBabysitCommand(command, mount, fleet, config, globals, out, deps, workspaceId, acceptableMountIds) {
501
492
  const repo = resolveStandaloneBabysitRepo(command.repo, config);
@@ -646,25 +637,60 @@ function standaloneBabysitClonePath(repo, config) {
646
637
  * is not automatically accessible from a different directory, and agents need
647
638
  * these paths for integration writebacks (Slack, GitHub, etc.).
648
639
  */
649
- async function ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds) {
640
+ async function ensureClonePathMounts(mountFn, workspaceId, config, acceptableMountIds, stderr = process.stderr, debug = process.env.FACTORY_LOG_LEVEL?.toLowerCase() === 'debug', reportSummary = true) {
650
641
  const mountOpts = { acceptableWorkspaceIds: acceptableMountIds };
651
642
  const daemonCwd = resolve(process.cwd());
652
643
  const clonePaths = [...new Set(Object.values(config.clonePaths ?? {}).map((clonePath) => resolve(clonePath)))]
653
644
  .filter((clonePath) => clonePath !== daemonCwd);
645
+ const refreshedStaleMounts = [];
654
646
  let nextIndex = 0;
655
647
  const mountNext = async () => {
656
648
  while (nextIndex < clonePaths.length) {
657
649
  const resolved = clonePaths[nextIndex++];
658
- try {
659
- await mountFn(workspaceId, resolved, mountOpts);
660
- }
661
- catch (error) {
662
- const message = error instanceof Error ? error.message : String(error);
663
- process.stderr.write(`[factory] warning: could not start relayfile mount at ${resolved}: ${message}\n`);
664
- }
650
+ const refreshed = await ensureMountPath(mountFn, workspaceId, resolved, mountOpts, stderr);
651
+ if (refreshed)
652
+ refreshedStaleMounts.push(refreshed);
665
653
  }
666
654
  };
667
655
  await Promise.all(Array.from({ length: Math.min(CLONE_MOUNT_PREFLIGHT_CONCURRENCY, clonePaths.length) }, mountNext));
656
+ if (reportSummary)
657
+ writeMountRefreshSummary(refreshedStaleMounts, stderr, debug);
658
+ return refreshedStaleMounts;
659
+ }
660
+ async function ensureMountPath(mountFn, workspaceId, path, mountOpts, stderr) {
661
+ const resolved = resolve(path);
662
+ const statePath = join(resolved, '.integrations', '.relay', 'state.json');
663
+ const staleBefore = checkMountStaleness(statePath, workspaceId, mountOpts.acceptableWorkspaceIds);
664
+ try {
665
+ await mountFn(workspaceId, resolved, {
666
+ ...mountOpts,
667
+ ...(staleBefore.stale ? { suppressStaleRefreshLogs: true } : {}),
668
+ });
669
+ if (staleBefore.stale && !checkMountStaleness(statePath, workspaceId, mountOpts.acceptableWorkspaceIds).stale) {
670
+ return { path: resolved, reason: staleBefore.reason };
671
+ }
672
+ }
673
+ catch (error) {
674
+ const message = error instanceof Error ? error.message : String(error);
675
+ stderr.write(`[factory] warning: could not start relayfile mount at ${resolved}: ${message}\n`);
676
+ }
677
+ return undefined;
678
+ }
679
+ function writeMountRefreshSummary(refreshedStaleMounts, stderr, debug) {
680
+ if (debug) {
681
+ for (const mount of [...refreshedStaleMounts].sort((left, right) => left.path.localeCompare(right.path))) {
682
+ const suffix = mount.reason ? ` (${mount.reason})` : '';
683
+ stderr.write(`[factory] debug: refreshed stale local mount at ${mount.path}${suffix}\n`);
684
+ }
685
+ }
686
+ if (refreshedStaleMounts.length > 0) {
687
+ const ages = refreshedStaleMounts
688
+ .map(({ reason }) => reason?.match(/^last reconcile (\d+)m ago$/u)?.[1])
689
+ .filter((age) => age !== undefined)
690
+ .map(Number);
691
+ const ageSuffix = ages.length > 0 ? ` (last reconcile ~${Math.max(...ages)}m ago)` : '';
692
+ stderr.write(`[factory] refreshed ${refreshedStaleMounts.length} stale local mount(s)${ageSuffix}\n`);
693
+ }
668
694
  }
669
695
  function resolveLocalMountFn(deps, mount) {
670
696
  if (deps.ensureLocalMount)
@@ -860,7 +886,7 @@ async function buildFleet(globals, loaded, deps) {
860
886
  // An injected createFleet owns fleet construction entirely (tests), so skip the
861
887
  // real broker bootstrap.
862
888
  if (deps.createFleet) {
863
- return deps.createFleet({ backend: globals.backend, cwd, connectionPath }, { ownedBrokerAgentExitTimeoutMs: globals.agentExitTimeoutMs });
889
+ return deps.createFleet({ backend: globals.backend, cwd, connectionPath, previewConfig: loaded?.config.preview }, { ownedBrokerAgentExitTimeoutMs: globals.agentExitTimeoutMs });
864
890
  }
865
891
  // The internal backend talks to a relay broker. Reuse the one already running
866
892
  // for this workspace, or start one if none is up, so `factory <run-once|loop>`
@@ -875,7 +901,12 @@ async function buildFleet(globals, loaded, deps) {
875
901
  logger,
876
902
  env,
877
903
  });
878
- return createFleet({ backend: 'internal', cwd, connectionPath }, {
904
+ if (started && connectionPath === undefined) {
905
+ stderr.write(`[factory] no existing relay broker connection found under ${cwd} (.agentworkforce/relay/connection.json);\n` +
906
+ 'starting a NEW broker. If you expected to attach to an already-running Factory, you are likely running\n' +
907
+ 'from the wrong directory — cd to the checkout that owns .agentworkforce/relay/ and retry.\n');
908
+ }
909
+ return createFleet({ backend: 'internal', cwd, connectionPath, previewConfig: loaded?.config.preview }, {
879
910
  harnessClient: client,
880
911
  ownsBroker: started,
881
912
  ownedBrokerAgentExitTimeoutMs: globals.agentExitTimeoutMs,
@@ -883,7 +914,7 @@ async function buildFleet(globals, loaded, deps) {
883
914
  logger,
884
915
  });
885
916
  }
886
- return createFleet({ backend: globals.backend, cwd, connectionPath }, { env: deps.env });
917
+ return createFleet({ backend: globals.backend, cwd, connectionPath, previewConfig: loaded?.config.preview }, { env: deps.env });
887
918
  }
888
919
  function streamLogger(stream) {
889
920
  const write = (message, args) => {
@@ -1274,6 +1305,20 @@ function requireValue(args, index, flag) {
1274
1305
  function writeJson(out, value) {
1275
1306
  out.write(`${JSON.stringify(value, null, 2)}\n`);
1276
1307
  }
1308
+ async function safeEnvironmentReap(reap = reapFactoryEnvironmentsOnce) {
1309
+ try {
1310
+ return await reap();
1311
+ }
1312
+ catch (error) {
1313
+ // Process cleanup remains useful on hosts without kubectl or an active
1314
+ // cluster. Surface the skipped backstop without failing the whole command.
1315
+ return {
1316
+ reaped: [],
1317
+ retained: [],
1318
+ error: error instanceof Error ? error.message : String(error),
1319
+ };
1320
+ }
1321
+ }
1277
1322
  async function flushProcessOutput() {
1278
1323
  await Promise.all([
1279
1324
  flushWritable(process.stdout),