Conversation
S1 step 1 of execution-plan.md §3.S1 (issue #386 — Hermes setup parity). Behavior-unchanged extraction; both helpers move from adapter-openclaw to @origintrail-official/dkg-core so that adapter-hermes can reuse them in S2. Dependency direction is cli → adapters → core, so dkg-core (not dkg-cli) is the only valid shared home — confirmed with team-lead during S1 prep. - Added: packages/core/src/resolve-cli-package-dir.ts - Added: packages/core/src/resolve-dkg-cli.ts - Added: exports for both via packages/core/src/index.ts - adapter-openclaw/src/setup.ts now thin-re-exports resolveCliPackageDir from dkg-core (preserves the adapter's public surface) - adapter-openclaw/src/resolve-dkg-cli.ts now thin-re-exports resolveDkgCli from dkg-core (preserves the in-tree import path used by setup.ts and setup-start-daemon.test.ts) - adapter-openclaw/test/resolve-dkg-cli.test.ts mock target updated from '../src/setup.js' to the dkg-core resolve-cli-package-dir.js module path (vitest mocks need a real cross-module boundary, so the two helpers live in two separate core modules) Test results vs s1-baseline.md (post-extraction, all targeted packages): - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 974 tests — same 2 pre-existing failures as baseline (plugin-id reconcile pending, writeDkgConfig autoUpdate edge); zero new failures - @origintrail-official/dkg-adapter-hermes: 1 file, 60 tests — green - @origintrail-official/dkg-node-ui: 20 files — green Note on @origintrail-official/dkg (CLI) tests: blocked on a baseline infrastructure issue (Hardhat global setup fails to spin up the local chain on port 9548), which is independent of this extraction. Flagging to team-lead in parallel.
S1 step 2 of execution-plan.md §3.S1 (issue #386 — Hermes setup parity). Behavior-unchanged extraction; `startDaemon` and its private helpers (`hasLocalRepoForCli`, `blueGreenMigrationMayRunDuringStart`, `daemonStartSpawnOptions`, `isProcessRunning`) move from adapter-openclaw to @origintrail-official/dkg-core so adapter-hermes can reuse them in S2 to satisfy issue #386 acceptance criterion 3 ("--no-start truly means do not start the DKG daemon"). - Added: packages/core/src/daemon-lifecycle.ts - Added: export via packages/core/src/index.ts - adapter-openclaw/src/setup.ts now thin-re-exports startDaemon from dkg-core (preserves the adapter's public surface) - Removed unused imports from adapter-openclaw/src/setup.ts: spawnSync, SpawnSyncOptions, lstatSync, blueGreenSlotReady, findPackageRepoDir, resolveDkgCli; kept `realpathSync` (still used by step 8b migration cleanup) and `sleep` (still used by readWalletsWithRetry — extracted in S1.3) - adapter-openclaw/test/setup-start-daemon.test.ts mock target updated from '../src/resolve-dkg-cli.js' to dkg-core's resolve-dkg-cli.js dist path (vitest needs to intercept the real cross-module call path inside daemon-lifecycle.ts) The `[setup] ...` console.log prefix is preserved verbatim in the new core module so user-visible output is unchanged. Test results vs s1-baseline.md (post-extraction): - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 974 tests — same 2 pre-existing failures as baseline; zero new failures - @origintrail-official/dkg-adapter-hermes: 1 file, 60 tests — green - @origintrail-official/dkg-node-ui: 20 files — green - @origintrail-official/dkg (CLI): curated subset gate per s1-baseline.md (Hardhat-dependent CLI tests deferred to release-readiness)
S1 step 3 of execution-plan.md §3.S1 (issue #386 — Hermes setup parity). Extract `readWallets`, `readWalletsWithRetry`, `logManualFundingInstructions`, `sleep`, and a new orchestrator `fundWalletsBestEffort({ network, callerId, didStartDaemon })` from adapter-openclaw to @origintrail-official/dkg-core/faucet-orchestration.ts so adapter-hermes can reuse them in S2 (issue #386 acceptance criterion: "--no-fund truly means do not perform faucet funding"; H-AC-19 will assert the 5×1s retry semantics in S2). - Added: packages/core/src/faucet-orchestration.ts with `readWallets`, `readWalletsWithRetry`, `logManualFundingInstructions`, `fundWalletsBestEffort` (orchestrator wrapping the faucet URL check, retry-after-daemon-start, requestFaucetFunding call, and manual-curl fallback that the OpenClaw runSetup body used inline) - Added: exports via packages/core/src/index.ts - adapter-openclaw/src/setup.ts now thin-re-exports the four helpers and refactors its runSetup Step 6 block to call `fundWalletsBestEffort` instead of inlining the orchestration. The body of the orchestrator is lifted line-for-line from the previous inline block — behavior-equivalent - adapter-openclaw/test/setup.test.ts: dual-mock pattern for `requestFaucetFunding` (barrel + dkg-core/dist/faucet.js), since `fundWalletsBestEffort` inside core calls `requestFaucetFunding` via intra-package import that the barrel mock alone wouldn't intercept. Same vitest mock-boundary pattern used in S1.1 + S1.2 The 5×1s retry semantics in `readWalletsWithRetry` are preserved exactly. Faucet failures stay non-fatal; the orchestrator never throws. Test results vs s1-baseline.md (post-extraction): - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 974 tests — same 2 pre-existing failures as baseline; zero new failures - @origintrail-official/dkg-adapter-hermes: 1 file, 60 tests — green - @origintrail-official/dkg-node-ui: 20 files — green - @origintrail-official/dkg (CLI): curated subset gate per s1-baseline.md
…-core S1 step 4 of execution-plan.md §3.S1 (issue #386 — Hermes setup parity). Extract the agent-agnostic field-level merge body of OpenClaw's `writeDkgConfig` (`name`, `apiPort`, `nodeRole`, `contextGraphs`, `auth`, `relay`, `autoUpdate.enabled` mirroring) into @origintrail-official/dkg-core/ensure-dkg-node-config.ts so adapter-hermes can bootstrap a missing `~/.dkg/config.json` on fresh setup (issue #386 acceptance criterion: "Fresh user flow: install package → `dkg hermes setup` → ..."). Ordering invariant — load-bearing per execution-plan.md §3.S1 step 4 and risk-register §8: OpenClaw's `writeDkgConfig` MUST keep running `migrateLegacyOpenClawTransport`, the `openclawAdapter`/`openclawChannel` deletes, and `pruneNetworkPinnedDefaults` BEFORE delegating to `ensureDkgNodeConfig`. Documented in the helper docstring + a new regression test `ordering invariant: legacy migration + prune run before ensureDkgNodeConfig field merge` that asserts four signals from one fixture (migration ran, deletes ran, prune ran, post-migration field merge respected existing). Single test catches any future refactor that flips the order. - Added: packages/core/src/ensure-dkg-node-config.ts with `ensureDkgNodeConfig({ agentName, network, apiPort, existing, overrides })`, `DkgNodeNetworkConfig`, `DkgNodeConfigOverrides`, `EnsureDkgNodeConfigOptions` - Added: exports via packages/core/src/index.ts - adapter-openclaw/src/setup.ts: `writeDkgConfig` shrinks to the read + log + migrate + delete + prune + delegate sequence; the field-level merge body that produced `config` and wrote it to disk now lives in dkg-core. Behavior-equivalent — body lifted line-for-line - adapter-openclaw/test/setup.test.ts: added the ordering-invariant regression test described above The pre-existing baseline failure `writeDkgConfig > mirrors only autoUpdate.enabled from network default and preserves existing pins` is unaffected by this extraction (it failed the same way before and after). Test results vs s1-baseline.md (post-extraction): - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 975 tests (+1 ordering-invariant test) — same 2 pre-existing baseline failures, zero new failures - @origintrail-official/dkg-adapter-hermes: 1 file, 60 tests — green - @origintrail-official/dkg-node-ui: 20 files — green - @origintrail-official/dkg (CLI): curated subset gate per s1-baseline.md
S1 step 5 of execution-plan.md §3.S1 (issue #386 — Hermes setup parity) and final step of S1. The per-integration UI attach-job machinery (`pendingOpenClawUiAttachJobs` Map + `scheduleOpenClawUiAttachJob` + `cancelPendingLocalAgentAttachJob` + `isOpenClawUiAttachCancelled`) moves to `packages/cli/src/daemon/local-agent-attach-jobs.ts` so adapter-hermes' S3 work can reuse the same scheduler keyed on `'hermes'` instead of `'openclaw'`. The Map keying is already on a string `integrationId`, so the migration is a rename: same body, OpenClaw substring stripped from the public symbols. - Added: packages/cli/src/daemon/local-agent-attach-jobs.ts with `scheduleAttachJob(integrationId, task, onAttachScheduled)`, `cancelPending(integrationId)`, `isCancelled(job)`, and the `PendingAttachJob` type - daemon/openclaw.ts: deleted the inline Map + body of the three helpers; kept the OpenClaw-named exports as backwards-compat thin wrappers around the new generic implementations. Existing OpenClaw call sites in `local-agents.ts` continue to import the legacy names unchanged (no edit to local-agents.ts in this slice — node-ui-engineer's S3 work can choose to retarget to the generic names, or leave them as backwards-compat re-exports indefinitely) This deferred edit means S1 did not need to consume the §2 file-ownership exception for `local-agents.ts` after all. node-ui-engineer's S3 single- writer ownership of that file is uninterrupted. Test results vs s1-baseline.md (post-extraction): - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 975 tests — same 2 pre-existing baseline failures, zero new failures - @origintrail-official/dkg-adapter-hermes: 1 file, 60 tests — green - @origintrail-official/dkg-node-ui: 20 files — green - @origintrail-official/dkg (CLI) curated subset (per s1-baseline.md): 4 files, 148 tests — green (`hermes-setup-cli-args.test.ts`, `openclaw-setup-cli-args.test.ts`, `daemon-hermes.test.ts`, `daemon-openclaw.test.ts`); the 84-test `daemon-openclaw.test.ts` exercises the attach-jobs surface directly and validated the rename
S2 step 1 of execution-plan.md §3.S2 (issue #386 — Hermes setup parity). Extends `HermesSetupCliOptions` and `NormalizedHermesSetupOptions` with the three flags S2 needs to drive the new `runHermesSetup` orchestrator in adapter-hermes (S2 step 3 — coming next). - `start?: boolean` — already in the interface; kept for clarity - `fund?: boolean` — new. Commander `--no-fund` / `--fund` convention, defaults to true. Mirrors OpenClaw `OpenClawSetupCliOptions.fund` - `preserveProvider?: boolean` — new. `--preserve-provider` (alias `--no-replace-provider`) opt-out for the issue #386 replace-by-default contract; defaults to false (replace) per setup-entrypoint-contract.md §2 `normalizeHermesSetupOptions` populates both new fields. The action handler `hermesSetupAction` already passes the full normalized object through `deps.runSetup`, so the new fields flow to the adapter automatically. Tests added in `cli/test/hermes-setup-cli-args.test.ts`: - H-AC-20: `--no-fund` round-trip + default-true - H-AC-30 (unit half): `--preserve-provider` round-trip + default-false (the verbatim throw-message half lives in adapter integration tests in S4) - H-AC-15: `--no-start` + `--no-fund` + `--dry-run` combine without error - H-AC-58 (unit half): `--daemon-url` + `--port` round-trip independently (the port-conflict warn fires inside `runHermesSetup` — see S2.5) The two pre-existing tests that snapshot the full default shape were updated to include `fund: true` and `preserveProvider: false`. New defaults are wire-additive — existing CLI invocations land at the same effective behavior post-S2 (replace-by-default and fund are the new behaviors, gated behind the orchestrator coming in S2 step 3). Test results vs s1-baseline.md curated CLI subset: - 4 files, 152 tests (148 baseline + 4 new) — green
S2 step 2 of execution-plan.md §3.S2 (issue #386 — Hermes setup parity). Wire the new flags from S2 step 1 (commit 1b12a543) into commander on the `dkg hermes setup` block (`packages/cli/src/cli.ts:1811-1849`). - `--no-fund` / `--fund`: standard commander boolean-flag pair, defaults to `fund: true`. Mirrors OpenClaw's identical pair. - `--preserve-provider`: opt-out of the issue #386 replace-by-default contract; sets `preserveProvider: true`. - `--no-replace-provider`: alias for `--preserve-provider`. Commander registers this as the negation of an implicit `--replace-provider` (parsed as `replaceProvider: false`); the action handler collapses that into the canonical `preserveProvider: true` so the normalizer + adapter see a single source of truth. Documented inline. `--no-start` was already registered (line 1823); kept unchanged. The S2 step 1 normalizer already populates `start`/`fund`/`preserveProvider` in `NormalizedHermesSetupOptions`, so the new flags flow end-to-end through `hermesSetupAction → deps.runSetup` to the adapter (which will consume them in S2 step 3 — the `runHermesSetup` orchestrator). Test gate (curated CLI subset per s1-baseline.md): - 4 files, 152 tests — green (no behavior change yet; wiring only)
…pers S2 step 3 of execution-plan.md §3.S2 (issue #386 — Hermes setup parity). Land the canonical entrypoint `runHermesSetup` per setup-entrypoint- contract.md §1-§3, wired to consume the S1 helpers (`startDaemon`, `fundWalletsBestEffort`, `ensureDkgNodeConfig`). `runSetup` is refactored into a thin throw-on-error wrapper around `runHermesSetup` so existing CLI handlers + setup-entry.mjs lazy exports keep working. Behavior, in order: 1. Bootstrap `~/.dkg/config.json` via `ensureDkgNodeConfig` (S1.4) when missing AND not dry-run. Best-effort — surfaces a warning on failure rather than throwing. 2. Start the DKG daemon via `startDaemon` (S1.2) when `start !== false` AND `!dryRun`. Issue #386 acceptance criterion 3: "--no-start truly means do not start the DKG daemon". 3. Fund wallets via `fundWalletsBestEffort` (S1.3) when `fund !== false` AND `!dryRun`. Issue #386 acceptance criterion: "--no-fund truly means do not perform faucet funding". Faucet failures are non-fatal — surface as warnings. 4. Run existing `setupHermesProfile` body (preserves dryRun short-circuit; S2 step 4 hardens that). 5. Daemon registration probe via `connectDaemonBestEffort` — decoupled from --no-start per issue #386 brief (the probe is best-effort; it lets operators register against an already- running daemon while skipping the new daemon-start step). 6. Verify via `verifyHermesProfile` when `verify !== false`. 7. Compute `HermesSetupResult` with full `transport` always populated per contract §3 (lifted from `state.bridge` with the DEFAULT_HERMES_API_SERVER_URL fallback). Port-conflict warn (contract §2 Open Question 1, H-AC-58 in S2 step 6): fires when both `--port` and `--daemon-url` are passed and the URL host:port disagrees. First-wins on `daemonUrl`. Verbatim warn string: "daemon URL host:port (<host>:<urlPort>) does not match --port (<port>); using URL". `HermesSetupRequest` and `HermesSetupResult` types added to types.ts matching contract §2-§3 verbatim. `HermesSetupState.priorMemoryProvider` is defined as optional (S4 populates it). Provider-replacement implementation is intentionally NOT in this commit — that's S4's work. The result-shape `providerSwap` field is defined so the daemon route consumer doesn't need to change between S2 → S3 → S4. Network config loading: inlined `loadHermesNetworkConfig` per helper-reuse-rec §43-46 (Hermes-only copy-shape; the CLI lookup itself uses the shared `resolveCliPackageDir` from S1.1). `HermesCliOptions` extended with `fund?`, `preserveProvider?`, `signal?`, `invokedBy?` so the CLI bridge from cli/src/hermes-setup.ts (post-S2.1) flows the new flags through. Test fixes — 5 legacy tests in hermes-adapter.test.ts assumed pre-S2 behavior where `runSetup` only triggered the registration probe (no actual daemon spawn / faucet). They now pass `start: false, fund: false` explicitly, matching the test's original "registration-against-already- running-daemon" intent. The new daemon-start + faucet behaviors will have dedicated coverage in S2 step 6 + S2 orchestration tests. Test results vs s1-baseline.md curated CLI subset + adapter packages: - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 975 tests (same 2 pre-existing baseline failures, zero new) — green - @origintrail-official/dkg-adapter-hermes: 60 tests — green - @origintrail-official/dkg (CLI) curated subset: 4 files, 152 tests — green
S2 step 4 + 5 of execution-plan.md §3.S2 (issue #386 — Hermes setup parity). Three new H-AC test cases land in `packages/adapter-hermes/test/hermes-adapter.test.ts`. The implementations they validate already shipped in S2.3 (commit f3b2f317); these tests pin the contract guarantees. - H-AC-21: `--dry-run` does not write any file under `hermesHome`. Pre/post snapshot of the directory contents must match. Brief explicitly calls out "no `config.yaml.bak.*`" — asserted via a pattern check on every entry. - H-AC-25: `--dry-run` still returns a populated `result.state` so callers can preview what would be written; no actual files exist on disk for any path in `state.managedFiles`. - H-AC-58: when both `--port` and `--daemon-url` are passed and the URL host:port disagrees, the orchestrator emits a `console.warn` with the verbatim string from `setup-entrypoint-contract.md` §2 Open Question 1, AND `result.state.daemonUrl` reflects the URL (first-wins on `daemonUrl`). Note on contract §5 "writes setup-state.json even in dry-run" concern: the H-AC-21 test confirmed the orchestrator already short-circuits all write paths under dry-run. `setupHermesProfile` early-returns on `plan.dryRun` before any write; `bootstrapDkgNodeConfig` / `startDaemon` / `fundWalletsBestEffort` / `connectDaemonBestEffort` are all gated on `!dryRun` in `runHermesSetup`. No bug to fix; the guarantee holds by construction. Documented in test comments. Test results: - @origintrail-official/dkg-adapter-hermes: 63 tests (60 baseline + 3 new H-AC) — green
Mirrors runOpenClawUiSetup at packages/cli/src/daemon/openclaw.ts:349. Dynamic import of adapter barrel preserves daemon startup in fresh workspace checkouts where adapter dist/ has not been built yet. Threads UI-only fields per setup-entrypoint-contract.md §2: start:false, verify:false, signal, invokedBy:'ui', nodeSkillContent loaded from CLI's bundled SKILL.md. Curated gate: 139/139 (84 daemon-openclaw + 55 daemon-hermes) green — behavior unchanged since no consumer wired yet (S3 step 2).
S4 step 2 of execution-plan.md §3.S4 (issue #386 — Hermes setup parity). Flips the pre-#386 throw-on-conflict default in `ensureManagedProviderBlock` to a replace-with-backup-and-capture flow. Throw is preserved verbatim behind `--preserve-provider` (`HermesSetupOptions.preserveProvider: true`) so operators who want the old behavior can opt back in (H-AC-30 adapter-half). Behavior: - Default (no `--preserve-provider`): when a non-DKG `memory.provider` is configured in `<hermesHome>/config.yaml`, write a timestamped backup at `<hermesHome>/config.yaml.bak.<unix-ts-ms>` containing the original bytes verbatim, capture `{ provider, configBackupPath, capturedAt }` into the in-memory swap result, then proceed with the existing managed-block rewrite. S4 step 3 (`restoreHermesProfile`) consumes the captured snapshot. - `preserveProvider: true`: throw the canonical "Refusing to replace existing Hermes memory.provider: <name>" verbatim per H-AC-30 (string preserved from pre-#386 code so external grep / log scrapers stay stable). - First-wins on capture: re-runs after a prior swap do NOT re-capture and do NOT write a second backup. The first install owns the snapshot. Mirrors OpenClaw's `previousMemorySlotOwner` first-wins semantics (parity-matrix.md Layer 4 row "Idempotency on re-run"). - Dry-run preserved: `setupHermesProfile` early-returns on `plan.dryRun` BEFORE `ensureManagedProviderBlock` runs, so neither the backup nor the rewrite touches disk under `--dry-run` (S2.4 H-AC-21 already pinned this; H-AC-26 in the deferred set will assert the same under a non-DKG-provider pre-seed). `HermesSetupOptions.preserveProvider` extended; `toSetupOptions` threads from `HermesCliOptions.preserveProvider`. `planHermesSetup`'s plan-warning emission is consolidated — the canonical throw now lives inside `ensureManagedProviderBlock` (one path, one message). The two pre-existing tests that asserted the throw-on-conflict default (`detects provider conflicts and preserves user config`, `detects provider conflicts when the top-level memory block has an inline comment`) are renamed and updated to pass `preserveProvider: true`, preserving their original assertions verbatim while documenting the opt-in mechanism. Six new H-AC tests landed: - H-AC-27: replaces existing non-DKG provider with managed DKG block - H-AC-28: replacement writes timestamped backup with verbatim bytes - H-AC-29: replacement captures `priorMemoryProvider` in setup-state - H-AC-29 (negative): fresh install does not populate priorMemoryProvider - H-AC-30 (adapter): preserveProvider:true throws verbatim message; no backup - H-AC-31: re-run after replacement does not take a second backup (first-wins capture) S4 step 1 (the `priorMemoryProvider` field on `HermesSetupState`) was already added in S2.3 (commit f3b2f317) per the plan note. This commit populates it. S4 step 3 (`restoreHermesProfile` primitive) is the next commit. Test results vs s1-baseline.md gates: - @origintrail-official/dkg-core: 33 files, 550 tests — green - @origintrail-official/dkg-adapter-openclaw: 22 files, 975 tests (same 2 pre-existing baseline failures, zero new) — green - @origintrail-official/dkg-adapter-hermes: 69 tests (63 baseline + 6 new H-AC) — green - @origintrail-official/dkg (CLI) curated subset: 4 files, 152 tests — green
… backup-file fallback S4 step 3 of execution-plan.md §3.S4 (issue #386 — Hermes setup parity). Authored `restoreHermesProfile(req: HermesRestoreRequest): HermesRestoreResult` per setup-entrypoint-contract.md §6 + QA addendum §10C #1 (post-restore verification). The primitive consumes the `priorMemoryProvider` snapshot captured by S4.2's replace-by-default branch and puts `<hermesHome>/config.yaml` back to its pre-replacement state. Behavior: 1. `path: 'noop'` — no priorMemoryProvider snapshot in setup-state (fresh install or already-DKG before setup). Idempotent: safe to call when there's nothing to restore. 2. `path: 'surgical'` — remove the managed block, then either rewrite the first remaining active provider line OR insert a `provider: <captured>` line into an existing `memory:` block when no active provider line remains (typical post-replace state since `insertManagedProviderIntoMemoryBlock` consumed the original line). Preserves user edits made to config.yaml after setup. Verified post-restore via `findConfiguredMemoryProvider(post) === captured.provider`. 3. `path: 'backup-file'` — surgical failed; atomic rename of the captured backup file over config.yaml. Whole-file restore (loses post-setup user edits but is the safety net). 4. `path: 'failed'` — both paths failed (e.g. operator deleted the backup file AND the active config has no rewriteable shape). Populated `restoreError` describes both failures. The primitive is intentionally independent of `disconnectHermesProfile` per contract §6: the daemon's `reverseHermesSetupForUi` (S3) calls disconnect first, then restore; restore failure does NOT roll back the disconnect. Integration stays disconnected; restore failure surfaces as a `runtime.lastError` warning, not an `'error'` runtime status. Types added to types.ts: `HermesRestoreRequest`, `HermesRestoreResult` (verbatim from contract §6). Both exported from the package barrel. Four new H-AC tests in hermes-adapter.test.ts: - H-AC-34: restoreHermesProfile via surgical path after replacement (asserts path === 'surgical', restoredProvider matches captured, managed block gone, captured provider re-inserted into memory block) - H-AC-35: backup-file fallback when surgical path fails (simulates user deleting the memory: block between setup and restore; asserts whole-file restore matches original bytes verbatim) - H-AC-36: returns failed when both paths fail (simulates operator cleanup deleting the backup file AND active config losing its rewriteable shape; asserts ok:false, path:'failed', and error message names both failure paths) - noop test: fresh install has no priorMemoryProvider; restore returns ok:true, path:'noop', no restoredFrom or restoredProvider S4 step 4 (CLI `--restore-provider` flag wiring on `dkg hermes disconnect`) is the next commit. S4 step 5 (uninstall hook) follows. Test results vs s1-baseline.md gates: - @origintrail-official/dkg-adapter-hermes: 73 tests (69 baseline + 4 new H-AC) — green Note re node-ui-engineer's parallel S3 work: `local-agents.ts`, `local-agents.test.ts`, `daemon/routes/local-agents.ts` etc. continue to show modified in `git status` — their S3 commits will land separately. Single-writer ownership preserved per §2.
…isconnect
reverseHermesSetupForUi now performs disconnect → restore in sequence
per setup-entrypoint-contract.md §6. Restore failure does NOT roll back
the disconnect: integration stays runtime.status:'disconnected', and
the failure surfaces as a runtime.lastError warning that the UI's
warning-chip path (S3 step 5, PanelRight.tsx) renders as
warning-not-error. The warning-chip-on-disconnected branch in the
PUT handler honors this contract by reading restoreError off the
return value and folding it into the disconnected patch's lastError
rather than catching a throw.
Disconnect-proper failures (the real reverseHermesSetupForUi throw
path) continue to surface as runtime.status:'error' as before.
restoreHermesProfile resolution: prefer deps injection (test stubs);
otherwise dynamic-import-and-feature-detect from
@origintrail-official/dkg-adapter-hermes. The feature detect is
defensive against test mocks that spread-replace the module without
re-exporting every property — failed property access falls through to
a noop restore returning { ok: true, path: 'noop' }. Real
restoreHermesProfile primitive landed in S4 commit 3a0d86ef; the
feature detect now finds it on the live import path.
NOTE on commit provenance: S3 step 2 (daemon Hermes branch wiring +
two PR-#315 baseline test updates in daemon-hermes.test.ts) was
co-mingled into S4 commit 02cf506c during a staging-race rather than
landing in its own commit per file-ownership table. Functionality
verified intact; provenance issue flagged to team-lead for arbitration.
Curated gate: 139/139 (84 daemon-openclaw + 55 daemon-hermes) green.
… lastError Adds an adapter-agnostic warning surface in the 'Connect Another Agent' section of PanelRight: when an integration record carries a `runtime.lastError` (mapped to `integration.error` by api.ts:1515), render it as a v10-local-agent-warning offline chip below the detail line. The disconnect-with-restore-failure path lands a Hermes integration with `enabled: false` + `runtime.status: 'disconnected'` + `runtime.lastError: 'Hermes provider restore failed: ...'` per setup-entrypoint-contract.md §6 and S3 step 4 wiring. The mapper at api.ts:1469-1475 routes that combination to UI status 'available' (integration appears in 'Connect Another Agent') with detail = runtime.lastError. The new warning chip surfaces the same lastError explicitly so the user sees the restore-failure context, not just an 'available to reconnect' affordance. Implementation choice: render adapter-agnostic rather than gating on `integration.id === 'hermes'`. OpenClaw doesn't surface lastError through the disconnect path today, but if it ever does the same chip renders for free with no extra branching. The data-testid lets panel-right.logic.test.ts target the chip per-integration for H-AC-47b verification. Curated gate: 13/13 panel-right tests green.
…stall always restores S4 steps 4 + 5 of execution-plan.md §3.S4 (issue #386 — Hermes setup parity). - packages/adapter-hermes/src/setup.ts: - HermesCliOptions gains optional restoreProvider (CLI-only; UI Disconnect always restores via daemon route per setup-entrypoint-contract.md §6). - runDisconnect: when restoreProvider is true, calls restoreHermesProfile after disconnectHermesProfile. Restore failure does NOT roll back disconnect — surfaces as a console.warn and printRestore output. Dry-run prints "Would restore" and skips. - runUninstall: unconditionally calls restoreHermesProfile BEFORE uninstallHermesProfile (uninstall removes setup-state.json which holds the priorMemoryProvider snapshot). Per H-AC-39. Dry-run prints "Would restore" and skips. - New printRestore helper formats the HermesRestoreResult { path, restoredFrom?, restoredProvider?, restoreError? } discriminator. - packages/cli/src/cli.ts: - hermes disconnect command gains --restore-provider flag. Default behavior unchanged (disconnect-only). Test gate (curated CLI subset per s1-baseline.md): - @origintrail-official/dkg-adapter-hermes: 73/73 green - (CLI subset re-run not affected — flag wiring only; disconnect-action behavior covered by adapter tests) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
S4 step 6 adapter-half (issue #386, execution-plan.md §3.S4 step 6). Closes the four H-AC rows from the matrix that exercise adapter-level behavior introduced in S4.2/S4.3 (replace-by-default + restore) + S4.4/S4.5 (uninstall hook in commit ea7f2201). All four pass against the implementation already on disk; no source-side changes. - H-AC-32: replacement is byte-equivalent across re-runs (idempotency on top of replace-by-default). Asserts byte-equal config.yaml between consecutive runs on the now-DKG-selected profile. - H-AC-33: replacement on a YAML config that already has `provider: dkg` marked-non-managed — adopted into the managed block via `markExistingDkgProvider` without writing a backup or capturing a priorMemoryProvider (no actual swap occurred). - H-AC-38: disconnect on a profile with no captured priorMemoryProvider — `restoreHermesProfile` returns `path: 'noop'`, ok:true. - H-AC-39: uninstall after replacement — restore-then-uninstall order consumes the captured backup, restores the prior provider line in config.yaml, and removes adapter-owned artifacts (dkg.json, plugin dir, .dkg-adapter-hermes state dir). Mirrors the runUninstall flow team-lead landed in ea7f2201. The 21 S2-deferred orchestration tests (H-AC-02/03/12/13/14/16/17/18/19/22/23/24/50) will land in a separate commit on `cli/test/hermes-setup-orchestration.test.ts`. Test results vs s1-baseline.md gates: - @origintrail-official/dkg-adapter-hermes: 77 tests (73 baseline + 4 new H-AC) — green
S4 step 6 orchestration half (issue #386, execution-plan.md §3.S4 step 6 + S2 deferred-test obligation from `s2-deferred-tests.md`). New test file `packages/cli/test/hermes-setup-orchestration.test.ts` landing the most impactful subset of the 21 deferred S2 step 6 rows. Covers the orchestrator-level wiring between `runHermesSetup` and the S1 dkg-core helpers (`startDaemon`, `requestFaucetFunding`): - H-AC-12: --no-start does not invoke startDaemon (+ positive control: default flags DO invoke it exactly once) - H-AC-16: --no-fund does not invoke requestFaucetFunding - H-AC-22: --dry-run does not invoke startDaemon - H-AC-23: --dry-run does not invoke requestFaucetFunding - H-AC-24: --dry-run does not invoke the daemon-registration probe (no fetch calls escape under dry-run) - H-AC-50: --port out-of-range rejects without invoking startDaemon The dual-mock pattern (dkg-core barrel + dist/faucet.js) mirrors the adapter-openclaw faucet test established in S1.3; documented in the file header for reviewer context. Tests exercise `runHermesSetup` directly per setup-entrypoint-contract.md (rather than the action handler) to lock the canonical entrypoint surface. The remaining 15 deferred rows (H-AC-02/03/13/14/17/18/19 etc.) need either real-daemon fixtures (out of scope per execution-plan §6) OR deeper DI refactoring of `runHermesSetup` (would require its own slice). Documented in s2-deferred-tests.md for the QA pre-#15 sweep. Test results vs s1-baseline.md curated CLI subset: - packages/cli/test/hermes-setup-orchestration.test.ts: 7/7 — green - adapter-hermes: 77 tests (post-d38731a9) — green - adapter-openclaw, dkg-core, node-ui: unchanged from baseline
Adds test-matrix.md group H + I rows that pin the new S3 contract
behaviors authored in 367a0bc8 (shim), 02cf506c (daemon Hermes
branch — see commit-provenance.md), 8f16ef15 (restore wiring), and
c840d14c (warning chip).
CLI gate (cli/test/daemon-hermes.test.ts):
- H-AC-37: UI Disconnect preserves chat history (no slot deletion in
DKG). Asserts disconnect call args are profile-only with no
chat-session URI fanout — restoreHermesProfile and
disconnectHermesProfile both stay adapter-side and never reach
into DKG memory slots.
- H-AC-40: UI Connect invokes runHermesUiSetup with the
contract-required AbortSignal (per setup-entrypoint-contract.md §2).
- H-AC-41: UI Connect transitions to runtime.status:'error' when
runHermesSetup returns ok:false / status:'error' (verifyHermesProfile
failure surface).
- H-AC-42: UI Connect attach is cancellable mid-flight via the
scheduled job's AbortController; cancelPending('hermes') propagates
signal.aborted to the in-flight setup function.
- H-AC-43: Notice copy is the verbatim cycle-1-finalized
'Hermes setup started. This chat tab will come online automatically
once Hermes finishes setting up.' wording.
- H-AC-44: Concurrency — second Connect during in-flight job does NOT
double-fire setup; second caller observes the in-flight job and
receives the 'already in progress' notice.
- H-AC-46: UI Refresh signature never accepts a setup injection
point — non-invocation guarantee enforced at the type level.
- H-AC-47b: UI Disconnect surfaces restoreError as a
reverseHermesSetupForUi return-value warning while the disconnect
itself succeeds (contract §6 'restore failure does NOT roll back
disconnect').
UI gate (node-ui/test/panel-right.logic.test.ts):
- H-AC-45: Connect Hermes button shows 'Connecting...' while connect
is in flight.
- H-AC-47: Refresh + Disconnect buttons are rendered when an
integration is connected and selected.
- H-AC-47b: Warning chip surfaces lastError on disconnected
integration in the 'Connect Another Agent' tab; chip carries the
restoreError text and is targetable via data-testid; Connect
button stays enabled for retry.
Tests added pull only from each H-AC's narrowest contract surface so
they don't entangle with adapter-side implementation choices that S4
may still iterate on (e.g. surgical-vs-backup-file restore path
ordering is asserted at the adapter level by S4, not duplicated here).
Curated gates:
- @origintrail-official/dkg cli (daemon-hermes + daemon-openclaw):
147/147 green (was 139 baseline; +8 H-AC additions in daemon-hermes).
- @origintrail-official/dkg-node-ui (panel-right.logic +
panel-right.component): 16/16 green (was 13 baseline; +3 H-AC
additions in panel-right.logic).
E2E spec at packages/node-ui/e2e/specs/hermes-connect.spec.ts covers H-AC-06 (fresh user clicks Connect Hermes from right panel) and H-AC-11 (existing user with stored profile lands chat-ready without re-Connect). Two cases share the post-condition (Hermes integration reaches chat-ready) but differ in pre-conditions, exactly per test-matrix.md group A/B. Implementation note: the spec uses Playwright `page.route` interception of `/api/local-agent-integrations/*` and `/api/hermes-channel/health` rather than spawning a real daemon + Hardhat chain + Hermes gateway. Per execution-plan.md §4 last paragraph: 'If the e2e harness is too brittle for CI, downgrade to manual sanity check and document — don't get stuck on infrastructure.' Spawning the daemon + chain for two e2e cases is exactly the infrastructure investment that section warns against — the existing e2e specs are all UI-only against the Vite dev server with mocked routes, and adding a daemon-spawning harness for two cases would be disproportionate and brittle on CI. The interception spec gives CI signal on the click-to-state-transition flow (which is what 'click-to-chat-ready' is really asserting at the UX level). The companion `agent-docs/hermes-parity/manual-sanity-checks.md` documents the full live-daemon path that QA drives during release-readiness, with explicit pass criteria for: - H-AC-06 live (fresh user, real `runHermesSetup` invocation, real backup-with-prior-provider-capture). - H-AC-11 live (existing user, `runHermesSetup` invoked from daemon Connect, real notice copy verbatim check). - Disconnect → restore live (real `restoreHermesProfile`, chat history persistence across disconnect/reconnect cycle). - Restore failure live (manual backup-file deletion to force the contract §6 'restore failure does not roll back disconnect' path). - --no-start / --no-fund / --dry-run live verification (the test-matrix.md gate-criterion 3 sanity check). Co-author: qa-engineer (per execution-plan.md §2 file-ownership table — 'qa drives the actual run as part of #15').
…ression tests Addresses adversarial-findings.md vectors 1, 5, 6 (issue #386, S4 close). VECTOR 6 BUG FIX (option 2 from the findings doc): Reorder `setupHermesProfile` so `setup-state.json` is written with the intended `priorMemoryProvider` BEFORE the destructive `ensureManagedProviderBlock` rewrite. A SIGINT (or crash, or power loss) between the two writes now leaves recoverable state on disk: re-run sees `existingState.priorMemoryProvider` populated, takes the first-wins branch unchanged, and `restoreHermesProfile` finds the captured backup at the recorded path. Pre-fix flow (`02cf506c` + `3a0d86ef`): L208-209 mkdirs → L215 dkg.json → L233 plugin dir → L237 ensureManagedProviderBlock (writes .bak.<ts> + managed config.yaml) → L263 setup-state.json (priorMemoryProvider). Post-fix flow (this commit): L208-209 mkdirs → L215 dkg.json → L233 plugin dir → peekProviderSwapIntent (READS only; no writes) → setup-state.json (priorMemoryProvider intent + updatedAt) → ensureManagedProviderBlock (writes .bak.<ts> at the pre-computed path + managed config.yaml) → setup-state.json refresh (updatedAt only). Implementation: - Added `peekProviderSwapIntent(configPath, { preserveProvider, nowMs })` that returns the same `{ swap }` shape as `ensureManagedProviderBlock` but performs zero writes. The `nowMs` parameter (defaults to `Date.now()`) lets the caller pre-compute the backup path before persisting it to setup-state.json. - Extended `ensureManagedProviderBlock` with an optional `intendedSwap` option. When supplied, the function honors the pre-computed `configBackupPath` instead of generating a new one — eliminates the clock-skew window between peek and execute. - `setupHermesProfile` now writes setup-state.json TWICE: once before the destructive rewrite (with the swap intent), once after (refresh `updatedAt` only). Both writes use the first-wins `priorMemoryProvider` from `existingState ?? intendedSwap`. Also fixes two pre-existing TS errors in `runDisconnect` / `runUninstall` (commit ea7f2201) that referenced `setupOptions.profile` where the field is `setupOptions.profileName`. Build now passes. H-AC-26 ADVERSARIAL TEST (vector 1 prevention proof): Pre-seed `<hermesHome>/config.yaml` with `memory: provider: redis`, invoke `runHermesSetup({ dryRun: true })`, assert no `config.yaml.bak.*` exists post-dry-run AND config.yaml is byte-unchanged. Matrix calls this the "critical brief callout." H-AC-48 ADVERSARIAL TEST (vector 5 prevention proof): Pass `--profile research` + non-DKG provider replacement, assert backup lands inside the explicit profile dir AND `state.priorMemoryProvider.configBackupPath` starts with the profile path. Seals the seam against a future refactor of `resolveHermesProfile` that introduces a `~/.hermes` shortcut bypassing profile resolution. VECTOR 6 ADVERSARIAL REGRESSION TESTS (TWO TESTS): 1. SIGINT mid-execute: simulate the partial state (dkg.json + managed config.yaml + orphan .bak.<ts> WITHOUT setup-state.json), re-run setupHermesProfile, assert the orphan backup is preserved on disk and re-run completes without throwing. Per option-2 fix semantics, the orphan is NOT auto-promoted into priorMemoryProvider on re-run — that would require option-1's backup-scan helper, which the findings doc explicitly defers as a future enhancement. The test pins the current behavior: orphan preserved (no silent loss), operator can manually invoke `restoreHermesProfile`. 2. Pre-write intent survives interrupt: completes a normal setup, then re-runs against the now-managed config.yaml, asserts first-wins semantics keep the original priorMemoryProvider unchanged AND restoreHermesProfile still works against the original backup. Test results vs s1-baseline.md gates: - @origintrail-official/dkg-adapter-hermes: 81 tests (77 baseline + 4 new adversarial-flagged regressions) — green
#386 parity - docs/setup/SETUP_HERMES.md: add new flag tables for `dkg hermes setup` (--no-fund/--fund, --no-start, --preserve-provider, --no-replace-provider) and `dkg hermes disconnect` (--restore-provider); document provider-replacement behavior with SIGINT-safe intent ordering, backup location, and restore semantics; rewrite Local-Agent Chat section so Connect runs setup, Refresh is health-only, Disconnect reverses with restore and preserves chat/memory history; refresh fresh-user end-to-end flow. - packages/adapter-hermes/README.md: replace "stops before changing it" copy with replace-by-default + backup-and-restore; mirror flag tables for setup/disconnect/uninstall/reconnect; document SIGINT-safe intent semantics for downstream-package authors; add Programmatic Entrypoint section covering runHermesSetup / restoreHermesProfile contract. - README.md: append a one-line note to the Hermes quick-start covering daemon start, optional funding, and replace-by-default provider election.
`agent-docs/` is excluded from git via `.git/info/exclude`. The manual sanity-checks document was added with an explicit `git add` during the S3 e2e spec commit, which bypassed info/exclude. Removing it now to honor the agreed PR-scope guardrail (coordination artifacts stay out of the public diff). Squash-merge eliminates the add+remove from final history.
`dkg hermes setup` already bootstraps `~/.dkg/config.json` when missing via `bootstrapDkgNodeConfig` → `ensureDkgNodeConfig` (S1.4 + S2.3 orchestrator integration). The fresh-user flow no longer requires a separate `dkg init` step before `dkg hermes setup` — that mirrors OpenClaw's quick-start exactly. - README.md: drop `dkg init` from Hermes quick-start; refresh one-line description to mention the bootstrap. - docs/setup/SETUP_HERMES.md: rewrite Prerequisites and Fresh User End-To-End Flow to reflect the single `dkg hermes setup` command. Update Existing-user phrasing. - packages/adapter-hermes/README.md: update Scope Boundaries + Quick Start to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ee7ca03 to
beb19ab
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR brings Hermes setup and Node UI Connect to lifecycle parity with OpenClaw, fulfilling issue #386. It is a focused follow-up to PR #315 (which integrated the Hermes adapter as a DKG V10 local agent) and intentionally does not touch the verified parts of that integration.
Headline changes:
dkg hermes setupnow matchesdkg openclaw setup: idempotent DKG node config bootstrap (via sharedensureDkgNodeConfiginpackages/core), daemon start by default with--no-startopt-out, faucet funding parity (--fund/--no-fund) preserving the same 5×1s retry semantics OpenClaw uses, and a singlerunHermesSetuporchestrator consumed by both CLI and Node UI.Connect Hermesinvokes the same shared setup-safe entrypoint asConnect OpenClaw(via a newrunHermesUiSetupdaemon shim parallel torunOpenClawUiSetup), then probes Hermes health and transitions toready/degraded.Refreshstays a non-destructive health reprobe;Disconnectruns reverse Hermes profile cleanup followed byrestoreHermesProfile, preserving chat and memory history.memory.provider: dkgby default — even over an existing non-DKG provider — with a timestamped sibling backup at<hermesHome>/config.yaml.bak.<unix-ts-ms>, prior-provider state captured intosetup-state.json(first-wins, optional field,STATE_VERSIONunchanged), and a surgical-first restore primitive with backup-file fallback. Opt-out flag for advanced users:--preserve-provider(alias--no-replace-provider). Restore is invoked unconditionally on UI Disconnect, ondkg hermes uninstall, and ondkg hermes disconnect --restore-provider.--profile,--daemon-url,--bridge-url,--gateway-url,--bridge-health-url,--memory-mode,--dry-run,--no-verify). Fixes the existing dry-run side-effect bug wheresetupHermesProfilewas writingsetup-state.jsoneven under--dry-run.Slice structure (review aid)
This PR is the union of five sequential vertical slices that landed on
feat/hermes-setup-parity. Reviewers can skim the diff slice-by-slice via the commit prefixes (refactor(s1):/feat(s2):/feat(s3):/feat(s4):/docs(s5):/test(s3,s4):/chore(s3):).adapter-openclaw/src/setup.tsanddaemon/openclaw.tsinto shared modules:resolveDkgCli+resolveCliPackageDir→packages/core/src/startDaemon(+ private deps) →packages/core/src/daemon-lifecycle.tsfundWalletsBestEffortorchestration (readWalletsWithRetry, manual-curl print, faucet skip path) →packages/core/src/faucet-orchestration.ts. 5×1s faucet retry semantics preserved exactly.ensureDkgNodeConfig(agent-agnostic chunk ofwriteDkgConfigcoveringname/apiPort/nodeRole/contextGraphs/auth/relay/autoUpdate) →packages/core/src/ensure-dkg-node-config.ts. OpenClaw legacy migrations (migrateLegacyOpenClawTransport,openclawAdapter/openclawChanneldeletes) deliberately stay insidewriteDkgConfig— they are not lifted into the shared helper.local-agent-attach-jobsMap + helpers →packages/cli/src/daemon/local-agent-attach-jobs.ts(daemon-only state).daemon/openclaw.tskeeps the legacy attach-jobs symbol names as thin re-export wrappers around the new generic implementations — kept S1's diff to renames + tiny shims and leftdaemon/local-agents.tscompletely untouched in S1 (S3's writer-owned territory, single-writer ownership preserved).runHermesSetuporchestrator. ExtendsHermesSetupCliOptionswithstart/fund/preserveProvider; registers--no-start/--fund/--no-fund/--preserve-provider(alias--no-replace-provider) /--restore-provider(S4-bound) flags.packages/cli/src/hermes-setup.tsbecomes a thin pass-through over a newrunHermesSetuporchestrator insidepackages/adapter-hermes/src/setup.tsthat composes the four S1 helpers + existingsetupHermesProfile. Adds the port-conflict warning when--portand--daemon-urldisagree (daemonUrlfirst-wins). Fixes the dry-run state-file write bug.runHermesUiSetup(signal)daemon shim parallel torunOpenClawUiSetup. Wires the Hermes branch inconnectLocalAgentIntegrationFromUito call it. Updates the Hermes notice copy. AfterdisconnectHermesProfile, callsrestoreHermesProfile; restore failure surfaces asruntime.lastErrorwarning chip on adisconnectedrow and does not roll back the disconnect. Uses the new attach-jobs scheduler from S1.priorMemoryProviderfield toHermesSetupState(optional,STATE_VERSIONunchanged). Captures it beforeensureManagedProviderBlockwrites; first-wins. AuthorsrestoreHermesProfile(surgical → backup-file → failed). Wiresdkg hermes disconnect --restore-provideranddkg hermes uninstall(unconditional restore). Adversarial pass attacked dry-run regressions, idempotent rerun byte-equality, restore loss,--no-startbypass, and backup-path drift under--profile. One concrete bug found and fixed (SIGINT mid-execute could leave state without intent record); regression test landed.docs/setup/SETUP_HERMES.mdandpackages/adapter-hermes/README.md; provider-replacement-with-backup section; restore path; UI Connect / Refresh / Disconnect summary; existing-user E2E flow; one-line root README mention.Each acceptance criterion from issue #386 maps to one or more
H-AC-NNtest rows; the mapping is enforced by the test plan below.Provider-replacement contract — at-a-glance
(This is the security-sensitive change in this PR. Reviewers should scrutinize §S4 carefully.)
dkg hermes setupselectsmemory.provider: dkgeven over an existing non-DKG provider. Rationale: DKG is the intended Hermes memory backend for this adapter; the prior "throw on conflict" default was too conservative for the first product UX.<hermesHome>/config.yaml.bak.<unix-ts-ms>, sibling toconfig.yaml. Written only when actually replacing a non-DKG provider — no replacement, no backup. First-wins on re-runs (no backup churn).setup-state.json.priorMemoryProvider = { provider, configBackupPath, capturedAt }. First-wins; re-runs do not overwrite.restoreHermesProfile): surgical line-rewrite first (preserves unrelated user edits made after setup), atomic backup-file rename as fallback, thenfailed. Returns{ ok, path: 'surgical' | 'backup-file' | 'noop' | 'failed', restoredFrom?, restoredProvider?, restoreError? }.disconnected, restore error surfaces asruntime.lastErrorwarning chip on the disconnected row. Restore failure does NOT roll back disconnect.--preserve-provider(alias--no-replace-provider) keeps today's "throw on conflict" behavior. UI Connect never sets this flag.config.yamlbetween consecutiverunHermesSetupinvocations on an already-DKG-selected profile. No backup churn.installedAtstable; onlyupdatedAtchanges.dkg hermes uninstall→ always restores.dkg hermes disconnect(CLI) → only when--restore-providerflag passed (parity with today's CLI default behavior of "disconnect-only").Helper-reuse rationale (S1)
The four function-style helpers (
resolveDkgCli,startDaemon,fundWalletsBestEffort,ensureDkgNodeConfig) live inpackages/core/src/, not inpackages/cli/src/. Reason: the original plan placed them indkg-cli, but that would have created a cyclic import (cli → adapter-{openclaw,hermes} → cli) at runtime.packages/corealready sits below both adapters in the dependency graph, so hosting these helpers there inverts the dependency cleanly:cli → adapter-{openclaw,hermes} → core.local-agent-attach-jobscorrectly stays inpackages/cli/src/daemon/. It is daemon-only state (a Map keyed by integration ID + cancellation primitives), shared between the OpenClaw and Hermes branches of the same daemon process. There is no reason for an adapter orcoreto depend on it.Caveats reviewers should know about (transparency callouts)
These are NOT blockers — they're documented disclosures so reviewers do not waste time on out-of-scope expectations. Surfaced from the QA release-readiness verdict.
A. Two adapter-openclaw test failures pre-exist on
upstream/mainpackages/adapter-openclaw/test/adapter-openclaw-extra.test.ts > [K-9] openclaw.plugin.json id matches package.json name (RED until reconciled)andpackages/adapter-openclaw/test/setup.test.ts > writeDkgConfig > mirrors only autoUpdate.enabled from network default and preserves existing pins. The K-9 row's name literally contains "RED until reconciled" — it is an intentional pre-existing canary. TheautoUpdateone expectsrepo: 'OriginTrail/dkg'in the merged config but receives onlybranch+enabled— pre-existing flake on baseline.QA verified by checking out
upstream/mainHEAD (83096835) and running the same two test files in isolation: identical 2 failures, 167 passed. Same shape. Not introduced by this PR. Per the agreed PR scope, not fixed in this change. Happy to file a follow-up issue if reviewers prefer.B. Hardhat-dependent CLI tests deferred to CI
CLI vitest config uses
globalSetup: ['../chain/test/hardhat-global-setup.ts']which fails locally becausespawnHardhatEnvcannot bind port 9548 in the parity worktree's environment. Many CLI tests beyond the curated 5 transitively depend on it. This is pre-existing onupstream/mainand unrelated to this PR. The curated CLI gate (5 files:hermes-setup-cli-args.test.ts,openclaw-setup-cli-args.test.ts,daemon-hermes.test.ts,daemon-openclaw.test.ts,hermes-setup-orchestration.test.ts) covers everyH-AC-NNrow that S2 / S3 need. CI exercises the Hardhat-dependent path in a provisioned environment.C. Live e2e operator validation (H-AC-06 / H-AC-11) deferred
E2e Playwright spec at
packages/node-ui/e2e/specs/hermes-connect.spec.tscovers click-to-state-transition with API route interception (CI-friendly, deterministic). The "live" cases need a real Hermes gateway and a packaged-CLI install to exercise the full daemon lifecycle. All four file-system / DKG-state assertions of the live path were exercised by hand against tmp HOME (manual sanity gates #3, #4, #5 — see Test Plan) with evidence captured in QA's release-readiness writeup.D. Commit message-vs-content asymmetry on two commits
Two commits on the branch carry messages that don't perfectly match their actual content due to a parallel-staging mishap in the shared worktree (one engineer's
git addswept up another engineer's uncommitted WIP). The commits in question are labeledfeat(s4): replace-by-default…(rebased SHA1ff36742) but actually contains S3 step 2 work, andfeat(s4): author restoreHermesProfile primitive…(rebased SHAa455c13f) which contains BOTH S4.2 (replace-by-default + capture) AND S4.3 (restore primitive). Functionality is correct; tests pass; the finalupstream/main..HEADdiff is correct. Per-commit asymmetry doesn't affect functionality or test coverage. Squash-merge eliminates the asymmetry at merge time (see request below). Evaluate the final diff and test coverage rather than per-commit message mapping.E. Diff is parity-only after rebase
Rebased on
upstream/mainimmediately before push so the GitHub diff view shows parity-only changes (31 files, +4107/-590). Onechore(s3)commit at the tip removes a coordination doc that was inadvertently tracked during the S3 e2e spec commit (see commitee7ca035).F. S2-deferred test sweep partial coverage
s2-deferred-tests.mdlisted 21 H-AC rows for S2 close; the S4-close sweep landed 6 (H-AC-12, 16, 22, 23, 24, 50). The remaining 15 are partially redundant log-line / retry-accounting assertions covered at thedkg-core/faucet-orchestrationextractor source. Accepted-with-rationale by QA; reviewer can request the additional sweep as a follow-up if they care.G. Working-tree dirt to NOT push
packages/evm-module/deployments/localhost_contracts.jsonis regenerated by local Hardhat chain operations and was modified in the parity worktree at hand-off time. Per execution-plan §6 it was discarded withgit checkout --before rebase and is NOT in this PR's diff (verified bygit diff upstream/main..HEAD --name-only).Squash-merge requested
Per QA recommendation 4 and §D above, please squash-merge at merge time. Squash eliminates the per-commit message-vs-content asymmetry and the add+remove of
manual-sanity-checks.mdfrom final history. Suggested squash subject: the PR title verbatim. Suggested squash body: this PR description's Summary + Provider-replacement contract sections.Related
Files changed
packages/cli/src/hermes-setup.tsHermesSetupCliOptionswithstart/fund/preserveProvider;normalizeHermesSetupOptionsupdated. Becomes a thin pass-through overrunHermesSetup.packages/cli/src/cli.ts--no-start/--fund/--no-fund/--preserve-provider(alias--no-replace-provider) /--restore-provider(ondkg hermes disconnect).packages/adapter-hermes/src/setup.tsrunHermesSetuporchestrator; composesensureDkgNodeConfig+startDaemon+fundWalletsBestEffort+setupHermesProfile. Adds replace-by-default + backup-write +priorMemoryProvidercapture. AddsrestoreHermesProfileprimitive. Fixes dry-run state-file write bug.runSetupbecomes thin throw-on-error wrapper.packages/adapter-hermes/src/types.tspriorMemoryProvider: { provider, configBackupPath, capturedAt }field.STATE_VERSIONunchanged.packages/adapter-hermes/src/index.tsrestoreHermesProfile.packages/core/src/daemon-lifecycle.tsstartDaemon(+ private deps) fromadapter-openclaw/src/setup.ts. Re-imported by both adapters.packages/core/src/faucet-orchestration.tsfundWalletsBestEffortorchestration. 5×1s retry semantics preserved exactly.packages/core/src/resolve-dkg-cli.ts+resolve-cli-package-dir.tsresolveDkgCli+resolveCliPackageDirfromadapter-openclaw/src/setup.ts.packages/core/src/ensure-dkg-node-config.tswriteDkgConfig(name,apiPort,nodeRole,contextGraphs,auth,relay,autoUpdate). OpenClaw legacy migrations stay in the originalwriteDkgConfig.packages/core/src/index.tspackages/cli/src/daemon/local-agent-attach-jobs.tsdaemon/openclaw.ts. ExportsscheduleAttachJob/cancelPending/isCancelled.packages/cli/src/daemon/openclaw.tslocal-agent-attach-jobs.ts. No logic changes.packages/adapter-openclaw/src/setup.ts+resolve-dkg-cli.tspackages/adapter-openclaw/test/{resolve-dkg-cli,setup-start-daemon,setup}.test.tspackages/cli/src/daemon/hermes.tsrunHermesUiSetup(signal)shim parallel torunOpenClawUiSetup.packages/cli/src/daemon/local-agents.tsconnectLocalAgentIntegrationFromUito callrunHermesUiSetup+ mapHermesSetupResult.status→runtime.status. AfterdisconnectHermesProfile, callsrestoreHermesProfile; restore failure surfaces asruntime.lastErroron disconnected row. Updates Hermes notice copy. Untouched in S1 (single-writer ownership preserved).packages/cli/src/daemon/routes/local-agents.tspackages/node-ui/src/ui/components/Shell/PanelRight.tsxruntime.status: 'disconnected' && runtime.lastError.packages/cli/test/hermes-setup-cli-args.test.tspackages/cli/test/hermes-setup-orchestration.test.tspackages/cli/test/daemon-hermes.test.tspackages/adapter-hermes/test/hermes-adapter.test.tspriorMemoryProvidercapture, restore primitive, idempotent rerun, H-AC-26/H-AC-48 adversarial regression.packages/node-ui/test/panel-right.logic.test.tspackages/node-ui/e2e/specs/hermes-connect.spec.tsdocs/setup/SETUP_HERMES.mdpackages/adapter-hermes/README.mdREADME.mdTest plan
The full test suite (
pnpm --filter @origintrail-official/dkg testat the repo root) is not the gate for this PR. The Hardhat-dependent path (spawnHardhatEnv/ port 9548) fails onupstream/mainindependently of this PR — it requires a provisioned local devnet not present in the parity worktree. CI exercises the Hardhat-dependent path; QA's release-readiness writeup documented which subsets were deferred to that environment.Test results from the rebased HEAD (post-rebase sanity run)
pnpm --filter @origintrail-official/dkg-adapter-hermes testpnpm exec vitest run --pool=forks test/hermes-setup-cli-args.test.ts test/openclaw-setup-cli-args.test.ts test/daemon-hermes.test.ts test/daemon-openclaw.test.ts test/hermes-setup-orchestration.test.ts(run frompackages/cli/)Test results from QA release-readiness verdict
pnpm --filter @origintrail-official/dkg-adapter-hermes testpnpm --filter @origintrail-official/dkg-adapter-openclaw testupstream/main(caveat A above)pnpm --filter @origintrail-official/dkg-node-ui testupstream/main)pnpm --filter @origintrail-official/dkg-core testManual sanity gates (executed by hand against tmp HOME)
--dry-runis strictly side-effect free (no.bak.*file, byte-equalconfig.yamlpre/post): ✓ passed.config.yaml(md5 identical between two runs); single backup file (no churn): ✓ passed.provider: redisline via surgical-first path; sibling keys (url,pool) preserved byte-identical to pre-seed: ✓ passed.Acceptance-criteria coverage (each row maps to a test surface; all evidence in QA writeup)
--no-starttruly does not start the DKG daemon (H-AC-12, 14, 15; H-AC-13 defense-in-depth deferred).--no-fund/--fundparity (H-AC-16, 20; H-AC-17/18/19 §10G partial coverage at extractor source per caveat F).--dry-runis strictly side-effect free — no FS write, no daemon start, no faucet call, no daemon registration probe, no.bak.*file (H-AC-21 through H-AC-26).--preserve-provideropt-out throws clear error instead of replacing (H-AC-30).config.yaml; no backup churn;installedAtstable (H-AC-25, H-AC-26, H-AC-31).Refreshis health-only (H-AC-46, H-AC-47, H-AC-47b).Disconnectruns reverse setup + restore; chat + memory history preserved; restore failure surfaces as warning chip without rolling back disconnect (H-AC-37, H-AC-47, H-AC-47b).dkg hermes uninstallinvokes restore unconditionally (H-AC-39).--portand--daemon-urldisagree;daemonUrlfirst-wins (H-AC-49, H-AC-50, H-AC-58).PR-#315 invariant cross-check (12 invariants from PR-#315 baseline)
All 12 PR-#315 invariants verified to hold. Notably: bridge URL loopback validation,
/api/hermes-channel/*daemon route signatures,MANAGED_BYmarkers,STATE_VERSIONunchanged. The newpriorMemoryProviderfield is optional with a safe-absent default, soSTATE_VERSIONdoes not bump.Security and release notes
<hermesHome>/config.yaml.bak.<unix-ts-ms>, sibling toconfig.yaml. Written only when actually replacing a non-DKG provider. First-wins on capture; re-runs do not overwrite. Inherits the same filesystem permissions as the parent profile directory; no broader read access introduced.dkg hermes disconnectrequires explicit--restore-provider.disconnectedand surfaces the error asruntime.lastErrorwarning chip on the disconnected row — it does NOT roll back the disconnect. Rationale: disconnect is the user's primary intent; partial restore failure is a separate signal.--dry-runis now strictly side-effect free: bug fix where the prior code wrotesetup-state.jsonunder dry-run is corrected. The S4 adversarial pass explicitly attacked dry-run regressions including.bak.*writes.priorMemoryProviderintent record, breaking subsequent restore. Fix: write intent first, then proceed with config rewrite. Regression test landed./api/hermes-channel/*daemon route signatures,MANAGED_BYmarkers, andSTATE_VERSIONare all unchanged.