@gajae-code/coding-agent 0.11.3 → 0.11.5

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 (213) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/types/cli/notify-cli.d.ts +5 -1
  3. package/dist/types/cli/worktree-cli.d.ts +8 -25
  4. package/dist/types/commands/gc.d.ts +4 -0
  5. package/dist/types/commands/worktree.d.ts +7 -32
  6. package/dist/types/config/settings-schema.d.ts +20 -0
  7. package/dist/types/export/html/template.generated.d.ts +1 -1
  8. package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
  9. package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
  10. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  11. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
  12. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
  13. package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
  14. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
  15. package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
  16. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  17. package/dist/types/harness-control-plane/owner.d.ts +3 -1
  18. package/dist/types/internal-urls/local-protocol.d.ts +18 -0
  19. package/dist/types/main.d.ts +2 -2
  20. package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +8 -3
  22. package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
  23. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
  24. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  25. package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
  26. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
  27. package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
  28. package/dist/types/modes/controllers/input-controller.d.ts +8 -2
  29. package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
  30. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
  31. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
  32. package/dist/types/plan-mode/approved-plan.d.ts +1 -0
  33. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  34. package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
  35. package/dist/types/sdk/broker/broker.d.ts +1 -0
  36. package/dist/types/sdk/broker/discovery.d.ts +10 -0
  37. package/dist/types/sdk/broker/ensure.d.ts +24 -1
  38. package/dist/types/sdk/broker/internal.d.ts +3 -0
  39. package/dist/types/sdk/broker/session-index.d.ts +12 -0
  40. package/dist/types/sdk/broker/state-version.d.ts +2 -1
  41. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
  43. package/dist/types/sdk/bus/config-commands.d.ts +8 -0
  44. package/dist/types/sdk/bus/config.d.ts +22 -0
  45. package/dist/types/sdk/bus/index.d.ts +162 -2
  46. package/dist/types/sdk/bus/notification-service.d.ts +71 -8
  47. package/dist/types/sdk/bus/session-control.d.ts +11 -8
  48. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
  49. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
  50. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
  51. package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
  52. package/dist/types/sdk/session.d.ts +16 -1
  53. package/dist/types/secrets/index.d.ts +4 -2
  54. package/dist/types/secrets/obfuscator.d.ts +1 -1
  55. package/dist/types/session/agent-session.d.ts +31 -2
  56. package/dist/types/session/artifacts.d.ts +30 -8
  57. package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
  58. package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
  59. package/dist/types/session/session-manager.d.ts +88 -18
  60. package/dist/types/session/session-storage.d.ts +27 -0
  61. package/dist/types/session/streaming-output.d.ts +4 -0
  62. package/dist/types/task/executor.d.ts +9 -0
  63. package/dist/types/tool-discovery/tool-index.d.ts +2 -0
  64. package/dist/types/tools/ask.d.ts +87 -12
  65. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  66. package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
  67. package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
  68. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
  69. package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
  70. package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/path-utils.d.ts +9 -14
  73. package/dist/types/tools/render-utils.d.ts +1 -8
  74. package/dist/types/tools/resource-gc.d.ts +14 -0
  75. package/dist/types/utils/image-loading.d.ts +23 -0
  76. package/dist/types/utils/image-resize.d.ts +2 -0
  77. package/dist/types/utils/pasted-image-loading.d.ts +39 -0
  78. package/dist/types/utils/pasted-image-path.d.ts +21 -19
  79. package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
  80. package/dist/types/web/scrapers/types.d.ts +1 -5
  81. package/package.json +25 -9
  82. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  83. package/scripts/run-threat-model-check.ts +60 -0
  84. package/src/cli/notify-cli.ts +40 -18
  85. package/src/cli/worktree-cli.ts +9 -287
  86. package/src/cli.ts +10 -9
  87. package/src/commands/gc.ts +11 -1
  88. package/src/commands/sdk.ts +10 -4
  89. package/src/commands/worktree.ts +10 -53
  90. package/src/config/model-profiles.ts +15 -15
  91. package/src/config/settings-schema.ts +17 -0
  92. package/src/daemon/runtime.ts +2 -2
  93. package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
  94. package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
  95. package/src/defaults/gjc/skills/team/SKILL.md +7 -9
  96. package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
  97. package/src/eval/js/shared/runtime.ts +1 -1
  98. package/src/eval/py/index.ts +2 -2
  99. package/src/export/html/template.generated.ts +1 -1
  100. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  101. package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
  102. package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
  103. package/src/gjc-runtime/deep-interview-state.ts +127 -3
  104. package/src/gjc-runtime/gc-render.ts +19 -1
  105. package/src/gjc-runtime/gc-runtime.ts +68 -3
  106. package/src/gjc-runtime/launch-tmux.ts +61 -6
  107. package/src/gjc-runtime/managed-owner-admission.ts +352 -0
  108. package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
  109. package/src/gjc-runtime/state-argv.ts +292 -0
  110. package/src/gjc-runtime/state-runtime.ts +80 -222
  111. package/src/gjc-runtime/team-runtime.ts +1 -1
  112. package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
  113. package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  114. package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
  115. package/src/harness-control-plane/owner.ts +59 -3
  116. package/src/hindsight/client.ts +65 -2
  117. package/src/internal-urls/agent-protocol.ts +76 -5
  118. package/src/internal-urls/docs-index.generated.ts +7 -6
  119. package/src/internal-urls/local-protocol.ts +416 -18
  120. package/src/lsp/index.ts +4 -88
  121. package/src/main.ts +37 -13
  122. package/src/modes/acp/acp-agent.ts +8 -0
  123. package/src/modes/components/ansi-display-validator.ts +54 -0
  124. package/src/modes/components/custom-editor.ts +103 -32
  125. package/src/modes/components/custom-provider-wizard.ts +26 -1
  126. package/src/modes/components/notifications-settings-editor.ts +26 -2
  127. package/src/modes/components/session-observer-overlay.ts +40 -37
  128. package/src/modes/components/settings-selector.ts +3 -1
  129. package/src/modes/components/tool-transcript-format.ts +393 -0
  130. package/src/modes/components/transcript-viewer-overlay.ts +63 -1
  131. package/src/modes/controllers/goal-mode-controller.ts +20 -1
  132. package/src/modes/controllers/input-controller.ts +98 -37
  133. package/src/modes/controllers/plan-mode-controller.ts +2 -0
  134. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  135. package/src/modes/controllers/selector-controller.ts +71 -9
  136. package/src/modes/interactive-mode.ts +14 -2
  137. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
  138. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
  139. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
  140. package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
  141. package/src/plan-mode/approved-plan.ts +3 -1
  142. package/src/prompts/agents/architect.md +1 -0
  143. package/src/prompts/agents/critic.md +1 -0
  144. package/src/prompts/system/custom-system-prompt.md +1 -1
  145. package/src/prompts/system/system-prompt.md +2 -1
  146. package/src/prompts/tools/lsp.md +3 -3
  147. package/src/runtime-mcp/manager.ts +16 -1
  148. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  149. package/src/sdk/broker/broker.ts +139 -32
  150. package/src/sdk/broker/discovery.ts +125 -0
  151. package/src/sdk/broker/ensure.ts +103 -19
  152. package/src/sdk/broker/internal.ts +10 -0
  153. package/src/sdk/broker/session-index.ts +309 -38
  154. package/src/sdk/broker/state-version.ts +5 -2
  155. package/src/sdk/bus/chat-daemon-cli.ts +72 -10
  156. package/src/sdk/bus/chat-daemon-control.ts +526 -130
  157. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  158. package/src/sdk/bus/config-commands.ts +22 -0
  159. package/src/sdk/bus/config.ts +37 -0
  160. package/src/sdk/bus/index.ts +384 -92
  161. package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
  162. package/src/sdk/bus/notification-service.ts +284 -62
  163. package/src/sdk/bus/recent-activity.ts +14 -5
  164. package/src/sdk/bus/session-control.ts +150 -44
  165. package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
  166. package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
  167. package/src/sdk/bus/telegram-daemon-control.ts +294 -76
  168. package/src/sdk/bus/telegram-daemon.ts +1842 -281
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/session.ts +155 -27
  171. package/src/secrets/index.ts +53 -10
  172. package/src/secrets/obfuscator.ts +21 -18
  173. package/src/secrets/regex.ts +2 -0
  174. package/src/session/agent-session.ts +495 -253
  175. package/src/session/artifacts.ts +188 -23
  176. package/src/session/context-estimation.ts +7 -2
  177. package/src/session/internal/managed-session-scope.ts +215 -39
  178. package/src/session/internal/managed-session-storage.ts +1152 -46
  179. package/src/session/session-manager.ts +1055 -148
  180. package/src/session/session-storage.ts +109 -11
  181. package/src/session/streaming-output.ts +6 -0
  182. package/src/task/executor.ts +71 -26
  183. package/src/task/index.ts +63 -20
  184. package/src/tool-discovery/tool-index.ts +18 -0
  185. package/src/tools/ask.ts +329 -57
  186. package/src/tools/bash-allowed-prefixes.ts +79 -29
  187. package/src/tools/bash-skill-urls.ts +14 -3
  188. package/src/tools/bash.ts +2 -2
  189. package/src/tools/browser/launch.ts +3 -1
  190. package/src/tools/browser/readable.ts +1 -1
  191. package/src/tools/browser/screenshot-format.ts +34 -0
  192. package/src/tools/browser/tab-protocol.ts +3 -0
  193. package/src/tools/browser/tab-supervisor.ts +125 -122
  194. package/src/tools/browser/tab-worker-entry.ts +33 -14
  195. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  196. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  197. package/src/tools/browser/tab-worker.ts +7 -5
  198. package/src/tools/index.ts +4 -0
  199. package/src/tools/path-utils.ts +42 -35
  200. package/src/tools/plan-mode-guard.ts +1 -0
  201. package/src/tools/read.ts +7 -1
  202. package/src/tools/render-utils.ts +1 -27
  203. package/src/tools/resource-gc.ts +123 -34
  204. package/src/tools/sqlite-reader.ts +3 -2
  205. package/src/tools/subagent.ts +2 -0
  206. package/src/utils/image-loading.ts +85 -34
  207. package/src/utils/image-resize.ts +16 -9
  208. package/src/utils/pasted-image-loading.ts +514 -0
  209. package/src/utils/pasted-image-path.ts +223 -98
  210. package/src/web/scrapers/html-to-markdown.ts +68 -0
  211. package/src/web/scrapers/readthedocs.ts +1 -1
  212. package/src/web/scrapers/types.ts +1 -68
  213. package/src/web/search/providers/openai-compatible.ts +76 -23
@@ -3,19 +3,38 @@ import type { Transport, WorkerInbound, WorkerOutbound } from "./tab-protocol";
3
3
  import { WorkerCore } from "./tab-worker";
4
4
 
5
5
  if (!parentPort) throw new Error("tab-worker-entry: missing parentPort");
6
+ const port = parentPort;
7
+ port.once("message", bootstrap => {
8
+ if (!isBootstrap(bootstrap)) {
9
+ port.postMessage({ type: "bootstrap-failed", error: "Invalid tab worker bootstrap." });
10
+ port.close();
11
+ return;
12
+ }
6
13
 
7
- const transport: Transport = {
8
- send(msg, transferList) {
9
- parentPort!.postMessage(msg, transferList ?? []);
10
- },
11
- onMessage(handler) {
12
- const wrap = (message: unknown): void => handler(message as WorkerOutbound | WorkerInbound);
13
- parentPort!.on("message", wrap);
14
- return () => parentPort!.off("message", wrap);
15
- },
16
- close() {
17
- parentPort!.close();
18
- },
19
- };
14
+ const transport: Transport = {
15
+ send(msg, transferList) {
16
+ port.postMessage(msg, transferList ?? []);
17
+ },
18
+ onMessage(handler) {
19
+ const wrap = (message: unknown): void => handler(message as WorkerOutbound | WorkerInbound);
20
+ port.on("message", wrap);
21
+ return () => port.off("message", wrap);
22
+ },
23
+ close() {
24
+ port.close();
25
+ },
26
+ };
20
27
 
21
- new WorkerCore(transport);
28
+ new WorkerCore(transport);
29
+ port.postMessage({ type: "bootstrap-ready", version: 1, mode: "native-free" });
30
+ });
31
+
32
+ function isBootstrap(message: unknown): message is Extract<WorkerInbound, { type: "bootstrap" }> {
33
+ return (
34
+ typeof message === "object" &&
35
+ message !== null &&
36
+ (message as { type?: unknown }).type === "bootstrap" &&
37
+ (message as { version?: unknown }).version === 1 &&
38
+ (message as { mode?: unknown }).mode === "native-free"
39
+ );
40
+ }
@@ -0,0 +1,67 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+ import * as url from "node:url";
4
+
5
+ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
6
+ const TOP_LEVEL_INTERNAL_URL_PREFIXES = ["agent://", "artifact://", "rule://", "local://"] as const;
7
+
8
+ function normalizeAtPrefix(filePath: string): string {
9
+ if (!filePath.startsWith("@")) return filePath;
10
+
11
+ const withoutAt = filePath.slice(1);
12
+ if (
13
+ withoutAt.startsWith("/") ||
14
+ withoutAt === "~" ||
15
+ withoutAt.startsWith("~/") ||
16
+ path.win32.isAbsolute(withoutAt) ||
17
+ withoutAt.startsWith("agent://") ||
18
+ withoutAt.startsWith("artifact://") ||
19
+ withoutAt.startsWith("rule://") ||
20
+ withoutAt.startsWith("local:")
21
+ ) {
22
+ return withoutAt;
23
+ }
24
+
25
+ return filePath;
26
+ }
27
+
28
+ function expandPath(filePath: string): string {
29
+ const normalized = normalizeAtPrefix(filePath).replace(UNICODE_SPACES, " ");
30
+ if (normalized.toLowerCase().startsWith("file://")) {
31
+ try {
32
+ return expandTilde(url.fileURLToPath(normalized));
33
+ } catch {
34
+ return normalized;
35
+ }
36
+ }
37
+ return expandTilde(normalized);
38
+ }
39
+
40
+ function expandTilde(filePath: string): string {
41
+ const home = os.homedir();
42
+ if (filePath === "~") return home;
43
+ if (filePath.startsWith("~/") || filePath.startsWith("~\\")) return home + filePath.slice(1);
44
+ if (filePath.startsWith("~")) return path.join(home, filePath.slice(1));
45
+ return filePath;
46
+ }
47
+
48
+ function normalizeLocalScheme(filePath: string): string {
49
+ return filePath.replace(/^(local:)\/(?!\/)/, "$1//");
50
+ }
51
+
52
+ /** Resolve filesystem-only worker inputs without evaluating the broad path utility module. */
53
+ export function resolveTabWorkerPath(filePath: string, cwd: string): string {
54
+ const normalized = normalizeLocalScheme(filePath);
55
+ const expanded = expandPath(normalized);
56
+ const expandedAndNormalized = normalizeLocalScheme(expanded);
57
+ for (const prefix of TOP_LEVEL_INTERNAL_URL_PREFIXES) {
58
+ if (expandedAndNormalized.startsWith(prefix)) {
59
+ throw new Error(
60
+ `Path "${normalized}" uses internal scheme "${prefix}" and must be resolved through the proper protocol handler, not as a filesystem path.`,
61
+ );
62
+ }
63
+ }
64
+
65
+ if (/^\/+$/u.test(expanded)) return cwd;
66
+ return path.isAbsolute(expanded) ? expanded : path.resolve(cwd, expanded);
67
+ }
@@ -0,0 +1,87 @@
1
+ import { isCompiledBinary } from "@gajae-code/utils/env";
2
+ import type { WorkerInbound, WorkerOutbound } from "./tab-protocol";
3
+
4
+ interface SmokeWorkerHandle {
5
+ postMessage(message: WorkerInbound): void;
6
+ addEventListener(type: string, listener: EventListener): void;
7
+ removeEventListener(type: string, listener: EventListener): void;
8
+ terminate(): void;
9
+ }
10
+
11
+ const SMOKE_TIMEOUT_MS = 5_000;
12
+ const TAB_WORKER_MODE = "native-free";
13
+
14
+ function smokeStartupError(stage: string): Error {
15
+ return new Error(
16
+ `Tab worker startup failed (stage=${stage}, mode=${TAB_WORKER_MODE}, platform=${process.platform}).`,
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Starts the real tab worker entry without connecting to a browser, then proves
22
+ * its bootstrap and shutdown protocol. The compiled branch deliberately keeps
23
+ * a static literal for Bun's --compile worker discovery.
24
+ */
25
+ export async function smokeTestTabWorker(timeoutMs = SMOKE_TIMEOUT_MS): Promise<void> {
26
+ const worker = isCompiledBinary()
27
+ ? new Worker("./packages/coding-agent/src/tools/browser/tab-worker-entry.ts", { type: "module" })
28
+ : new Worker(new URL("./tab-worker-entry.ts", import.meta.url).href, { type: "module" });
29
+ await smokeTestTabWorkerWithWorkerForTest(worker, timeoutMs);
30
+ }
31
+
32
+ /** Test-only: verify the smoke handshake against a controlled worker transport. */
33
+ export async function smokeTestTabWorkerWithWorkerForTest(worker: SmokeWorkerHandle, timeoutMs: number): Promise<void> {
34
+ type SmokePhase = "await-bootstrap" | "await-closed";
35
+ let phase: SmokePhase = "await-bootstrap";
36
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
37
+ let settled = false;
38
+ let timeout: NodeJS.Timeout | undefined;
39
+
40
+ const cleanup = (): void => {
41
+ if (timeout) clearTimeout(timeout);
42
+ worker.removeEventListener("message", onMessage);
43
+ worker.removeEventListener("error", onError);
44
+ worker.removeEventListener("messageerror", onMessageError);
45
+ };
46
+ const succeed = (): void => {
47
+ if (settled) return;
48
+ settled = true;
49
+ resolve();
50
+ };
51
+ const fail = (stage: string): void => {
52
+ if (settled) return;
53
+ settled = true;
54
+ reject(smokeStartupError(stage));
55
+ };
56
+ const onMessage: EventListener = event => {
57
+ const message = (event as MessageEvent<WorkerOutbound>).data;
58
+ if (message.type === "log") return;
59
+ if (phase === "await-bootstrap") {
60
+ if (message.type === "bootstrap-ready" && message.version === 1 && message.mode === TAB_WORKER_MODE) {
61
+ phase = "await-closed";
62
+ worker.postMessage({ type: "close" } satisfies WorkerInbound);
63
+ } else if (message.type === "bootstrap-failed") {
64
+ fail("bootstrap");
65
+ } else {
66
+ fail("protocol-phase");
67
+ }
68
+ return;
69
+ }
70
+ if (message.type === "closed") succeed();
71
+ else fail("protocol-phase");
72
+ };
73
+ const onError: EventListener = () => fail("error");
74
+ const onMessageError: EventListener = () => fail("messageerror");
75
+
76
+ worker.addEventListener("message", onMessage);
77
+ worker.addEventListener("error", onError);
78
+ worker.addEventListener("messageerror", onMessageError);
79
+ timeout = setTimeout(() => fail("smoke-timeout"), timeoutMs);
80
+ try {
81
+ worker.postMessage({ type: "bootstrap", version: 1, mode: TAB_WORKER_MODE } satisfies WorkerInbound);
82
+ await promise;
83
+ } finally {
84
+ cleanup();
85
+ worker.terminate();
86
+ }
87
+ }
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
 
5
- import { Snowflake, untilAborted } from "@gajae-code/utils";
5
+ import { untilAborted } from "@gajae-code/utils/abortable";
6
+ import { Snowflake } from "@gajae-code/utils/snowflake";
6
7
  import type { HTMLElement } from "linkedom";
7
8
  import type {
8
9
  Browser,
@@ -17,8 +18,6 @@ import type {
17
18
  import { JsRuntime, type RuntimeHooks } from "../../eval/js/shared/runtime";
18
19
  import type { JsDisplayOutput } from "../../eval/js/shared/types";
19
20
  import { resizeImage } from "../../utils/image-resize";
20
- import { resolveToCwd } from "../path-utils";
21
- import { formatScreenshot } from "../render-utils";
22
21
  import { ToolAbortError, ToolError, throwIfAborted } from "../tool-errors";
23
22
  import {
24
23
  applyStealthPatches,
@@ -28,6 +27,7 @@ import {
28
27
  loadPuppeteerInWorker,
29
28
  } from "./launch";
30
29
  import { extractReadableFromHtml, type ReadableFormat, type ReadableResult } from "./readable";
30
+ import { formatScreenshot } from "./screenshot-format";
31
31
  import type {
32
32
  Observation,
33
33
  ObservationEntry,
@@ -41,6 +41,7 @@ import type {
41
41
  WorkerInbound,
42
42
  WorkerInitPayload,
43
43
  } from "./tab-protocol";
44
+ import { resolveTabWorkerPath } from "./tab-worker-path-resolver";
44
45
 
45
46
  declare global {
46
47
  interface Element extends HTMLElement {}
@@ -813,7 +814,8 @@ export class WorkerCore {
813
814
  { type: "image", data: buffer.toBase64(), mimeType: "image/png" },
814
815
  { maxWidth: 1024, maxHeight: 1024, maxBytes: 150 * 1024, jpegQuality: 70 },
815
816
  );
816
- const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
817
+ const explicitPath = opts.save ? resolveTabWorkerPath(opts.save, session.cwd) : undefined;
818
+
817
819
  const dest =
818
820
  explicitPath ??
819
821
  (session.browserScreenshotDir
@@ -950,7 +952,7 @@ export class WorkerCore {
950
952
  page.locator(normalizeSelector(selector)).setTimeout(timeoutMs).waitHandle(),
951
953
  )) as ElementHandle;
952
954
  try {
953
- const absolute = filePaths.map(filePath => resolveToCwd(filePath, session.cwd));
955
+ const absolute = filePaths.map(filePath => resolveTabWorkerPath(filePath, session.cwd));
954
956
  const upload = handle as unknown as { uploadFile: (...paths: string[]) => Promise<void> };
955
957
  const tagName = (await untilAborted(signal, () =>
956
958
  handle.evaluate(el => (el as unknown as { tagName: string }).tagName),
@@ -216,6 +216,8 @@ export interface ToolSession {
216
216
  cwd: string;
217
217
  /** Whether UI is available */
218
218
  hasUI: boolean;
219
+ /** Whether this session will bind a workflow-gate emitter after tool construction. */
220
+ workflowGateEligible?: boolean;
219
221
  /** Skip Python kernel availability check and warmup */
220
222
  skipPythonPreflight?: boolean;
221
223
  /** Pre-loaded context files (AGENTS.md, etc) */
@@ -262,6 +264,8 @@ export interface ToolSession {
262
264
  requestForegroundBashBackground?: () => boolean;
263
265
  /** Get session ID */
264
266
  getSessionId?: () => string | null;
267
+ /** Whether local:// must use external managed scratch instead of artifacts/local. */
268
+ isManagedSessionDestination?: () => boolean;
265
269
  /** Get Hindsight runtime state for this agent session. */
266
270
  getHindsightSessionState?: () => HindsightSessionState | undefined;
267
271
  /** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import * as url from "node:url";
5
- import { isEnoent } from "@gajae-code/utils";
5
+ import { isEnoent } from "@gajae-code/utils/fs-error";
6
6
  import { InternalUrlRouter } from "../internal-urls";
7
7
  import { ToolError } from "./tool-errors";
8
8
 
@@ -10,13 +10,9 @@ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
10
10
  const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*|raw|conflicts)$/i;
11
11
  const FILE_LINE_RANGE_ONLY_RE = /^L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*$/i;
12
12
  const FILE_RAW_ONLY_RE = /^raw$/i;
13
- // Permissive selector chunk for internal URLs — accepts well-formed selectors
14
- // plus common malformed shapes (e.g. `:-N`) so the read tool peels the entire
15
- // selector chain off before dispatching to a protocol handler.
16
- const INTERNAL_URL_SELECTOR_PART_RE =
17
- /^(?:raw|conflicts|L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*|-\d+(?:[-+]\d+)?)$/i;
18
- // Schemes whose host grammar is identifier-shaped, so any trailing
19
- // `:<selector-chunk>` is unambiguously a read-tool selector.
13
+ // Schemes whose authority grammar is identifier-shaped and may therefore carry
14
+ // a read selector immediately after the authority. Colons in a path, query, or
15
+ // fragment remain part of the URL and are never considered selectors.
20
16
  const INTERNAL_SCHEMES_WITH_SELECTORS: Record<string, true> = {
21
17
  agent: true,
22
18
  artifact: true,
@@ -148,39 +144,50 @@ export function splitPathAndSel(rawPath: string): { path: string; sel?: string }
148
144
  /**
149
145
  * Variant of {@link splitPathAndSel} for internal URLs (`scheme://...`).
150
146
  *
151
- * The filesystem-path splitter is intentionally conservative: it refuses to
152
- * peel a trailing `:<chunk>` unless that chunk matches the strict selector
153
- * grammar. That rule is right for filesystem paths (a file named `a:1-50` is
154
- * legal) but wrong for internal URLs, where any trailing `:<chunk>` after the
155
- * scheme is unambiguously a read-tool selector — even if malformed (e.g.
156
- * `artifact://3:raw:-100`).
157
- *
158
- * This function iteratively peels selector-shaped chunks (well-formed plus
159
- * common malformed shapes like `:-N`) so the rest of the read tool can pass a
160
- * clean URL to the protocol handler and surface selector errors via parseSel
161
- * instead of as misleading "host invalid" errors from the handler.
162
- *
163
- * Falls back to the input unchanged when nothing matches.
147
+ * Artifact authorities are generated identifiers, so their complete tail is
148
+ * unambiguously a selector and malformed selectors can be rejected before
149
+ * resolving the artifact. Other resources may legitimately use colons in
150
+ * their identities, so they only lose strict, recognized selector suffixes.
151
+ * Skill authorities additionally use the active registry to distinguish a
152
+ * skill name from its selector without making path-utils depend on skills.
164
153
  */
165
- export function splitInternalUrlSel(rawPath: string): { path: string; sel?: string } {
154
+ export function splitInternalUrlSel(
155
+ rawPath: string,
156
+ options: { activeSkillNames?: readonly string[] } = {},
157
+ ): { path: string; sel?: string } {
166
158
  const schemeMatch = rawPath.match(INTERNAL_URL_SCHEME_RE);
167
159
  if (!schemeMatch) return { path: rawPath };
168
- if (!INTERNAL_SCHEMES_WITH_SELECTORS[schemeMatch[1].toLowerCase()]) return { path: rawPath };
160
+ const scheme = schemeMatch[1].toLowerCase();
161
+ if (!INTERNAL_SCHEMES_WITH_SELECTORS[scheme]) return { path: rawPath };
169
162
 
170
163
  const schemeEnd = schemeMatch[0].length;
171
- let path = rawPath;
172
- const chunks: string[] = [];
173
- while (true) {
174
- const colon = path.lastIndexOf(":");
175
- // Stop before crossing into the scheme separator `://`.
176
- if (colon < schemeEnd) break;
177
- const tail = path.slice(colon + 1);
178
- if (!INTERNAL_URL_SELECTOR_PART_RE.test(tail)) break;
179
- chunks.unshift(tail);
180
- path = path.slice(0, colon);
164
+ const authorityTerminator = rawPath.slice(schemeEnd).search(/[/?#]/);
165
+ if (authorityTerminator !== -1) return { path: rawPath };
166
+ const authority = rawPath.slice(schemeEnd);
167
+ const firstColon = authority.indexOf(":");
168
+ if (firstColon === -1) return { path: rawPath };
169
+
170
+ if (scheme === "artifact") {
171
+ return { path: rawPath.slice(0, schemeEnd + firstColon), sel: authority.slice(firstColon + 1) };
172
+ }
173
+
174
+ if (scheme === "skill") {
175
+ const activeSkillNames = options.activeSkillNames ?? [];
176
+ if (activeSkillNames.includes(authority)) return { path: rawPath };
177
+ const skillName = activeSkillNames
178
+ .filter(name => authority.startsWith(`${name}:`))
179
+ .sort((a, b) => b.length - a.length)[0];
180
+ if (skillName) {
181
+ return {
182
+ path: `${rawPath.slice(0, schemeEnd)}${skillName}`,
183
+ sel: authority.slice(skillName.length + 1),
184
+ };
185
+ }
181
186
  }
182
- if (chunks.length === 0) return { path: rawPath };
183
- return { path, sel: chunks.join(":") };
187
+
188
+ const strict = splitPathAndSel(authority);
189
+ if (strict.sel === undefined) return { path: rawPath };
190
+ return { path: `${rawPath.slice(0, schemeEnd)}${strict.path}`, sel: strict.sel };
184
191
  }
185
192
 
186
193
  function assertNotInternalUrl(expanded: string, original: string): void {
@@ -11,6 +11,7 @@ function resolveRawPath(session: ToolSession, targetPath: string): string {
11
11
  if (normalized.startsWith(LOCAL_SCHEME_PREFIX)) {
12
12
  return resolveLocalUrlToPath(normalized, {
13
13
  getArtifactsDir: session.getArtifactsDir,
14
+ isManagedDestination: session.isManagedSessionDestination,
14
15
  getSessionId: session.getSessionId,
15
16
  });
16
17
  }
package/src/tools/read.ts CHANGED
@@ -11,6 +11,7 @@ import * as z from "zod/v4";
11
11
  import { getFileReadCache } from "../edit/file-read-cache";
12
12
  import { isNotebookPath, readEditableNotebookText } from "../edit/notebook";
13
13
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
+ import { getActiveSkills } from "../extensibility/skills";
14
15
  import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../hashline/hash";
15
16
  import { InternalUrlRouter } from "../internal-urls";
16
17
  import { parseInternalUrl } from "../internal-urls/parse";
@@ -1585,8 +1586,13 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1585
1586
  // off the URL and surfaced via parseSel rather than confusing handlers.
1586
1587
  const internalRouter = InternalUrlRouter.instance();
1587
1588
  if (internalRouter.canHandle(readPath)) {
1588
- const internalTarget = splitInternalUrlSel(readPath);
1589
+ const internalTarget = splitInternalUrlSel(readPath, {
1590
+ activeSkillNames: getActiveSkills().map(skill => skill.name),
1591
+ });
1589
1592
  const parsed = parseSel(internalTarget.sel);
1593
+ if (internalTarget.sel !== undefined && parsed.kind === "none") {
1594
+ throw new ToolError(`Invalid internal URL selector "${internalTarget.sel}".`);
1595
+ }
1590
1596
  return this.#handleInternalUrl(internalTarget.path, parsed, signal);
1591
1597
  }
1592
1598
 
@@ -15,10 +15,10 @@ import { pluralize } from "@gajae-code/utils";
15
15
  import { settings } from "../config/settings";
16
16
  import type { Theme } from "../modes/theme/theme";
17
17
  import { Hasher } from "../tui/utils";
18
- import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
19
18
 
20
19
  export { Ellipsis } from "@gajae-code/natives";
21
20
  export { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "@gajae-code/tui";
21
+ export { formatScreenshot } from "./browser/screenshot-format";
22
22
 
23
23
  // =============================================================================
24
24
  // Standardized Display Constants
@@ -591,32 +591,6 @@ export function formatToolWorkingDirectory(workdir: string | undefined, projectD
591
591
  return replaceTabs(displayWorkdir);
592
592
  }
593
593
 
594
- export function formatScreenshot(opts: {
595
- saveFullRes: boolean;
596
- savedMimeType: string;
597
- savedByteLength: number;
598
- dest: string;
599
- resized: ResizedImage;
600
- }): string[] {
601
- const lines = ["Screenshot captured"];
602
- if (opts.saveFullRes) {
603
- lines.push(
604
- `Saved: ${opts.savedMimeType} (${(opts.savedByteLength / 1024).toFixed(2)} KB) to ${shortenPath(opts.dest)}`,
605
- );
606
- lines.push(
607
- `Model: ${opts.resized.mimeType} (${(opts.resized.buffer.length / 1024).toFixed(2)} KB, ${opts.resized.width}x${opts.resized.height})`,
608
- );
609
- } else {
610
- lines.push(`Format: ${opts.resized.mimeType} (${(opts.resized.buffer.length / 1024).toFixed(2)} KB)`);
611
- lines.push(`Dimensions: ${opts.resized.width}x${opts.resized.height}`);
612
- }
613
- const dimensionNote = formatDimensionNote(opts.resized);
614
- if (dimensionNote) {
615
- lines.push(dimensionNote);
616
- }
617
- return lines;
618
- }
619
-
620
594
  export function wrapBrackets(text: string, theme: Theme): string {
621
595
  return `${theme.format.bracketLeft}${text}${theme.format.bracketRight}`;
622
596
  }