peaks-loop 4.0.35 → 4.0.37

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 (128) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/bin/peaks.js +71 -1
  5. package/dist/cli/cli-helpers.js +7 -0
  6. package/dist/cli/commands/_register.js +2 -0
  7. package/dist/cli/commands/best-practice-scan-command.d.ts +14 -1
  8. package/dist/cli/commands/best-practice-scan-command.js +67 -9
  9. package/dist/cli/commands/code-runtime-commands.d.ts +5 -2
  10. package/dist/cli/commands/code-runtime-commands.js +78 -7
  11. package/dist/cli/commands/core/doctor-command.d.ts +8 -0
  12. package/dist/cli/commands/core/doctor-command.js +44 -2
  13. package/dist/cli/commands/core/memory-command.js +5 -1
  14. package/dist/cli/commands/dispatch-commands.js +15 -3
  15. package/dist/cli/commands/dispatch-from-dag.js +17 -0
  16. package/dist/cli/commands/hooks-commands.js +10 -1
  17. package/dist/cli/commands/job-commands.js +107 -25
  18. package/dist/cli/commands/memory-commands.d.ts +24 -0
  19. package/dist/cli/commands/memory-commands.js +77 -10
  20. package/dist/cli/commands/request-commands.d.ts +8 -0
  21. package/dist/cli/commands/request-commands.js +23 -2
  22. package/dist/cli/commands/scan-commands.js +1 -1
  23. package/dist/cli/commands/sub-agent-commands.js +2 -0
  24. package/dist/cli/commands/wave-plan-commands.d.ts +24 -0
  25. package/dist/cli/commands/wave-plan-commands.js +93 -0
  26. package/dist/cli/commands/web-commands.d.ts +28 -0
  27. package/dist/cli/commands/web-commands.js +327 -0
  28. package/dist/cli/commands/web-lifecycle-commands.d.ts +49 -0
  29. package/dist/cli/commands/web-lifecycle-commands.js +321 -0
  30. package/dist/services/best-practice/scan-orchestrator.d.ts +22 -0
  31. package/dist/services/best-practice/scan-orchestrator.js +14 -5
  32. package/dist/services/code/orchestrator-can-do.js +27 -4
  33. package/dist/services/context/build-dispatch-system-prompt.d.ts +66 -9
  34. package/dist/services/context/build-dispatch-system-prompt.js +132 -17
  35. package/dist/services/context/context-audit-hint.d.ts +79 -0
  36. package/dist/services/context/context-audit-hint.js +150 -0
  37. package/dist/services/context/context-audit.d.ts +100 -0
  38. package/dist/services/context/context-audit.js +322 -0
  39. package/dist/services/context/summary-view.d.ts +54 -0
  40. package/dist/services/context/summary-view.js +114 -0
  41. package/dist/services/dispatch/file-overlap-wave-planner.d.ts +70 -0
  42. package/dist/services/dispatch/file-overlap-wave-planner.js +119 -0
  43. package/dist/services/dispatch/session-capsule.d.ts +23 -0
  44. package/dist/services/dispatch/session-capsule.js +56 -0
  45. package/dist/services/dispatch/slice-dag.d.ts +9 -0
  46. package/dist/services/dispatch/slice-dag.js +9 -1
  47. package/dist/services/dispatch/test-tool-detection.d.ts +12 -1
  48. package/dist/services/dispatch/test-tool-detection.js +14 -13
  49. package/dist/services/hooks/auto-compact-hook-install.js +10 -1
  50. package/dist/services/hooks/write-gate.js +88 -0
  51. package/dist/services/ide/adapters/claude-code-adapter.d.ts +10 -0
  52. package/dist/services/ide/adapters/claude-code-adapter.js +20 -1
  53. package/dist/services/ide/ide-types.d.ts +15 -0
  54. package/dist/services/lint/detect-eslint.d.ts +2 -0
  55. package/dist/services/lint/detect-eslint.js +23 -9
  56. package/dist/services/lint/npx-resolver.d.ts +6 -0
  57. package/dist/services/lint/npx-resolver.js +38 -14
  58. package/dist/services/memory/project-memory-service/parsers/frontmatter.d.ts +5 -0
  59. package/dist/services/memory/project-memory-service/parsers/frontmatter.js +55 -5
  60. package/dist/services/release/version-precheck-service.js +9 -2
  61. package/dist/services/scan/file-size-scan.d.ts +29 -0
  62. package/dist/services/scan/file-size-scan.js +63 -0
  63. package/dist/services/session/caller-binding-service.d.ts +24 -0
  64. package/dist/services/session/caller-binding-service.js +34 -0
  65. package/dist/services/session/getSessionDir.js +15 -10
  66. package/dist/services/skills/hooks-codegate-superpowers.d.ts +33 -0
  67. package/dist/services/skills/hooks-codegate-superpowers.js +34 -3
  68. package/dist/services/skills/hooks-settings-service.d.ts +10 -0
  69. package/dist/services/skills/hooks-settings-service.js +152 -61
  70. package/dist/services/slice/slice-check-service.d.ts +14 -0
  71. package/dist/services/slice/slice-check-service.js +110 -50
  72. package/dist/services/slice/slice-check-types.d.ts +12 -7
  73. package/dist/services/slice/slice-check-types.js +8 -3
  74. package/dist/services/slice/slice-decompose-runners.js +24 -21
  75. package/dist/services/sop/sop-check-service.js +12 -1
  76. package/dist/services/web/bounded-output.d.ts +34 -0
  77. package/dist/services/web/bounded-output.js +68 -0
  78. package/dist/services/web/browser-acquire.d.ts +14 -0
  79. package/dist/services/web/browser-acquire.js +84 -0
  80. package/dist/services/web/browser-session-manager.d.ts +111 -0
  81. package/dist/services/web/browser-session-manager.js +413 -0
  82. package/dist/services/web/daemon-entry.d.ts +1 -0
  83. package/dist/services/web/daemon-entry.js +65 -0
  84. package/dist/services/web/daemon-registry.d.ts +42 -0
  85. package/dist/services/web/daemon-registry.js +164 -0
  86. package/dist/services/web/daemon-supervisor.d.ts +144 -0
  87. package/dist/services/web/daemon-supervisor.js +455 -0
  88. package/dist/services/web/playwright-loader.d.ts +89 -0
  89. package/dist/services/web/playwright-loader.js +253 -0
  90. package/dist/services/web/snapshot-pruner.d.ts +48 -0
  91. package/dist/services/web/snapshot-pruner.js +241 -0
  92. package/dist/services/web/untrusted-envelope.d.ts +27 -0
  93. package/dist/services/web/untrusted-envelope.js +44 -0
  94. package/dist/services/web/web-artifact-paths.d.ts +79 -0
  95. package/dist/services/web/web-artifact-paths.js +163 -0
  96. package/dist/services/web/web-client.d.ts +19 -0
  97. package/dist/services/web/web-client.js +55 -0
  98. package/dist/services/web/web-daemon-service.d.ts +38 -0
  99. package/dist/services/web/web-daemon-service.js +416 -0
  100. package/dist/services/web/web-fallback.d.ts +70 -0
  101. package/dist/services/web/web-fallback.js +121 -0
  102. package/dist/services/web/web-install-service.d.ts +91 -0
  103. package/dist/services/web/web-install-service.js +346 -0
  104. package/dist/services/web/web-login-profile.d.ts +89 -0
  105. package/dist/services/web/web-login-profile.js +612 -0
  106. package/dist/services/web/web-login-staging.d.ts +27 -0
  107. package/dist/services/web/web-login-staging.js +173 -0
  108. package/dist/services/web/web-protocol.d.ts +58 -0
  109. package/dist/services/web/web-protocol.js +58 -0
  110. package/dist/services/web/web-status-report.d.ts +33 -0
  111. package/dist/services/web/web-status-report.js +47 -0
  112. package/dist/services/workspace/claude-settings-template.d.ts +41 -5
  113. package/dist/services/workspace/claude-settings-template.js +116 -64
  114. package/dist/services/workspace/workspace-claude-settings-materializer.js +5 -1
  115. package/dist/services/workspace/workspace-service.js +33 -0
  116. package/package.json +5 -5
  117. package/scripts/copy-templates.mjs +12 -0
  118. package/scripts/sync-version.mjs +20 -0
  119. package/skills/bee/peaks-qa/SKILL.md +2 -0
  120. package/skills/bee/peaks-qa/references/qa-sub-agent-dispatch.md +12 -0
  121. package/skills/bee/peaks-rd/SKILL.md +2 -0
  122. package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +14 -0
  123. package/skills/bee/peaks-txt/SKILL.md +2 -0
  124. package/skills/bee/peaks-ui/SKILL.md +2 -0
  125. package/skills/peaks-code/SKILL.md +18 -0
  126. package/skills/peaks-code/references/browser-workflow.md +10 -1
  127. package/skills/peaks-code/references/context-governance.md +29 -0
  128. package/skills/peaks-doctor/SKILL.md +2 -0
@@ -0,0 +1,79 @@
1
+ /** Sub-directory of the session dir that owns every `peaks web` artifact. */
2
+ export declare const WEB_SUBDIR = "web";
3
+ /** Sub-directory of the session dir that owns the per-dispatch storage state. */
4
+ export declare const WEB_PROFILES_SUBDIR = "pw-profiles";
5
+ /** `<root>/.peaks/_runtime/<sid>/web/` — the root of every web artifact. */
6
+ export declare function webDir(projectRoot: string, sessionId: string): string;
7
+ /**
8
+ * `<root>/.peaks/_runtime/<sid>/pw-profiles/` — the per-dispatch storage-state
9
+ * root. A SIBLING of `web/`, which is why the guard for `webContextStatePath`
10
+ * must use this resolver and never `webDir` (getting that wrong threw
11
+ * `WEB_PATH_ESCAPE` for every dispatch).
12
+ */
13
+ export declare function webProfilesDir(projectRoot: string, sessionId: string): string;
14
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/` — daemon info, lock, and log. */
15
+ export declare function webDaemonDir(projectRoot: string, sessionId: string): string;
16
+ /**
17
+ * `<root>/.peaks/_runtime/<sid>/web/shot-<ts>.png`.
18
+ * `<ts>` is `YYYYMMDDTHHMMSSmmmZ` — no colons, so the name is Windows-safe
19
+ * (tech-doc §7.1).
20
+ */
21
+ export declare function webShotPath(projectRoot: string, sessionId: string, ts: string): string;
22
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.json`. */
23
+ export declare function webDaemonInfoPath(projectRoot: string, sessionId: string): string;
24
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.lock` — the cold-start lock. */
25
+ export declare function webSpawnLockPath(projectRoot: string, sessionId: string): string;
26
+ /**
27
+ * `<homedir>/.peaks/web/install.lock` — the chromium install lock.
28
+ *
29
+ * MACHINE-GLOBAL, deliberately not per session (S3 repair, R4). The resource it
30
+ * guards is Playwright's own browser cache (`%LOCALAPPDATA%\ms-playwright`,
31
+ * `~/.cache/ms-playwright`), which every session, every project and every
32
+ * daemon of this user shares. A lock under `<root>/.peaks/_runtime/<sid>/web/`
33
+ * serialized nothing across sessions: N sessions could run N concurrent
34
+ * installers into that one cache, and `install --force` in one session could
35
+ * delete a browser another session's daemon has open. `~/.peaks` is the
36
+ * existing per-user peaks location (`config.json`, `logs/`), so this adds no
37
+ * new convention, and it stays outside the project root — AC1's "no root
38
+ * pollution" is untouched.
39
+ */
40
+ export declare function webInstallLockPath(): string;
41
+ /**
42
+ * `<homedir>/.peaks/web-profiles/` — the root of the user-level login profiles
43
+ * (S4, design §10.2).
44
+ *
45
+ * Deliberately CROSS-PROJECT: one logged-in session reused across every project
46
+ * and worktree is the feature's whole value, so it is scoped to the user, not to
47
+ * `(projectRoot, sessionId)`. AC1's "no root pollution" is untouched — this
48
+ * lives under the user's home, never under the project root.
49
+ *
50
+ * The name lives HERE rather than in `web-login-profile.ts` for the same reason
51
+ * `webInstallLockPath` does: this module is the only one under
52
+ * `src/services/web/` allowed to contain the `.peaks` literal (tech-doc §7.2
53
+ * rule 1).
54
+ */
55
+ export declare function userWebProfilesDir(): string;
56
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.log` — daemon stdout/stderr. */
57
+ export declare function webLogPath(projectRoot: string, sessionId: string): string;
58
+ /**
59
+ * `<root>/.peaks/_runtime/<sid>/pw-profiles/<dispatchId>/storageState.json`.
60
+ *
61
+ * The DIRECTORY convention is the existing `pw-profiles/<dispatchId>` one
62
+ * (`src/services/worktree/playwright-profile.ts:21`), reused rather than
63
+ * re-implemented. The FILE inside it is a Playwright `storageState.json`, not
64
+ * a Chromium `userDataDir`: orchestrator decision C1 — a persistent context
65
+ * per dispatch would spawn one browser process per dispatch and violate Q8.
66
+ *
67
+ * Note the sibling relationship: this path is NOT under `webDir(...)`. The
68
+ * guard parent for it is `webProfilesDir(...)`.
69
+ */
70
+ export declare function webContextStatePath(projectRoot: string, sessionId: string, dispatchId: string): string;
71
+ /**
72
+ * Throw unless `child` resolves to `parent` or to a path strictly inside it.
73
+ *
74
+ * The single write guard for the slice: every write site calls this before
75
+ * touching disk, and a violation surfaces as `WEB_PATH_ESCAPE` in a `fail()`
76
+ * envelope. Uses the shared `isInsidePath` so a bare `startsWith` sibling-prefix
77
+ * collision (`/proj2` vs `/proj`) cannot pass.
78
+ */
79
+ export declare function assertUnder(child: string, parent: string): void;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Canonical path resolvers for the `peaks web` artifact tree (slice S1, AC1).
3
+ *
4
+ * What is actually guaranteed here, and what is not:
5
+ *
6
+ * - `sessionId` is validated against a strict slug before any `join`, so a
7
+ * traversing sid (`..\..\..\tmp\pwn`) cannot move the whole tree out of the
8
+ * project. A resolver rejects it outright.
9
+ * - `assertUnder` then anchors each write to a parent this module derived
10
+ * itself, so a traversing `dispatchId` cannot escape either.
11
+ *
12
+ * Both halves are needed: `assertUnder(child, parent)` takes a parent derived
13
+ * from the same `sessionId` the child is, so on its own it can only constrain
14
+ * the components BELOW the session dir. The sid slug check is what closes the
15
+ * sid itself.
16
+ *
17
+ * Every path is derived from `getSessionDir(projectRoot, sessionId)` — with two
18
+ * declared exceptions. `webContextStatePath` is built on the pre-existing
19
+ * `pw-profiles/<dispatchId>` convention (`playwright-profile.ts`) and therefore
20
+ * lands beside `web/`, not inside it. `webInstallLockPath` is not under the
21
+ * session at all: it guards Playwright's machine-global browser cache, so its
22
+ * scope is the user, not the session (see its docstring).
23
+ *
24
+ * This is the ONLY module under `src/services/web/` that may name `.peaks`;
25
+ * every other writer in the slice imports a resolver from here (tech-doc §7.2
26
+ * rule 1). Shape mirrors `canonicalQaDir()`
27
+ * (`src/services/workflow/artifact-paths.ts:55`).
28
+ *
29
+ * The write guard for the whole slice is `assertUnder` — it is a thin wrapper
30
+ * on the shared, cross-platform `isInsidePath` from `src/shared/path-utils.ts`
31
+ * (see `.peaks/memory/2026-08-04-cross-platform-path-utility-rule.md`), so the
32
+ * sibling-prefix case (`/proj2` vs `/proj`) is rejected by construction.
33
+ */
34
+ import { resolve } from 'node:path';
35
+ import { join } from 'node:path';
36
+ import { homedir } from 'node:os';
37
+ import { isInsidePath } from '../../shared/path-utils.js';
38
+ import { getSessionDir } from '../session/getSessionDir.js';
39
+ import { playwrightProfilePaths } from '../worktree/playwright-profile.js';
40
+ /** Sub-directory of the session dir that owns every `peaks web` artifact. */
41
+ export const WEB_SUBDIR = 'web';
42
+ /** Sub-directory of the session dir that owns the per-dispatch storage state. */
43
+ export const WEB_PROFILES_SUBDIR = 'pw-profiles';
44
+ /**
45
+ * A session id is a single path component. Anything else — a separator, `..`,
46
+ * or `.` — would let the value that names the session also relocate it.
47
+ */
48
+ const SESSION_ID_SLUG_RE = /^[A-Za-z0-9._-]+$/;
49
+ /**
50
+ * Reject a `sessionId` that is not a single, non-traversing path component.
51
+ *
52
+ * This is the check `assertUnder` structurally cannot make (see the module
53
+ * docstring): the guard's child and parent both descend from `sessionId`, so a
54
+ * traversing sid moves them together and the containment test still passes.
55
+ */
56
+ function assertValidSessionId(sessionId) {
57
+ if (sessionId === '.' || sessionId === '..' || !SESSION_ID_SLUG_RE.test(sessionId)) {
58
+ throw new Error(`WEB_INVALID_SESSION_ID: ${JSON.stringify(sessionId)} is not a single path component`);
59
+ }
60
+ }
61
+ /** `<root>/.peaks/_runtime/<sid>/web/` — the root of every web artifact. */
62
+ export function webDir(projectRoot, sessionId) {
63
+ assertValidSessionId(sessionId);
64
+ return join(getSessionDir(projectRoot, sessionId), WEB_SUBDIR);
65
+ }
66
+ /**
67
+ * `<root>/.peaks/_runtime/<sid>/pw-profiles/` — the per-dispatch storage-state
68
+ * root. A SIBLING of `web/`, which is why the guard for `webContextStatePath`
69
+ * must use this resolver and never `webDir` (getting that wrong threw
70
+ * `WEB_PATH_ESCAPE` for every dispatch).
71
+ */
72
+ export function webProfilesDir(projectRoot, sessionId) {
73
+ assertValidSessionId(sessionId);
74
+ return join(getSessionDir(projectRoot, sessionId), WEB_PROFILES_SUBDIR);
75
+ }
76
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/` — daemon info, lock, and log. */
77
+ export function webDaemonDir(projectRoot, sessionId) {
78
+ return join(webDir(projectRoot, sessionId), 'daemon');
79
+ }
80
+ /**
81
+ * `<root>/.peaks/_runtime/<sid>/web/shot-<ts>.png`.
82
+ * `<ts>` is `YYYYMMDDTHHMMSSmmmZ` — no colons, so the name is Windows-safe
83
+ * (tech-doc §7.1).
84
+ */
85
+ export function webShotPath(projectRoot, sessionId, ts) {
86
+ return join(webDir(projectRoot, sessionId), `shot-${ts}.png`);
87
+ }
88
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.json`. */
89
+ export function webDaemonInfoPath(projectRoot, sessionId) {
90
+ return join(webDaemonDir(projectRoot, sessionId), 'daemon.json');
91
+ }
92
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.lock` — the cold-start lock. */
93
+ export function webSpawnLockPath(projectRoot, sessionId) {
94
+ return join(webDaemonDir(projectRoot, sessionId), 'daemon.lock');
95
+ }
96
+ /**
97
+ * `<homedir>/.peaks/web/install.lock` — the chromium install lock.
98
+ *
99
+ * MACHINE-GLOBAL, deliberately not per session (S3 repair, R4). The resource it
100
+ * guards is Playwright's own browser cache (`%LOCALAPPDATA%\ms-playwright`,
101
+ * `~/.cache/ms-playwright`), which every session, every project and every
102
+ * daemon of this user shares. A lock under `<root>/.peaks/_runtime/<sid>/web/`
103
+ * serialized nothing across sessions: N sessions could run N concurrent
104
+ * installers into that one cache, and `install --force` in one session could
105
+ * delete a browser another session's daemon has open. `~/.peaks` is the
106
+ * existing per-user peaks location (`config.json`, `logs/`), so this adds no
107
+ * new convention, and it stays outside the project root — AC1's "no root
108
+ * pollution" is untouched.
109
+ */
110
+ export function webInstallLockPath() {
111
+ return join(homedir(), '.peaks', 'web', 'install.lock');
112
+ }
113
+ /**
114
+ * `<homedir>/.peaks/web-profiles/` — the root of the user-level login profiles
115
+ * (S4, design §10.2).
116
+ *
117
+ * Deliberately CROSS-PROJECT: one logged-in session reused across every project
118
+ * and worktree is the feature's whole value, so it is scoped to the user, not to
119
+ * `(projectRoot, sessionId)`. AC1's "no root pollution" is untouched — this
120
+ * lives under the user's home, never under the project root.
121
+ *
122
+ * The name lives HERE rather than in `web-login-profile.ts` for the same reason
123
+ * `webInstallLockPath` does: this module is the only one under
124
+ * `src/services/web/` allowed to contain the `.peaks` literal (tech-doc §7.2
125
+ * rule 1).
126
+ */
127
+ export function userWebProfilesDir() {
128
+ return join(homedir(), '.peaks', 'web-profiles');
129
+ }
130
+ /** `<root>/.peaks/_runtime/<sid>/web/daemon/daemon.log` — daemon stdout/stderr. */
131
+ export function webLogPath(projectRoot, sessionId) {
132
+ return join(webDaemonDir(projectRoot, sessionId), 'daemon.log');
133
+ }
134
+ /**
135
+ * `<root>/.peaks/_runtime/<sid>/pw-profiles/<dispatchId>/storageState.json`.
136
+ *
137
+ * The DIRECTORY convention is the existing `pw-profiles/<dispatchId>` one
138
+ * (`src/services/worktree/playwright-profile.ts:21`), reused rather than
139
+ * re-implemented. The FILE inside it is a Playwright `storageState.json`, not
140
+ * a Chromium `userDataDir`: orchestrator decision C1 — a persistent context
141
+ * per dispatch would spawn one browser process per dispatch and violate Q8.
142
+ *
143
+ * Note the sibling relationship: this path is NOT under `webDir(...)`. The
144
+ * guard parent for it is `webProfilesDir(...)`.
145
+ */
146
+ export function webContextStatePath(projectRoot, sessionId, dispatchId) {
147
+ assertValidSessionId(sessionId);
148
+ const { userDataDir } = playwrightProfilePaths({ projectRoot, sessionId, dispatchId });
149
+ return join(userDataDir, 'storageState.json');
150
+ }
151
+ /**
152
+ * Throw unless `child` resolves to `parent` or to a path strictly inside it.
153
+ *
154
+ * The single write guard for the slice: every write site calls this before
155
+ * touching disk, and a violation surfaces as `WEB_PATH_ESCAPE` in a `fail()`
156
+ * envelope. Uses the shared `isInsidePath` so a bare `startsWith` sibling-prefix
157
+ * collision (`/proj2` vs `/proj`) cannot pass.
158
+ */
159
+ export function assertUnder(child, parent) {
160
+ if (!isInsidePath(resolve(child), resolve(parent))) {
161
+ throw new Error(`WEB_PATH_ESCAPE: ${child} is not under ${parent}`);
162
+ }
163
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Loopback HTTP client for the `peaks web` daemon (slice S1, file 6).
3
+ *
4
+ * The CLI layer never speaks HTTP itself: this class is the only thing it
5
+ * knows how to talk to, so every transport concern (bearer token, timeouts,
6
+ * envelope normalisation) lives here and every CLI test can stub the daemon
7
+ * with a plain `node:http` server.
8
+ */
9
+ import type { WebDaemonInfo, WebOp, WebOpResponse } from './web-protocol.js';
10
+ export declare class WebDaemonClient {
11
+ private readonly baseUrl;
12
+ private readonly token;
13
+ constructor(info: WebDaemonInfo);
14
+ /** True when a daemon is listening on the recorded port and accepting our token. */
15
+ health(): Promise<boolean>;
16
+ /** Invoke one op. A transport failure throws; an op failure comes back as `ok: false`. */
17
+ call<T>(op: WebOp, args: Readonly<Record<string, unknown>>, timeoutMs: number): Promise<WebOpResponse<T>>;
18
+ private headers;
19
+ }
@@ -0,0 +1,55 @@
1
+ /** `/health` is a liveness probe; a wedged daemon must answer fast or not at all. */
2
+ const HEALTH_TIMEOUT_MS = 500;
3
+ export class WebDaemonClient {
4
+ baseUrl;
5
+ token;
6
+ constructor(info) {
7
+ this.baseUrl = `http://127.0.0.1:${info.port}`;
8
+ this.token = info.token;
9
+ }
10
+ /** True when a daemon is listening on the recorded port and accepting our token. */
11
+ async health() {
12
+ try {
13
+ const response = await fetch(`${this.baseUrl}/health`, {
14
+ headers: this.headers(),
15
+ signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS)
16
+ });
17
+ return response.ok;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ /** Invoke one op. A transport failure throws; an op failure comes back as `ok: false`. */
24
+ async call(op, args, timeoutMs) {
25
+ const body = { op, args };
26
+ const response = await fetch(`${this.baseUrl}/op`, {
27
+ method: 'POST',
28
+ headers: this.headers(),
29
+ body: JSON.stringify(body),
30
+ signal: AbortSignal.timeout(timeoutMs)
31
+ });
32
+ if (!response.ok) {
33
+ return {
34
+ ok: false,
35
+ data: null,
36
+ code: `WEB_DAEMON_HTTP_${response.status}`,
37
+ message: `peaks web daemon answered HTTP ${response.status}`,
38
+ warnings: [],
39
+ nextActions: ['Run `peaks web stop` and retry to respawn the daemon']
40
+ };
41
+ }
42
+ const parsed = (await response.json());
43
+ return {
44
+ ok: parsed.ok === true,
45
+ data: parsed.data ?? null,
46
+ code: parsed.code ?? null,
47
+ message: parsed.message ?? null,
48
+ warnings: Array.isArray(parsed.warnings) ? parsed.warnings : [],
49
+ nextActions: Array.isArray(parsed.nextActions) ? parsed.nextActions : []
50
+ };
51
+ }
52
+ headers() {
53
+ return { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' };
54
+ }
55
+ }
@@ -0,0 +1,38 @@
1
+ import { BrowserSessionManager, type CloseAllResult } from './browser-session-manager.js';
2
+ import { type WebOp, type WebOpResponse } from './web-protocol.js';
3
+ /** Loopback only: the bearer token is the second lock, not the only one. */
4
+ export declare const DAEMON_HOST = "127.0.0.1";
5
+ export interface WebDaemonConfig {
6
+ readonly projectRoot: string;
7
+ readonly sessionId: string;
8
+ /** `peaks-loop` version written into `daemon.json`. */
9
+ readonly version: string;
10
+ }
11
+ export interface RunningWebDaemon {
12
+ readonly port: number;
13
+ readonly token: string;
14
+ /**
15
+ * Close every context, persist each dispatch's `storageState.json`, close the
16
+ * browser process, stop listening and remove `daemon.json`.
17
+ *
18
+ * Returns the teardown result rather than swallowing it: a `storageState.json`
19
+ * that could not be written is silent data loss for S4's login profile (R1).
20
+ */
21
+ readonly close: () => Promise<CloseAllResult>;
22
+ }
23
+ /**
24
+ * Start the daemon. Resolves once the server is listening and `daemon.json`
25
+ * exists, so the caller (and every poller in `ensureDaemon`) can treat the
26
+ * resolution as "reachable".
27
+ */
28
+ export declare function startWebDaemon(config: WebDaemonConfig): Promise<RunningWebDaemon>;
29
+ /**
30
+ * Serve one op. Exported separately from the server so every verb's payload
31
+ * shape and failure code are unit-testable without a socket.
32
+ *
33
+ * The manager arrives as a PROVIDER, not a value: acquiring it is what launches
34
+ * chromium, so it must happen only for ops that actually need a page. An op the
35
+ * daemon does not serve (`install`, `login`, a typo) must answer without
36
+ * starting a browser at all.
37
+ */
38
+ export declare function routeOp(op: WebOp, args: Readonly<Record<string, unknown>>, managerFor: () => Promise<BrowserSessionManager>): Promise<WebOpResponse>;