@aexol/spectral 0.9.211 → 0.9.215

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 (115) hide show
  1. package/dist/agent/agents.d.ts.map +1 -1
  2. package/dist/agent/agents.js +40 -0
  3. package/dist/backend/generated-images.d.ts +90 -0
  4. package/dist/backend/generated-images.d.ts.map +1 -0
  5. package/dist/backend/generated-images.js +214 -0
  6. package/dist/backend/machine-routing.d.ts +25 -0
  7. package/dist/backend/machine-routing.d.ts.map +1 -0
  8. package/dist/backend/machine-routing.js +35 -0
  9. package/dist/commands/serve.d.ts.map +1 -1
  10. package/dist/commands/serve.js +31 -6
  11. package/dist/extensions/browser/browser-service.d.ts +75 -1
  12. package/dist/extensions/browser/browser-service.d.ts.map +1 -1
  13. package/dist/extensions/browser/browser-service.js +600 -34
  14. package/dist/extensions/browser/index.d.ts +10 -4
  15. package/dist/extensions/browser/index.d.ts.map +1 -1
  16. package/dist/extensions/browser/index.js +44 -5
  17. package/dist/extensions/browser/stream/stream-tool.d.ts +10 -0
  18. package/dist/extensions/browser/stream/stream-tool.d.ts.map +1 -1
  19. package/dist/extensions/browser/stream/stream-tool.js +42 -14
  20. package/dist/extensions/browser/tools/core.d.ts.map +1 -1
  21. package/dist/extensions/browser/tools/core.js +63 -2
  22. package/dist/extensions/desktop-control/index.d.ts +55 -0
  23. package/dist/extensions/desktop-control/index.d.ts.map +1 -0
  24. package/dist/extensions/desktop-control/index.js +633 -0
  25. package/dist/extensions/desktop-control/keys.d.ts +33 -0
  26. package/dist/extensions/desktop-control/keys.d.ts.map +1 -0
  27. package/dist/extensions/desktop-control/keys.js +170 -0
  28. package/dist/extensions/desktop-control/linux.d.ts +26 -0
  29. package/dist/extensions/desktop-control/linux.d.ts.map +1 -0
  30. package/dist/extensions/desktop-control/linux.js +195 -0
  31. package/dist/extensions/desktop-control/macos.d.ts +53 -0
  32. package/dist/extensions/desktop-control/macos.d.ts.map +1 -0
  33. package/dist/extensions/desktop-control/macos.js +382 -0
  34. package/dist/extensions/desktop-control/safety.d.ts +89 -0
  35. package/dist/extensions/desktop-control/safety.d.ts.map +1 -0
  36. package/dist/extensions/desktop-control/safety.js +157 -0
  37. package/dist/extensions/desktop-control/types.d.ts +79 -0
  38. package/dist/extensions/desktop-control/types.d.ts.map +1 -0
  39. package/dist/extensions/desktop-control/types.js +10 -0
  40. package/dist/extensions/desktop-control/windows.d.ts +31 -0
  41. package/dist/extensions/desktop-control/windows.d.ts.map +1 -0
  42. package/dist/extensions/desktop-control/windows.js +276 -0
  43. package/dist/extensions/desktop-screenshot/index.d.ts +103 -0
  44. package/dist/extensions/desktop-screenshot/index.d.ts.map +1 -1
  45. package/dist/extensions/desktop-screenshot/index.js +363 -17
  46. package/dist/extensions/image-generation/index.d.ts.map +1 -1
  47. package/dist/extensions/image-generation/index.js +133 -34
  48. package/dist/extensions/seo/index.d.ts.map +1 -1
  49. package/dist/extensions/seo/index.js +4 -0
  50. package/dist/extensions/seo/tools/backend-bridge.d.ts +4 -0
  51. package/dist/extensions/seo/tools/backend-bridge.d.ts.map +1 -0
  52. package/dist/extensions/seo/tools/backend-bridge.js +378 -0
  53. package/dist/extensions/seo/tools/full-audit.d.ts +2 -0
  54. package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -1
  55. package/dist/extensions/seo/tools/full-audit.js +1 -1
  56. package/dist/extensions/seo/tools/report.d.ts +10 -0
  57. package/dist/extensions/seo/tools/report.d.ts.map +1 -1
  58. package/dist/extensions/seo/tools/report.js +1 -1
  59. package/dist/extensions/spectral-vision-fallback.d.ts.map +1 -1
  60. package/dist/extensions/spectral-vision-fallback.js +11 -1
  61. package/dist/generated/zeus/const.d.ts.map +1 -1
  62. package/dist/generated/zeus/const.js +148 -4
  63. package/dist/generated/zeus/index.d.ts +649 -0
  64. package/dist/generated/zeus/index.d.ts.map +1 -1
  65. package/dist/mcp/tool-registrar.d.ts.map +1 -1
  66. package/dist/mcp/tool-registrar.js +5 -2
  67. package/dist/mcp/types.d.ts +5 -0
  68. package/dist/mcp/types.d.ts.map +1 -1
  69. package/dist/relay/client.d.ts +11 -0
  70. package/dist/relay/client.d.ts.map +1 -1
  71. package/dist/relay/client.js +24 -6
  72. package/dist/relay/dispatcher.d.ts +12 -1
  73. package/dist/relay/dispatcher.d.ts.map +1 -1
  74. package/dist/relay/dispatcher.js +44 -42
  75. package/dist/sdk/ai/providers/faux.js +1 -1
  76. package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
  77. package/dist/sdk/ai/providers/openai-completions.js +57 -16
  78. package/dist/sdk/ai/providers/openrouter-images.d.ts.map +1 -1
  79. package/dist/sdk/ai/providers/openrouter-images.js +45 -9
  80. package/dist/sdk/ai/providers/simple-options.d.ts.map +1 -1
  81. package/dist/sdk/ai/providers/simple-options.js +2 -0
  82. package/dist/sdk/ai/types.d.ts +37 -1
  83. package/dist/sdk/ai/types.d.ts.map +1 -1
  84. package/dist/sdk/ai/utils/image-source.d.ts +121 -0
  85. package/dist/sdk/ai/utils/image-source.d.ts.map +1 -0
  86. package/dist/sdk/ai/utils/image-source.js +410 -0
  87. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  88. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +14 -0
  89. package/dist/sdk/coding-agent/core/extensions/types.d.ts +9 -0
  90. package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
  91. package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
  92. package/dist/sdk/coding-agent/core/system-prompt.js +15 -9
  93. package/dist/sdk/coding-agent/modes/rpc/rpc-mode.d.ts.map +1 -1
  94. package/dist/sdk/coding-agent/modes/rpc/rpc-mode.js +16 -3
  95. package/dist/sdk/coding-agent/utils/image-resize.d.ts.map +1 -1
  96. package/dist/sdk/coding-agent/utils/image-resize.js +7 -3
  97. package/dist/server/agent-bridge.d.ts +14 -0
  98. package/dist/server/agent-bridge.d.ts.map +1 -1
  99. package/dist/server/agent-bridge.js +105 -26
  100. package/dist/server/handlers/queue.d.ts.map +1 -1
  101. package/dist/server/handlers/queue.js +11 -34
  102. package/dist/server/image-attachments.d.ts +109 -0
  103. package/dist/server/image-attachments.d.ts.map +1 -0
  104. package/dist/server/image-attachments.js +195 -0
  105. package/dist/server/session-stream.d.ts +69 -0
  106. package/dist/server/session-stream.d.ts.map +1 -1
  107. package/dist/server/session-stream.js +388 -32
  108. package/dist/server/storage.d.ts.map +1 -1
  109. package/dist/server/storage.js +10 -31
  110. package/dist/server/ui/terminal-confirm.d.ts +145 -0
  111. package/dist/server/ui/terminal-confirm.d.ts.map +1 -0
  112. package/dist/server/ui/terminal-confirm.js +333 -0
  113. package/dist/server/wire.d.ts +30 -5
  114. package/dist/server/wire.d.ts.map +1 -1
  115. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agent/agents.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAC/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAqCD,eAAO,MAAM,aAAa,EAAE,WAAW,EAkDtC,CAAC;AA0DF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAO3E;AACD;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,yEAAyE;IACzE,YAAY,EAAE,IAAI,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG;IACF,YAAY,EAAE,KAAK,CAAC;IACpB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA6EnC;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,oBAAoB,CAmBtB;AACD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,UAAmB,EAC1B,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAepB;AACD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAgBpB;AACD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,IAAI,CAChB,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,CACT,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,CAgBR"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agent/agents.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAC/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6ED,eAAO,MAAM,aAAa,EAAE,WAAW,EAkDtC,CAAC;AA0DF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAO3E;AACD;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,yEAAyE;IACzE,YAAY,EAAE,IAAI,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG;IACF,YAAY,EAAE,KAAK,CAAC;IACpB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA6EnC;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,oBAAoB,CAmBtB;AACD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,UAAmB,EAC1B,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAepB;AACD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAgBpB;AACD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,IAAI,CAChB,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,CACT,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,CAgBR"}
@@ -29,6 +29,44 @@ const GHOST_SYSTEM_PROMPT = [
29
29
  "3. Integrate: combine subagent reports into one coherent outcome. Resolve gaps or conflicts by sending follow-up `subagent` tasks — never by doing the work yourself.",
30
30
  "4. Verify: after every implementation phase, delegate a `reviewer` pass before declaring the work done.",
31
31
  "",
32
+ "## Wave orchestration",
33
+ "",
34
+ "For non-trivial work, organize delegation into waves. A wave is one `subagent`",
35
+ "parallel call (a set of independent tasks) plus a barrier: you wait for all",
36
+ "reports, verify each against its done-criteria, write a short distillate",
37
+ "(~10-15 lines: decisions, key file paths, what was rejected and why,",
38
+ "distillate kept in session context — no PLAN.md file), then",
39
+ "decide continue / re-plan / stop before launching the next wave.",
40
+ "",
41
+ "Routing — waves vs single wave vs sequence:",
42
+ "- Independent tasks with no dependencies → one wave (single `subagent` parallel call).",
43
+ "- Tasks depending on other tasks' results → separate, later waves (topological",
44
+ " order by dependsOn). Never put dependent tasks in the same wave.",
45
+ "- A strict 1:1 dependency inside one logical step → `chain` with {previous},",
46
+ " not a one-task wave.",
47
+ "- More than ~8 parallel tasks, or fully independent fire-and-forget research →",
48
+ " split into smaller waves; `session_fanout` stays fire-and-forget instead of",
49
+ " `subagent` parallel (read worker reports yourself, results are not returned).",
50
+ "",
51
+ "Typical shapes:",
52
+ "- research-wave → implement-wave → review-wave (standard feature/fix flow).",
53
+ "- research-wave (wide, 3-6 researchers) → implement-wave (1-3 executors on",
54
+ " disjoint scopes) → review-wave (reviewer + fix executor if needed).",
55
+ "- Single research-wave → direct answer (read-only questions need no more waves).",
56
+ "",
57
+ "Per-task prompt shape: goal (one action) + scope (dirs/files/commands, explicit",
58
+ "exclusions) + facts the task needs (paths, line numbers from prior distillates)",
59
+ "+ expected report shape (done-criteria: Status/Dowody/Luki text block, no JSON).",
60
+ "Keep it under ~40 lines. Between waves pass only distillates and selected facts —",
61
+ "never full transcripts.",
62
+ "",
63
+ "Barrier checklist: all reports back → each verified against done-criteria →",
64
+ "contradictions resolved (follow-up task if needed) → distillate written",
65
+ "(kept in session context, compressed to 3-5 lines when older than 2 waves) →",
66
+ "continue (next wave from ready tasks) / re-plan (wave failed: rewrite wave list,",
67
+ "keep old distillates) / stop (goal met or provably unachievable — report why).",
68
+ "There is no wave-level timeout — only the per-subagent timeout applies.",
69
+ "",
32
70
  "## Anti-patterns — never do these",
33
71
  "",
34
72
  "- Do not explore the codebase yourself (no broad reads or searches) — that is `researcher`'s job.",
@@ -36,6 +74,8 @@ const GHOST_SYSTEM_PROMPT = [
36
74
  "- Do not skip the `reviewer` pass after implementation.",
37
75
  "- Do not treat delegation as optional: a session with zero `subagent` calls on a non-trivial task is a failed session.",
38
76
  "- Do not claim completion without subagent evidence (files changed, commands run, test results).",
77
+ "- Do not put dependent tasks in the same wave, and do not start the next wave",
78
+ " before the barrier (distillate + stop/continue decision) is done.",
39
79
  ].join("\n");
40
80
  export const SYSTEM_AGENTS = [
41
81
  {
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Backend-hosted image storage client.
3
+ *
4
+ * Uploads raw image bytes to the Aexol backend so that sessions only ever
5
+ * carry a stable URL (`/generated-images/<imageId>`) instead of a base64
6
+ * payload. The three-step contract is:
7
+ *
8
+ * 1. `POST {backendUrl}/generated-images/upload-url`
9
+ * -> `{ imageId, uploadUrl, url, expiresIn, requiredHeaders? }`
10
+ * 2. `PUT <uploadUrl>` (presigned, ~600s)
11
+ * with `Content-Type` + every header from `requiredHeaders`
12
+ * 3. `POST {backendUrl}/generated-images/<imageId>/confirm`
13
+ * -> `{ id, size, url }`
14
+ *
15
+ * Errors from the backend have the shape
16
+ * `{ "error": { "message": "...", "type": "..." } }` and are surfaced as a
17
+ * `GeneratedImageUploadError` carrying the HTTP status and the step that
18
+ * failed, so callers can decide whether to degrade gracefully.
19
+ *
20
+ * Shared by `image_generate` (Phase 1) and — in a later phase — user
21
+ * screenshots/attachments, hence `kind` is a parameter rather than a constant.
22
+ *
23
+ * @module @spectral/backend/generated-images
24
+ */
25
+ export type GeneratedImageKind = "generated" | "attachment";
26
+ export type UploadStep = "upload-url" | "put" | "confirm";
27
+ export interface UploadImageOptions {
28
+ /** Backend base url, e.g. `https://api.aexol.ai` (no trailing slash needed). */
29
+ backendUrl: string;
30
+ /** Machine JWT (`Authorization: Bearer <token>`). */
31
+ token: string;
32
+ /** Raw image bytes. */
33
+ bytes: Uint8Array;
34
+ /** MIME type of the bytes, e.g. `image/png`. */
35
+ contentType: string;
36
+ /** Storage bucket/kind on the backend. Defaults to `"generated"`. */
37
+ kind?: GeneratedImageKind;
38
+ /** Intrinsic pixel width, when known (optional metadata). */
39
+ width?: number;
40
+ /** Intrinsic pixel height, when known (optional metadata). */
41
+ height?: number;
42
+ /** Injectable for tests / alternative runtimes. Defaults to global `fetch`. */
43
+ fetchImpl?: typeof fetch;
44
+ /** Abort the whole sequence (e.g. tool cancellation). */
45
+ signal?: AbortSignal;
46
+ /** Per-request timeout. Defaults to 60s. */
47
+ timeoutMs?: number;
48
+ }
49
+ export interface UploadImageResult {
50
+ imageId: string;
51
+ /** Backend-relative url (`/generated-images/<imageId>`) — safe to persist in a session. */
52
+ url: string;
53
+ size: number;
54
+ }
55
+ export interface UploadUrlResponse {
56
+ imageId: string;
57
+ uploadUrl: string;
58
+ url: string;
59
+ expiresIn?: number;
60
+ requiredHeaders?: Record<string, string>;
61
+ }
62
+ export interface ConfirmResponse {
63
+ id: string;
64
+ size: number;
65
+ url: string;
66
+ }
67
+ /**
68
+ * Thrown for every failure of the upload sequence (including presigned PUT
69
+ * failures). `status` is `undefined` for network-level errors.
70
+ */
71
+ export declare class GeneratedImageUploadError extends Error {
72
+ readonly step: UploadStep;
73
+ readonly status?: number;
74
+ readonly type?: string;
75
+ constructor(message: string, options: {
76
+ step: UploadStep;
77
+ status?: number;
78
+ type?: string;
79
+ cause?: unknown;
80
+ });
81
+ }
82
+ /**
83
+ * Upload raw image bytes to backend storage and confirm them.
84
+ *
85
+ * @returns the image id, the backend-relative url and the confirmed byte size.
86
+ * @throws {GeneratedImageUploadError} on any HTTP or network failure, with a
87
+ * human-readable message taken from the backend `error.message` when present.
88
+ */
89
+ export declare function uploadImage(options: UploadImageOptions): Promise<UploadImageResult>;
90
+ //# sourceMappingURL=generated-images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-images.d.ts","sourceRoot":"","sources":["../../src/backend/generated-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE5D,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,KAAK,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IAClC,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,yDAAyD;IACzD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACZ;AAID;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAGtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQ/E;AA6ED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0HzF"}
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Backend-hosted image storage client.
3
+ *
4
+ * Uploads raw image bytes to the Aexol backend so that sessions only ever
5
+ * carry a stable URL (`/generated-images/<imageId>`) instead of a base64
6
+ * payload. The three-step contract is:
7
+ *
8
+ * 1. `POST {backendUrl}/generated-images/upload-url`
9
+ * -> `{ imageId, uploadUrl, url, expiresIn, requiredHeaders? }`
10
+ * 2. `PUT <uploadUrl>` (presigned, ~600s)
11
+ * with `Content-Type` + every header from `requiredHeaders`
12
+ * 3. `POST {backendUrl}/generated-images/<imageId>/confirm`
13
+ * -> `{ id, size, url }`
14
+ *
15
+ * Errors from the backend have the shape
16
+ * `{ "error": { "message": "...", "type": "..." } }` and are surfaced as a
17
+ * `GeneratedImageUploadError` carrying the HTTP status and the step that
18
+ * failed, so callers can decide whether to degrade gracefully.
19
+ *
20
+ * Shared by `image_generate` (Phase 1) and — in a later phase — user
21
+ * screenshots/attachments, hence `kind` is a parameter rather than a constant.
22
+ *
23
+ * @module @spectral/backend/generated-images
24
+ */
25
+ const DEFAULT_TIMEOUT_MS = 60_000;
26
+ /**
27
+ * Thrown for every failure of the upload sequence (including presigned PUT
28
+ * failures). `status` is `undefined` for network-level errors.
29
+ */
30
+ export class GeneratedImageUploadError extends Error {
31
+ step;
32
+ status;
33
+ type;
34
+ constructor(message, options) {
35
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
36
+ this.name = "GeneratedImageUploadError";
37
+ this.step = options.step;
38
+ this.status = options.status;
39
+ this.type = options.type;
40
+ }
41
+ }
42
+ function baseUrlOf(backendUrl) {
43
+ return backendUrl.replace(/\/+$/, "");
44
+ }
45
+ function joinUrl(base, path) {
46
+ return `${baseUrlOf(base)}${path.startsWith("/") ? path : `/${path}`}`;
47
+ }
48
+ function isRecord(value) {
49
+ return typeof value === "object" && value !== null;
50
+ }
51
+ function errorMessageFrom(body, status) {
52
+ const error = isRecord(body) && isRecord(body.error) ? body.error : undefined;
53
+ const message = typeof error?.message === "string" && error.message ? error.message : undefined;
54
+ const type = typeof error?.type === "string" && error.type ? error.type : undefined;
55
+ return { message: message ?? `HTTP ${status}`, type };
56
+ }
57
+ /** Parse a JSON body, tolerating empty/invalid bodies (presigned S3 replies are empty). */
58
+ async function readJson(response) {
59
+ const text = await response.text();
60
+ if (!text)
61
+ return undefined;
62
+ try {
63
+ return JSON.parse(text);
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ function requireString(value, field, step) {
70
+ if (typeof value !== "string" || !value) {
71
+ throw new GeneratedImageUploadError(`Backend response is missing "${field}"`, { step });
72
+ }
73
+ return value;
74
+ }
75
+ /**
76
+ * Run a single request with an optional timeout + external abort signal.
77
+ *
78
+ * The timeout never outlives the call and an already-aborted external signal
79
+ * aborts immediately.
80
+ */
81
+ async function fetchWithTimeout(url, init, options) {
82
+ const controller = new AbortController();
83
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
84
+ const onAbort = () => controller.abort();
85
+ if (options.signal) {
86
+ if (options.signal.aborted)
87
+ controller.abort();
88
+ else
89
+ options.signal.addEventListener("abort", onAbort, { once: true });
90
+ }
91
+ try {
92
+ return await options.fetchImpl(url, { ...init, signal: controller.signal });
93
+ }
94
+ finally {
95
+ clearTimeout(timer);
96
+ options.signal?.removeEventListener("abort", onAbort);
97
+ }
98
+ }
99
+ function networkError(step, error) {
100
+ if (error instanceof GeneratedImageUploadError)
101
+ return error;
102
+ const message = error instanceof Error ? error.message : String(error);
103
+ const aborted = error instanceof Error && error.name === "AbortError";
104
+ return new GeneratedImageUploadError(aborted
105
+ ? `Upload ${step} timed out or was cancelled (${message})`
106
+ : `Upload ${step} failed: ${message}`, { step, cause: error });
107
+ }
108
+ /**
109
+ * Upload raw image bytes to backend storage and confirm them.
110
+ *
111
+ * @returns the image id, the backend-relative url and the confirmed byte size.
112
+ * @throws {GeneratedImageUploadError} on any HTTP or network failure, with a
113
+ * human-readable message taken from the backend `error.message` when present.
114
+ */
115
+ export async function uploadImage(options) {
116
+ const { backendUrl, token, bytes, contentType, kind = "generated", width, height, fetchImpl, signal, timeoutMs, } = options;
117
+ if (!backendUrl) {
118
+ throw new GeneratedImageUploadError("Cannot upload image: no backend url configured", { step: "upload-url" });
119
+ }
120
+ if (!token) {
121
+ throw new GeneratedImageUploadError("Cannot upload image: not authenticated (missing machine token)", {
122
+ step: "upload-url",
123
+ });
124
+ }
125
+ const doFetch = fetchImpl ?? ((...args) => fetch(...args));
126
+ const base = baseUrlOf(backendUrl);
127
+ const authHeaders = { Authorization: `Bearer ${token}` };
128
+ // ── 1. mint an upload url ────────────────────────────────────────────────
129
+ let uploadUrlResponse;
130
+ try {
131
+ const body = {
132
+ contentType,
133
+ kind,
134
+ size: bytes.byteLength,
135
+ };
136
+ if (typeof width === "number" && Number.isFinite(width))
137
+ body.width = width;
138
+ if (typeof height === "number" && Number.isFinite(height))
139
+ body.height = height;
140
+ const response = await fetchWithTimeout(joinUrl(base, "/generated-images/upload-url"), {
141
+ method: "POST",
142
+ headers: { ...authHeaders, "Content-Type": "application/json" },
143
+ body: JSON.stringify(body),
144
+ }, { timeoutMs, signal, fetchImpl: doFetch });
145
+ const json = await readJson(response);
146
+ if (!response.ok) {
147
+ const { message, type } = errorMessageFrom(json, response.status);
148
+ throw new GeneratedImageUploadError(`Could not mint image upload url: ${message}`, {
149
+ step: "upload-url",
150
+ status: response.status,
151
+ type,
152
+ });
153
+ }
154
+ const payload = isRecord(json) ? json : {};
155
+ uploadUrlResponse = {
156
+ imageId: requireString(payload.imageId, "imageId", "upload-url"),
157
+ uploadUrl: requireString(payload.uploadUrl, "uploadUrl", "upload-url"),
158
+ url: requireString(payload.url, "url", "upload-url"),
159
+ expiresIn: payload.expiresIn,
160
+ requiredHeaders: payload.requiredHeaders,
161
+ };
162
+ }
163
+ catch (error) {
164
+ throw networkError("upload-url", error);
165
+ }
166
+ // ── 2. PUT the bytes to the presigned url ────────────────────────────────
167
+ try {
168
+ const headers = { "Content-Type": contentType };
169
+ // The backend signs extra headers (e.g. `x-amz-acl: private`) into the
170
+ // presigned url — they MUST be sent verbatim or S3 rejects the PUT.
171
+ if (isRecord(uploadUrlResponse.requiredHeaders)) {
172
+ for (const [key, value] of Object.entries(uploadUrlResponse.requiredHeaders)) {
173
+ if (typeof value === "string")
174
+ headers[key] = value;
175
+ }
176
+ }
177
+ // Normalize to a standalone buffer so byteOffset/byteLength of a
178
+ // subarray (Buffer view) cannot leak extra bytes into the body.
179
+ const payload = new Uint8Array(bytes.byteLength);
180
+ payload.set(bytes);
181
+ const response = await fetchWithTimeout(uploadUrlResponse.uploadUrl, { method: "PUT", headers, body: payload }, { timeoutMs, signal, fetchImpl: doFetch });
182
+ if (!response.ok) {
183
+ const detail = await response.text().catch(() => "");
184
+ const snippet = detail.replace(/\s+/g, " ").trim().slice(0, 300);
185
+ throw new GeneratedImageUploadError(`Image upload to storage failed: HTTP ${response.status}${snippet ? ` (${snippet})` : ""}`, { step: "put", status: response.status });
186
+ }
187
+ }
188
+ catch (error) {
189
+ throw networkError("put", error);
190
+ }
191
+ // ── 3. confirm so the backend can serve the image ────────────────────────
192
+ try {
193
+ const response = await fetchWithTimeout(joinUrl(base, `/generated-images/${encodeURIComponent(uploadUrlResponse.imageId)}/confirm`), { method: "POST", headers: { ...authHeaders } }, { timeoutMs, signal, fetchImpl: doFetch });
194
+ const json = await readJson(response);
195
+ if (!response.ok) {
196
+ const { message, type } = errorMessageFrom(json, response.status);
197
+ throw new GeneratedImageUploadError(`Could not confirm uploaded image: ${message}`, {
198
+ step: "confirm",
199
+ status: response.status,
200
+ type,
201
+ });
202
+ }
203
+ const payload = isRecord(json) ? json : {};
204
+ const confirmedSize = Number(payload.size);
205
+ return {
206
+ imageId: typeof payload.id === "string" && payload.id ? payload.id : uploadUrlResponse.imageId,
207
+ url: typeof payload.url === "string" && payload.url ? payload.url : uploadUrlResponse.url,
208
+ size: Number.isFinite(confirmedSize) && confirmedSize > 0 ? confirmedSize : bytes.byteLength,
209
+ };
210
+ }
211
+ catch (error) {
212
+ throw networkError("confirm", error);
213
+ }
214
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Backend routing for CLI-side image hosting.
3
+ *
4
+ * `uploadImage()` (see `./generated-images.ts`) needs the backend base url
5
+ * and the machine JWT. Those are exactly the credentials `agent-bridge.ts`
6
+ * receives for text models, so anything in the CLI that wants to host an
7
+ * image (generated images, tool screenshots, ...) resolves them the same way.
8
+ *
9
+ * Returns `null` when the CLI is not logged in / the JWT expired — callers
10
+ * then keep the image local (inline base64 / file on disk) instead of
11
+ * failing the turn.
12
+ *
13
+ * @module @spectral/backend/machine-routing
14
+ */
15
+ export interface BackendRouting {
16
+ backendUrl: string;
17
+ machineJwt: string;
18
+ }
19
+ /**
20
+ * Resolve `backendUrl` + `machineJwt`, mirroring how `agent-bridge.ts` obtains
21
+ * them (`serve.ts` → `registration.record`). `SPECTRAL_BACKEND_URL` wins over
22
+ * the configured api url so dev/staging overrides keep working.
23
+ */
24
+ export declare function resolveBackendRouting(): Promise<BackendRouting | null>;
25
+ //# sourceMappingURL=machine-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-routing.d.ts","sourceRoot":"","sources":["../../src/backend/machine-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAY5E"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Backend routing for CLI-side image hosting.
3
+ *
4
+ * `uploadImage()` (see `./generated-images.ts`) needs the backend base url
5
+ * and the machine JWT. Those are exactly the credentials `agent-bridge.ts`
6
+ * receives for text models, so anything in the CLI that wants to host an
7
+ * image (generated images, tool screenshots, ...) resolves them the same way.
8
+ *
9
+ * Returns `null` when the CLI is not logged in / the JWT expired — callers
10
+ * then keep the image local (inline base64 / file on disk) instead of
11
+ * failing the turn.
12
+ *
13
+ * @module @spectral/backend/machine-routing
14
+ */
15
+ import { getApiUrl, readConfig } from "../config.js";
16
+ import { loadMachine } from "../relay/machine-store.js";
17
+ import { isJwtExpired } from "../relay/registration.js";
18
+ /**
19
+ * Resolve `backendUrl` + `machineJwt`, mirroring how `agent-bridge.ts` obtains
20
+ * them (`serve.ts` → `registration.record`). `SPECTRAL_BACKEND_URL` wins over
21
+ * the configured api url so dev/staging overrides keep working.
22
+ */
23
+ export async function resolveBackendRouting() {
24
+ const cfg = await readConfig();
25
+ if (!cfg)
26
+ return null;
27
+ const machine = await loadMachine();
28
+ if (machine?.machineJwt && !isJwtExpired(machine.machineJwt)) {
29
+ const backendUrl = process.env.SPECTRAL_BACKEND_URL ||
30
+ getApiUrl(cfg.apiUrl).replace(/\/mcp\/?$/, "");
31
+ return { backendUrl, machineJwt: machine.machineJwt };
32
+ }
33
+ // No usable machine JWT — let the caller fall back to the local path.
34
+ return null;
35
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAYlE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAarE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAojBlF;AA+DD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5E"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAYlE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAarE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4kBlF;AAmFD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5E"}
@@ -312,7 +312,16 @@ export async function runServe(opts = {}) {
312
312
  publishMetaEvent: deferredPublishMetaEvent,
313
313
  mcpState: getLiveMcpState() ?? undefined,
314
314
  devProcessRegistry,
315
- refreshNativeExtensions: () => refreshNativeExtensionsInfo(cwd, r, registration.record.machineId),
315
+ refreshNativeExtensions: async () => {
316
+ await refreshNativeExtensionsInfo(cwd, r, registration.record.machineId);
317
+ // Parity with the `config_patch` path: also hot-reload running
318
+ // sessions so a manual refresh applies immediately.
319
+ // Swallow: the manager isolates per-session failures, but the
320
+ // aggregate promise must never become an unhandled rejection.
321
+ void manager.reloadExtensions().catch((err) => {
322
+ process.stderr.write(`[serve] extension reload failed: ${err instanceof Error ? err.message : String(err)}\n`);
323
+ });
324
+ },
316
325
  refreshCloudAgents: (teamId) => refreshCloudAgentsImpl({ backendUrl, machineJwt: registration.record.machineJwt, teamId }),
317
326
  }).then((response) => {
318
327
  r.send(response);
@@ -360,11 +369,12 @@ export async function runServe(opts = {}) {
360
369
  return;
361
370
  }
362
371
  if (frame.kind === "config_patch") {
363
- handleConfigPatch(frame, {
372
+ void handleConfigPatch(frame, {
364
373
  cwd,
365
374
  agentDir: getAgentDir(),
366
375
  relay: r,
367
376
  machineId: registration.record.machineId,
377
+ manager,
368
378
  });
369
379
  return;
370
380
  }
@@ -440,11 +450,18 @@ export async function runServe(opts = {}) {
440
450
  r.on("reconnect-scheduled", ({ delayMs, attempt }) => {
441
451
  process.stdout.write(`Reconnect attempt ${attempt} in ${delayMs}ms\n`);
442
452
  });
443
- r.on("error", (err) => {
444
- const msg = err instanceof Error ? err.message : String(err);
445
- process.stderr.write(`Relay error: ${msg}\n`);
446
- });
447
453
  }
454
+ // The `error` listener must be registered regardless of `silent`:
455
+ // without it, a transport hiccup in silent mode would surface as an
456
+ // unhandled 'error' event and kill the whole serve process. (RelayClient
457
+ // itself also guards this now, but the explicit listener keeps the
458
+ // stderr output contract for non-silent runs.)
459
+ r.on("error", (err) => {
460
+ if (silent)
461
+ return;
462
+ const msg = err instanceof Error ? err.message : String(err);
463
+ process.stderr.write(`Relay error: ${msg}\n`);
464
+ });
448
465
  // When the relay reports an auth failure (HTTP 401/403 on WS upgrade),
449
466
  // the machine JWT is invalid. Attempt re-registration with the original
450
467
  // credentials to obtain a fresh JWT, then restart the relay. If
@@ -683,6 +700,14 @@ async function handleConfigPatch(frame, deps) {
683
700
  settingsManager.setDisabledNativeExtensions([...disabledNative]);
684
701
  settingsManager.setEnabledNativeExtensions([...enabledNative]);
685
702
  await settingsManager.flush();
703
+ // Hot-reload already-running sessions so the toggle takes effect without
704
+ // restarting them. Fire-and-forget: busy sessions are skipped by the
705
+ // manager and pick the change up on their next session/bridge creation.
706
+ // Swallow: per-session failures are isolated by the manager; this only
707
+ // guards against an unhandled rejection from the aggregate promise.
708
+ void deps.manager?.reloadExtensions().catch((err) => {
709
+ process.stderr.write(`[serve] extension reload failed: ${err instanceof Error ? err.message : String(err)}\n`);
710
+ });
686
711
  // Broadcast updated state back to browsers
687
712
  await refreshNativeExtensionsInfo(deps.cwd, deps.relay, deps.machineId);
688
713
  }
@@ -36,6 +36,33 @@ export declare class BrowserService {
36
36
  private _recording;
37
37
  private closePromise;
38
38
  private launchLock;
39
+ /** Unique marker identifying the process tree of the current launch. */
40
+ private launchToken;
41
+ /**
42
+ * Token of the current *or previous* launch — only cleared by the next
43
+ * launch. Post-close fallbacks (`forceKillBrowserProcess()` / `browser_quit`)
44
+ * still need it to find the process tree after `close()` nulled launchToken.
45
+ * Every kill through this token is marker-verified first, so a recycled pid
46
+ * is never signalled.
47
+ */
48
+ private lastLaunchToken;
49
+ /**
50
+ * Root pid + full marker-matched tree, resolved once after a successful
51
+ * launch (single `ps` call) so exit/signal handlers can SIGKILL without
52
+ * running `execSync` from inside the handler.
53
+ */
54
+ private browserPid;
55
+ private browserPids;
56
+ /**
57
+ * Evidence for the synchronous teardown path: the pids that were matched by
58
+ * *our* marker at launch time, when they were matched and under which token.
59
+ * `killBrowserProcessSync()` may only signal pids listed here — a pid that we
60
+ * never marker-verified (or that no longer carries the marker) can belong to
61
+ * somebody else by now.
62
+ */
63
+ private verifiedBrowserPids;
64
+ private verifiedBrowserPidsAt;
65
+ private verifiedBrowserPidsToken;
39
66
  constructor(config?: Partial<BrowserConfig>);
40
67
  static getInstance(config?: Partial<BrowserConfig>): BrowserService;
41
68
  static resetInstance(): void;
@@ -97,6 +124,45 @@ export declare class BrowserService {
97
124
  getNetworkRequest(index: number): NetworkRequest | undefined;
98
125
  clearNetworkRequests(): void;
99
126
  close(): Promise<void>;
127
+ /**
128
+ * Last-resort teardown used by the `browser_quit` tool: SIGKILL the browser
129
+ * process without waiting for a graceful close. Safe to call at any time.
130
+ */
131
+ forceKillBrowserProcess(): void;
132
+ /**
133
+ * SIGKILL the browser process behind `browser` (defaults to the live one).
134
+ * `browser.close()` can hang, be rejected or be abandoned while the host
135
+ * process is shutting down, which leaves a visible Chromium window behind.
136
+ *
137
+ * `opts.scanProcessTable === false` skips `findBrowserPids()` (a `ps -eo`
138
+ * fork) — use it when nothing indicates a live browser. Only pids we already
139
+ * know about (or the one `browser.process()` exposes) are then signalled,
140
+ * each still marker-verified before the SIGKILL.
141
+ */
142
+ killBrowserProcess(browser?: Browser | null, opts?: {
143
+ scanProcessTable?: boolean;
144
+ }): void;
145
+ /**
146
+ * Exit/signal-handler path: SIGKILL the pids resolved right after launch.
147
+ * Deliberately execSync-free — spawning `ps` from inside an `exit` handler is
148
+ * unreliable (and can silently no-op).
149
+ *
150
+ * Never signals a pid that may not be ours: only pids whose marker was
151
+ * verified at launch time are eligible, and each candidate still has to pass
152
+ * a synchronous identity check — a `process.kill(pid, 0)` probe plus, where
153
+ * the platform allows it, a `/proc/<pid>/cmdline` read. Where no synchronous
154
+ * verification exists (no `/proc`), the launch-time evidence is trusted only
155
+ * while it is fresh (`PID_VERIFY_TTL_MS`): an orphaned browser window is
156
+ * preferable to SIGKILLing a recycled pid that now belongs to someone else.
157
+ */
158
+ killBrowserProcessSync(): void;
159
+ /**
160
+ * Resolve the browser's pids once, right after a successful launch, so the
161
+ * synchronous teardown path never has to inspect the process table again.
162
+ */
163
+ private resolveBrowserPids;
164
+ /** Find the pids of the browser processes we spawned (marker match). */
165
+ private findBrowserPids;
100
166
  startRecording(opts?: {
101
167
  outputDir?: string;
102
168
  filename?: string;
@@ -104,7 +170,15 @@ export declare class BrowserService {
104
170
  outputPath: string;
105
171
  startedAt: number;
106
172
  }>;
107
- stopRecording(): Promise<{
173
+ /**
174
+ * @param opts.relaunch Re-open a fresh non-recording context afterwards
175
+ * (default true, what `browser_record_stop` needs). Pass `false` when the
176
+ * session is shutting down — otherwise a brand new Chromium window is
177
+ * spawned only to be closed again immediately.
178
+ */
179
+ stopRecording(opts?: {
180
+ relaunch?: boolean;
181
+ }): Promise<{
108
182
  outputPath: string;
109
183
  durationSeconds: number;
110
184
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/browser-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IACtE,OAAO,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IACrE,MAAM,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CACrE;AAiBD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,aAAa,CAA8D;IACnF,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAKd;IACR,OAAO,CAAC,UAAU,CAA0F;IAC5G,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,UAAU,CAA8B;gBACpC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC;IAG3C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,cAAc;IAsBnE,MAAM,CAAC,aAAa,IAAI,IAAI;IAS5B;;;;;;OAMG;WACU,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IActC,MAAM,CAAC,WAAW,IAAI,OAAO;WAQhB,SAAS,CAAC,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBzE,MAAM,CAAC,oBAAoB,IAAI,MAAM,GAAG,IAAI;IAUtC,aAAa,IAAI,OAAO,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,cAAc,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC;KACZ,CAAC;IA8DF,UAAU,IAAI,OAAO;IAIrB,gBAAgB,IAAI,cAAc;IAIlC,aAAa,IAAI,IAAI;IAKf,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAS9D,YAAY,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAKvD,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAY1B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1D,QAAQ,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAO1D,SAAS,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IASvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAS1D,OAAO,CAAC,wBAAwB;IAchC,iBAAiB,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAGvD,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYzE,kBAAkB,CAChB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,EAC9C,GAAG,UAAQ,GACV,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IASzD,oBAAoB,IAAI,IAAI;IAG5B,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAwBrC,kBAAkB,IAAI,cAAc,EAAE;IAGtC,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAG5D,oBAAoB,IAAI,IAAI;IAItB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCtB,cAAc,CAClB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAC/C,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA6D/C,aAAa,IAAI,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAsC/E,eAAe,IAAI;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IASxG,YAAY,IAAI,IAAI;IAIpB,SAAS,IAAI,aAAa;CAG3B"}
1
+ {"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/browser-service.ts"],"names":[],"mappings":"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"}