@gajae-code/coding-agent 0.11.2 → 0.11.4

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 (101) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/types/cli/launch-disposition.d.ts +23 -0
  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 +85 -10
  7. package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
  8. package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
  9. package/dist/types/main.d.ts +1 -0
  10. package/dist/types/modes/components/custom-editor.d.ts +8 -3
  11. package/dist/types/modes/components/tool-transcript-format.d.ts +19 -0
  12. package/dist/types/modes/controllers/input-controller.d.ts +8 -2
  13. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  14. package/dist/types/sdk/broker/broker.d.ts +1 -0
  15. package/dist/types/sdk/broker/discovery.d.ts +10 -0
  16. package/dist/types/sdk/broker/ensure.d.ts +24 -1
  17. package/dist/types/sdk/broker/internal.d.ts +3 -0
  18. package/dist/types/sdk/broker/session-index.d.ts +12 -0
  19. package/dist/types/sdk/broker/state-version.d.ts +2 -1
  20. package/dist/types/sdk/bus/index.d.ts +2 -0
  21. package/dist/types/sdk/session.d.ts +2 -0
  22. package/dist/types/tools/ask.d.ts +39 -12
  23. package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
  24. package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
  25. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
  26. package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
  27. package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
  28. package/dist/types/tools/path-utils.d.ts +9 -14
  29. package/dist/types/tools/read.d.ts +2 -0
  30. package/dist/types/tools/render-utils.d.ts +1 -8
  31. package/dist/types/tools/resource-gc.d.ts +14 -0
  32. package/dist/types/tools/subagent.d.ts +1 -0
  33. package/dist/types/utils/file-mentions.d.ts +3 -3
  34. package/dist/types/utils/image-loading.d.ts +23 -0
  35. package/dist/types/utils/image-resize.d.ts +2 -0
  36. package/dist/types/utils/pasted-image-loading.d.ts +39 -0
  37. package/dist/types/utils/pasted-image-path.d.ts +21 -19
  38. package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
  39. package/dist/types/web/scrapers/types.d.ts +1 -5
  40. package/package.json +24 -8
  41. package/scripts/run-threat-model-check.ts +60 -0
  42. package/src/cli/launch-disposition.ts +42 -0
  43. package/src/cli/worktree-cli.ts +9 -287
  44. package/src/cli.ts +10 -9
  45. package/src/commands/gc.ts +11 -1
  46. package/src/commands/sdk.ts +3 -2
  47. package/src/commands/worktree.ts +10 -53
  48. package/src/config/model-profiles.ts +15 -15
  49. package/src/config/settings-schema.ts +55 -10
  50. package/src/eval/js/shared/runtime.ts +1 -1
  51. package/src/gjc-runtime/gc-render.ts +19 -1
  52. package/src/gjc-runtime/gc-runtime.ts +68 -3
  53. package/src/gjc-runtime/state-argv.ts +292 -0
  54. package/src/gjc-runtime/state-runtime.ts +51 -212
  55. package/src/main.ts +99 -22
  56. package/src/modes/components/custom-editor.ts +103 -32
  57. package/src/modes/components/custom-provider-wizard.ts +26 -1
  58. package/src/modes/components/session-observer-overlay.ts +10 -23
  59. package/src/modes/components/tool-transcript-format.ts +72 -0
  60. package/src/modes/components/transcript-viewer-overlay.ts +42 -2
  61. package/src/modes/controllers/input-controller.ts +98 -37
  62. package/src/modes/controllers/selector-controller.ts +1 -1
  63. package/src/modes/interactive-mode.ts +13 -2
  64. package/src/modes/prompt-action-autocomplete.ts +7 -0
  65. package/src/sdk/broker/broker.ts +139 -32
  66. package/src/sdk/broker/discovery.ts +125 -0
  67. package/src/sdk/broker/ensure.ts +103 -19
  68. package/src/sdk/broker/internal.ts +10 -0
  69. package/src/sdk/broker/session-index.ts +228 -15
  70. package/src/sdk/broker/state-version.ts +5 -2
  71. package/src/sdk/bus/index.ts +4 -1
  72. package/src/sdk/bus/telegram-daemon.ts +50 -10
  73. package/src/sdk/host/host.ts +13 -1
  74. package/src/sdk/session.ts +4 -1
  75. package/src/session/internal/managed-session-scope.ts +15 -1
  76. package/src/tools/ask.ts +216 -41
  77. package/src/tools/bash-allowed-prefixes.ts +79 -29
  78. package/src/tools/browser/launch.ts +3 -1
  79. package/src/tools/browser/readable.ts +1 -1
  80. package/src/tools/browser/screenshot-format.ts +34 -0
  81. package/src/tools/browser/tab-protocol.ts +3 -0
  82. package/src/tools/browser/tab-supervisor.ts +125 -122
  83. package/src/tools/browser/tab-worker-entry.ts +33 -14
  84. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  85. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  86. package/src/tools/browser/tab-worker.ts +7 -5
  87. package/src/tools/output-meta.ts +6 -2
  88. package/src/tools/path-utils.ts +42 -35
  89. package/src/tools/read.ts +189 -64
  90. package/src/tools/render-utils.ts +1 -27
  91. package/src/tools/resource-gc.ts +123 -34
  92. package/src/tools/subagent.ts +43 -2
  93. package/src/utils/file-mentions.ts +4 -4
  94. package/src/utils/image-loading.ts +85 -34
  95. package/src/utils/image-resize.ts +16 -9
  96. package/src/utils/pasted-image-loading.ts +514 -0
  97. package/src/utils/pasted-image-path.ts +223 -98
  98. package/src/web/scrapers/html-to-markdown.ts +68 -0
  99. package/src/web/scrapers/readthedocs.ts +1 -1
  100. package/src/web/scrapers/types.ts +1 -68
  101. package/src/web/search/providers/openai-compatible.ts +76 -23
@@ -15,6 +15,7 @@
15
15
  * - Questions may time out and auto-select the recommended option (configurable, disabled in plan mode)
16
16
  */
17
17
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
18
+ import type { RawArgumentValidationResult } from "@gajae-code/ai/types";
18
19
  import { type Component } from "@gajae-code/tui";
19
20
  import * as z from "zod/v4";
20
21
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
@@ -29,13 +30,19 @@ export declare const askSchema: z.ZodObject<{
29
30
  }, z.core.$strip>>;
30
31
  multi: z.ZodOptional<z.ZodBoolean>;
31
32
  recommended: z.ZodOptional<z.ZodNumber>;
32
- deepInterview: z.ZodOptional<z.ZodObject<{
33
+ deepInterview: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
33
34
  round_id: z.ZodOptional<z.ZodString>;
34
35
  round: z.ZodNumber;
35
36
  component: z.ZodString;
36
37
  dimension: z.ZodString;
37
38
  ambiguity: z.ZodNumber;
38
- intent_contract: z.ZodOptional<z.ZodObject<{
39
+ }, z.core.$strict>, z.ZodObject<{
40
+ round_id: z.ZodOptional<z.ZodString>;
41
+ ambiguity: z.ZodNumber;
42
+ round: z.ZodLiteral<0>;
43
+ component: z.ZodLiteral<"review-topology">;
44
+ dimension: z.ZodLiteral<"topology">;
45
+ intent_contract: z.ZodObject<{
39
46
  items: z.ZodArray<z.ZodObject<{
40
47
  id: z.ZodString;
41
48
  category: z.ZodEnum<{
@@ -47,8 +54,14 @@ export declare const askSchema: z.ZodObject<{
47
54
  statement: z.ZodString;
48
55
  }, z.core.$strict>>;
49
56
  confirmation_options: z.ZodArray<z.ZodString>;
50
- }, z.core.$strict>>;
51
- intent_review: z.ZodOptional<z.ZodObject<{
57
+ }, z.core.$strict>;
58
+ }, z.core.$strict>, z.ZodObject<{
59
+ round_id: z.ZodOptional<z.ZodString>;
60
+ component: z.ZodString;
61
+ dimension: z.ZodString;
62
+ ambiguity: z.ZodNumber;
63
+ round: z.ZodNumber;
64
+ intent_review: z.ZodObject<{
52
65
  observed_items: z.ZodArray<z.ZodObject<{
53
66
  id: z.ZodString;
54
67
  category: z.ZodEnum<{
@@ -65,8 +78,8 @@ export declare const askSchema: z.ZodObject<{
65
78
  rationale: z.ZodString;
66
79
  }, z.core.$strict>>;
67
80
  approval_options: z.ZodArray<z.ZodString>;
68
- }, z.core.$strict>>;
69
- }, z.core.$strict>>;
81
+ }, z.core.$strict>;
82
+ }, z.core.$strict>]>>;
70
83
  workflowGate: z.ZodOptional<z.ZodObject<{
71
84
  stage: z.ZodEnum<{
72
85
  "deep-interview": "deep-interview";
@@ -82,6 +95,7 @@ export declare const askSchema: z.ZodObject<{
82
95
  }, z.core.$strip>>;
83
96
  }, z.core.$strip>;
84
97
  export type AskToolInput = z.infer<typeof askSchema>;
98
+ declare function recoverRoundZeroIntentContract(arguments_: Record<string, unknown>): RawArgumentValidationResult;
85
99
  /** Result for a single question */
86
100
  export interface QuestionResult {
87
101
  id: string;
@@ -149,13 +163,19 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
149
163
  }, z.core.$strip>>;
150
164
  multi: z.ZodOptional<z.ZodBoolean>;
151
165
  recommended: z.ZodOptional<z.ZodNumber>;
152
- deepInterview: z.ZodOptional<z.ZodObject<{
166
+ deepInterview: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
153
167
  round_id: z.ZodOptional<z.ZodString>;
154
168
  round: z.ZodNumber;
155
169
  component: z.ZodString;
156
170
  dimension: z.ZodString;
157
171
  ambiguity: z.ZodNumber;
158
- intent_contract: z.ZodOptional<z.ZodObject<{
172
+ }, z.core.$strict>, z.ZodObject<{
173
+ round_id: z.ZodOptional<z.ZodString>;
174
+ ambiguity: z.ZodNumber;
175
+ round: z.ZodLiteral<0>;
176
+ component: z.ZodLiteral<"review-topology">;
177
+ dimension: z.ZodLiteral<"topology">;
178
+ intent_contract: z.ZodObject<{
159
179
  items: z.ZodArray<z.ZodObject<{
160
180
  id: z.ZodString;
161
181
  category: z.ZodEnum<{
@@ -167,8 +187,14 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
167
187
  statement: z.ZodString;
168
188
  }, z.core.$strict>>;
169
189
  confirmation_options: z.ZodArray<z.ZodString>;
170
- }, z.core.$strict>>;
171
- intent_review: z.ZodOptional<z.ZodObject<{
190
+ }, z.core.$strict>;
191
+ }, z.core.$strict>, z.ZodObject<{
192
+ round_id: z.ZodOptional<z.ZodString>;
193
+ component: z.ZodString;
194
+ dimension: z.ZodString;
195
+ ambiguity: z.ZodNumber;
196
+ round: z.ZodNumber;
197
+ intent_review: z.ZodObject<{
172
198
  observed_items: z.ZodArray<z.ZodObject<{
173
199
  id: z.ZodString;
174
200
  category: z.ZodEnum<{
@@ -185,8 +211,8 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
185
211
  rationale: z.ZodString;
186
212
  }, z.core.$strict>>;
187
213
  approval_options: z.ZodArray<z.ZodString>;
188
- }, z.core.$strict>>;
189
- }, z.core.$strict>>;
214
+ }, z.core.$strict>;
215
+ }, z.core.$strict>]>>;
190
216
  workflowGate: z.ZodOptional<z.ZodObject<{
191
217
  stage: z.ZodEnum<{
192
218
  "deep-interview": "deep-interview";
@@ -201,6 +227,7 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
201
227
  }, z.core.$strip>>;
202
228
  }, z.core.$strip>>;
203
229
  }, z.core.$strip>;
230
+ readonly rawArgumentValidation: typeof recoverRoundZeroIntentContract;
204
231
  readonly strict = true;
205
232
  readonly loadMode = "discoverable";
206
233
  constructor(session: ToolSession);
@@ -0,0 +1,8 @@
1
+ import { type ResizedImage } from "../../utils/image-resize";
2
+ export declare function formatScreenshot(opts: {
3
+ saveFullRes: boolean;
4
+ savedMimeType: string;
5
+ savedByteLength: number;
6
+ dest: string;
7
+ resized: ResizedImage;
8
+ }): string[];
@@ -70,6 +70,10 @@ export type ToolReply = {
70
70
  error: RunErrorPayload;
71
71
  };
72
72
  export type WorkerInbound = {
73
+ type: "bootstrap";
74
+ version: 1;
75
+ mode: "native-free";
76
+ } | {
73
77
  type: "init";
74
78
  payload: WorkerInitPayload;
75
79
  } | {
@@ -112,6 +116,13 @@ export interface RunErrorPayload {
112
116
  isAbort: boolean;
113
117
  }
114
118
  export type WorkerOutbound = {
119
+ type: "bootstrap-ready";
120
+ version: 1;
121
+ mode: "native-free";
122
+ } | {
123
+ type: "bootstrap-failed";
124
+ error: string;
125
+ } | {
115
126
  type: "ready";
116
127
  info: ReadyInfo;
117
128
  } | {
@@ -5,8 +5,8 @@ interface WorkerHandle {
5
5
  send(msg: WorkerInbound, transferList?: Transferable[]): void;
6
6
  onMessage(handler: (msg: WorkerOutbound) => void): () => void;
7
7
  onError(handler: (error: Error) => void): () => void;
8
+ onClose(handler: () => void): () => void;
8
9
  terminate(): Promise<void>;
9
- readonly mode: "worker" | "inline";
10
10
  }
11
11
  export type DialogPolicy = "accept" | "dismiss";
12
12
  export interface PendingRun {
@@ -0,0 +1,2 @@
1
+ /** Resolve filesystem-only worker inputs without evaluating the broad path utility module. */
2
+ export declare function resolveTabWorkerPath(filePath: string, cwd: string): string;
@@ -0,0 +1,16 @@
1
+ import type { WorkerInbound } from "./tab-protocol";
2
+ interface SmokeWorkerHandle {
3
+ postMessage(message: WorkerInbound): void;
4
+ addEventListener(type: string, listener: EventListener): void;
5
+ removeEventListener(type: string, listener: EventListener): void;
6
+ terminate(): void;
7
+ }
8
+ /**
9
+ * Starts the real tab worker entry without connecting to a browser, then proves
10
+ * its bootstrap and shutdown protocol. The compiled branch deliberately keeps
11
+ * a static literal for Bun's --compile worker discovery.
12
+ */
13
+ export declare function smokeTestTabWorker(timeoutMs?: number): Promise<void>;
14
+ /** Test-only: verify the smoke handshake against a controlled worker transport. */
15
+ export declare function smokeTestTabWorkerWithWorkerForTest(worker: SmokeWorkerHandle, timeoutMs: number): Promise<void>;
16
+ export {};
@@ -7,21 +7,16 @@ export declare function splitPathAndSel(rawPath: string): {
7
7
  /**
8
8
  * Variant of {@link splitPathAndSel} for internal URLs (`scheme://...`).
9
9
  *
10
- * The filesystem-path splitter is intentionally conservative: it refuses to
11
- * peel a trailing `:<chunk>` unless that chunk matches the strict selector
12
- * grammar. That rule is right for filesystem paths (a file named `a:1-50` is
13
- * legal) but wrong for internal URLs, where any trailing `:<chunk>` after the
14
- * scheme is unambiguously a read-tool selector — even if malformed (e.g.
15
- * `artifact://3:raw:-100`).
16
- *
17
- * This function iteratively peels selector-shaped chunks (well-formed plus
18
- * common malformed shapes like `:-N`) so the rest of the read tool can pass a
19
- * clean URL to the protocol handler and surface selector errors via parseSel
20
- * instead of as misleading "host invalid" errors from the handler.
21
- *
22
- * Falls back to the input unchanged when nothing matches.
10
+ * Artifact authorities are generated identifiers, so their complete tail is
11
+ * unambiguously a selector and malformed selectors can be rejected before
12
+ * resolving the artifact. Other resources may legitimately use colons in
13
+ * their identities, so they only lose strict, recognized selector suffixes.
14
+ * Skill authorities additionally use the active registry to distinguish a
15
+ * skill name from its selector without making path-utils depend on skills.
23
16
  */
24
- export declare function splitInternalUrlSel(rawPath: string): {
17
+ export declare function splitInternalUrlSel(rawPath: string, options?: {
18
+ activeSkillNames?: readonly string[];
19
+ }): {
25
20
  path: string;
26
21
  sel?: string;
27
22
  };
@@ -27,6 +27,8 @@ export interface ReadToolDetails {
27
27
  method?: string;
28
28
  notes?: string[];
29
29
  meta?: OutputMeta;
30
+ /** Whether this explicit content read may be spilled by output metadata. */
31
+ spillEligible?: boolean;
30
32
  /** Raw text + start line for user-visible TUI rendering, set when content is text-like.
31
33
  * Mirrors the same lines the model receives but without hashline/line-number prefixes,
32
34
  * so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
@@ -8,9 +8,9 @@ import type { ToolCallContext } from "@gajae-code/agent-core";
8
8
  import type { Ellipsis } from "@gajae-code/natives";
9
9
  import type { Component } from "@gajae-code/tui";
10
10
  import type { Theme } from "../modes/theme/theme";
11
- import { type ResizedImage } from "../utils/image-resize";
12
11
  export { Ellipsis } from "@gajae-code/natives";
13
12
  export { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "@gajae-code/tui";
13
+ export { formatScreenshot } from "./browser/screenshot-format";
14
14
  /** Resolve inline image dimension caps from settings and viewport. */
15
15
  export declare function resolveImageOptions(): {
16
16
  maxWidthCells: number;
@@ -107,13 +107,6 @@ export declare function truncateDiffByHunk(diffText: string, maxHunks: number, m
107
107
  };
108
108
  export declare function shortenPath(filePath: string, homeDir?: string): string;
109
109
  export declare function formatToolWorkingDirectory(workdir: string | undefined, projectDir: string): string | undefined;
110
- export declare function formatScreenshot(opts: {
111
- saveFullRes: boolean;
112
- savedMimeType: string;
113
- savedByteLength: number;
114
- dest: string;
115
- resized: ResizedImage;
116
- }): string[];
117
110
  export declare function wrapBrackets(text: string, theme: Theme): string;
118
111
  export declare const PARSE_ERRORS_LIMIT = 20;
119
112
  export declare function dedupeParseErrors(errors: string[] | undefined): string[];
@@ -44,11 +44,25 @@ export interface ResourceGcRegistration {
44
44
  export declare function registerResourceGcSession(reg: ResourceGcRegistration): () => void;
45
45
  export declare function sweepOnce(d?: ResourceGcDeps): Promise<void>;
46
46
  export declare function __setResourceGcDepsForTest(overrides: Partial<ResourceGcDeps>): void;
47
+ export declare function __setResourceGcSchedulerNowForTest(now: () => number): void;
47
48
  export declare function __runResourceGcTickForTest(): Promise<void>;
49
+ export declare function __runResourceGcTimerCallbackForTest(owner: {
50
+ generation: number;
51
+ token: number;
52
+ }, deadline: number): Promise<void>;
48
53
  export declare function __getResourceGcStateForTest(): {
49
54
  timerActive: boolean;
50
55
  sessionCount: number;
51
56
  rssWarningActive: boolean;
52
57
  inProgress: boolean;
58
+ generation: number;
59
+ pendingDeadline: number | null;
60
+ pendingOwner: {
61
+ generation: number;
62
+ token: number;
63
+ } | null;
64
+ deferredDeadline: number | null;
65
+ deferredGeneration: number | null;
66
+ activeGeneration: number | null;
53
67
  };
54
68
  export declare function __resetResourceGcForTest(): void;
@@ -98,6 +98,7 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
98
98
  constructor(session: ToolSession);
99
99
  execute(_toolCallId: string, params: SubagentParams, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<SubagentToolDetails>, _context?: AgentToolContext): Promise<AgentToolResult<SubagentToolDetails>>;
100
100
  }
101
+ export declare function capCodePointsAndBytes(text: string, maxCodePoints: number, maxBytes: number): string;
101
102
  /**
102
103
  * Canonical, value-based rendered-state signature for the `subagent` await panel.
103
104
  *
@@ -1,8 +1,8 @@
1
1
  import type { AgentMessage } from "@gajae-code/agent-core";
2
2
  /**
3
- * Default inline cap for auto-read file mentions, deliberately below the
4
- * read-tool cap (`DEFAULT_MAX_BYTES`, 50KB) so an incidental `@path` mention
5
- * injects a smaller snippet than an explicit `read`. Overridable per call.
3
+ * Default inline cap for auto-read file mentions, aligned with the read-tool's
4
+ * 10 KiB bare-read receipt so an incidental `@path` mention injects a bounded
5
+ * snippet. Overridable per call.
6
6
  */
7
7
  export declare const DEFAULT_FILE_MENTION_INLINE_BYTES: number;
8
8
  /** Extract all @filepath mentions from text */
@@ -8,6 +8,24 @@ export interface LoadImageInputOptions {
8
8
  maxBytes?: number;
9
9
  resolvedPath?: string;
10
10
  detectedMimeType?: string;
11
+ signal?: AbortSignal;
12
+ }
13
+ export interface LoadImageInputBytesOptions {
14
+ inputBuffer: Buffer;
15
+ resolvedPath: string;
16
+ mimeType: string;
17
+ autoResize: boolean;
18
+ maxBytes?: number;
19
+ maxOutputBytes?: number;
20
+ signal?: AbortSignal;
21
+ }
22
+ export interface TransformedImageInput {
23
+ resolvedPath: string;
24
+ mimeType: string;
25
+ buffer: Uint8Array;
26
+ textNote: string;
27
+ dimensionNote?: string;
28
+ bytes: number;
11
29
  }
12
30
  export interface LoadedImageInput {
13
31
  resolvedPath: string;
@@ -23,4 +41,9 @@ export declare class ImageInputTooLargeError extends Error {
23
41
  constructor(bytes: number, maxBytes: number);
24
42
  }
25
43
  export declare function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null>;
44
+ /** Transform trusted source bytes without materializing a base64 payload. */
45
+ export declare function transformImageInputBytes(options: LoadImageInputBytesOptions): Promise<TransformedImageInput>;
46
+ export declare function materializeImageInput(transformed: TransformedImageInput): LoadedImageInput;
47
+ /** Build a model image payload from bytes already read through a trusted descriptor. */
48
+ export declare function loadImageInputBytes(options: LoadImageInputBytesOptions): Promise<LoadedImageInput>;
26
49
  export declare function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null>;
@@ -16,6 +16,7 @@ export interface ResizedImage {
16
16
  wasResized: boolean;
17
17
  get data(): string;
18
18
  }
19
+ export declare const DEFAULT_IMAGE_RESIZE_MAX_BYTES: number;
19
20
  /**
20
21
  * Resize and recompress an image to fit within the specified max dimensions and file size.
21
22
  *
@@ -31,6 +32,7 @@ export interface ResizedImage {
31
32
  * Backed by `Bun.Image`: a chainable native pipeline that runs decode/transform/encode
32
33
  * off the JS thread when the terminal (`.bytes()`) is awaited.
33
34
  */
35
+ export declare function resizeImageBuffer(inputBuffer: Buffer, inputMimeType: string, options?: ImageResizeOptions): Promise<ResizedImage>;
34
36
  export declare function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage>;
35
37
  /**
36
38
  * Format a dimension note for resized images.
@@ -0,0 +1,39 @@
1
+ import type { ImageContent } from "@gajae-code/ai";
2
+ import { type LoadedImageInput, transformImageInputBytes } from "./image-loading";
3
+ export declare const MAX_PASTED_IMAGE_SOURCE_BYTES: number;
4
+ export declare const MAX_PASTED_IMAGE_OUTPUT_BYTES: number;
5
+ export declare const MAX_PASTED_IMAGE_DIMENSION = 20000;
6
+ export declare const MAX_PASTED_IMAGE_PIXELS = 40000000;
7
+ export declare const MAX_PASTED_IMAGE_DECODED_BYTES: number;
8
+ export type PastedImageBatchErrorCode = "too-many" | "unsafe-path" | "unsupported-image" | "source-aggregate-too-large" | "output-aggregate-too-large" | "dimensions-too-large";
9
+ export declare class PastedImageBatchError extends Error {
10
+ readonly code: PastedImageBatchErrorCode;
11
+ readonly imagePath?: string | undefined;
12
+ constructor(code: PastedImageBatchErrorCode, message: string, imagePath?: string | undefined);
13
+ }
14
+ export interface LoadedPastedImageBatch {
15
+ images: ImageContent[];
16
+ loadedInputs: LoadedImageInput[];
17
+ sourcePaths: string[];
18
+ }
19
+ export interface PastedImageLoadingDependencies {
20
+ /** Synchronization hook used to exercise pathname swaps after descriptor open. */
21
+ afterDescriptorOpen?: (imagePath: string, index: number) => Promise<void> | void;
22
+ beforeDescriptorRead?: (imagePath: string, index: number) => Promise<void> | void;
23
+ transformImageBytes?: typeof transformImageInputBytes;
24
+ }
25
+ export type PastedImageSourcePolicy = "confirmed" | "automatic-temp";
26
+ export interface LoadPastedImageBatchOptions {
27
+ paths: readonly string[];
28
+ autoResize: boolean;
29
+ signal: AbortSignal;
30
+ sourcePolicy?: PastedImageSourcePolicy;
31
+ maxImageBytes?: number;
32
+ maxSourceBytes?: number;
33
+ maxOutputBytes?: number;
34
+ maxDimension?: number;
35
+ maxPixels?: number;
36
+ maxDecodedBytes?: number;
37
+ dependencies?: PastedImageLoadingDependencies;
38
+ }
39
+ export declare function loadPastedImageBatch(options: LoadPastedImageBatchOptions): Promise<LoadedPastedImageBatch>;
@@ -1,9 +1,8 @@
1
+ export declare const MAX_PASTED_IMAGE_COUNT = 16;
2
+ export declare const MAX_PASTED_IMAGE_PASTE_CHARACTERS: number;
3
+ export declare const MAX_PASTED_IMAGE_PATH_CHARACTERS: number;
1
4
  export interface DecodePastedPathOptions {
2
- /**
3
- * Platform whose path semantics apply when decoding (shell unescaping,
4
- * `file://` drive letters / UNC hosts). Defaults to `process.platform`;
5
- * injectable so tests can pin the win32 contract from any host.
6
- */
5
+ /** Platform whose path and shell semantics apply. Defaults to `process.platform`. */
7
6
  platform?: NodeJS.Platform;
8
7
  /** Home directory for `~/` expansion. Defaults to `os.homedir()`. */
9
8
  homedir?: string;
@@ -12,21 +11,24 @@ export interface ResolvePastedImagePathOptions extends DecodePastedPathOptions {
12
11
  /** Base directory for relative paths. Defaults to `process.cwd()`. */
13
12
  cwd?: string;
14
13
  }
15
- /**
16
- * Decode pasted text into a filesystem path candidate. No filesystem access.
17
- *
18
- * Handles terminal drag-drop shell escaping (`\ `, `\(`, ...; skipped on
19
- * win32 where `\` is the path separator), quoted paths, `file://` URIs
20
- * (drive-letter, `file://localhost`, and UNC forms on win32), and `~/`
21
- * expansion. Returns `undefined` when the text is empty, spans multiple
22
- * lines, or is an invalid `file://` URI.
23
- */
14
+ export type PastedImagePathParseResult = {
15
+ kind: "paths";
16
+ paths: string[];
17
+ requiresConfirmation: boolean;
18
+ } | {
19
+ kind: "too-many";
20
+ maxCandidates: number;
21
+ };
22
+ /** Decode one pasted path candidate without filesystem access. */
24
23
  export declare function decodePastedPathCandidate(text: string, options?: DecodePastedPathOptions): string | undefined;
24
+ /** Tokenize a bounded complete path list without filesystem access. */
25
+ export declare function decodePastedPathCandidates(text: string, options?: DecodePastedPathOptions, maxCandidates?: number): string[] | undefined;
26
+ /** Resolve one recognized clipboard-temp image path lexically, before any filesystem access. */
27
+ export declare function resolvePastedImagePath(text: string, options?: ResolvePastedImagePathOptions): string | undefined;
25
28
  /**
26
- * Returns the resolved path when the whole pasted text is a recognized clipboard
27
- * temp path to an existing image file, otherwise `undefined` (the paste is
28
- * inserted as text). Arbitrary saved image paths are not auto-attached here;
29
- * users attach them explicitly with `@path/to/image.png`.
29
+ * Parse a complete image path paste without filesystem access. A single path
30
+ * keeps the clipboard-temp-only policy; multi-path saved-image pastes require
31
+ * explicit confirmation before any path is opened.
30
32
  */
31
- export declare function resolvePastedImagePath(text: string, options?: ResolvePastedImagePathOptions): string | undefined;
33
+ export declare function parsePastedImagePaths(text: string, options?: ResolvePastedImagePathOptions): PastedImagePathParseResult | undefined;
32
34
  export declare function formatPastedImageReference(placeholder: string, imagePath: string): string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Convert HTML to markdown using Turndown with GFM support.
3
+ * Strips script/style tags before conversion.
4
+ */
5
+ export declare function htmlToBasicMarkdown(html: string): Promise<string>;
@@ -44,11 +44,7 @@ export interface LoadPageResult {
44
44
  * Fetch a page with timeout and size limit
45
45
  */
46
46
  export declare function loadPage(url: string, options?: LoadPageOptions): Promise<LoadPageResult>;
47
- /**
48
- * Convert HTML to markdown using Turndown with GFM support.
49
- * Strips script/style tags before conversion.
50
- */
51
- export declare function htmlToBasicMarkdown(html: string): Promise<string>;
47
+ export { htmlToBasicMarkdown } from "./html-to-markdown";
52
48
  /**
53
49
  * Build a RenderResult from markdown content. Calls finalizeOutput internally.
54
50
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.11.2",
4
+ "version": "0.11.4",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -53,13 +53,13 @@
53
53
  "@agentclientprotocol/sdk": "1.2.1",
54
54
  "@babel/parser": "^7.29.3",
55
55
  "@mozilla/readability": "^0.6.0",
56
- "@gajae-code/stats": "0.11.2",
57
- "@gajae-code/agent-core": "0.11.2",
58
- "@gajae-code/ai": "0.11.2",
59
- "@gajae-code/bridge-client": "0.11.2",
60
- "@gajae-code/natives": "0.11.2",
61
- "@gajae-code/tui": "0.11.2",
62
- "@gajae-code/utils": "0.11.2",
56
+ "@gajae-code/stats": "0.11.4",
57
+ "@gajae-code/agent-core": "0.11.4",
58
+ "@gajae-code/ai": "0.11.4",
59
+ "@gajae-code/bridge-client": "0.11.4",
60
+ "@gajae-code/natives": "0.11.4",
61
+ "@gajae-code/tui": "0.11.4",
62
+ "@gajae-code/utils": "0.11.4",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -171,6 +171,14 @@
171
171
  "types": "./dist/types/capability/*.d.ts",
172
172
  "import": "./src/capability/*.ts"
173
173
  },
174
+ "./cli/worktree-cli": {
175
+ "types": "./dist/types/cli/worktree-cli.d.ts",
176
+ "import": "./src/cli/worktree-cli.ts"
177
+ },
178
+ "./cli/worktree-cli.js": {
179
+ "types": "./dist/types/cli/worktree-cli.d.ts",
180
+ "import": "./src/cli/worktree-cli.ts"
181
+ },
174
182
  "./cli/*": {
175
183
  "types": "./dist/types/cli/*.d.ts",
176
184
  "import": "./src/cli/*.ts"
@@ -187,6 +195,14 @@
187
195
  "./commands/ultraqa": null,
188
196
  "./commands/ultrawork": null,
189
197
  "./commands/performance-goal": null,
198
+ "./commands/worktree": {
199
+ "types": "./dist/types/commands/worktree.d.ts",
200
+ "import": "./src/commands/worktree.ts"
201
+ },
202
+ "./commands/worktree.js": {
203
+ "types": "./dist/types/commands/worktree.d.ts",
204
+ "import": "./src/commands/worktree.ts"
205
+ },
190
206
  "./commands/*": {
191
207
  "types": "./dist/types/commands/*.d.ts",
192
208
  "import": "./src/commands/*.ts"
@@ -14,6 +14,61 @@ function assert(condition: unknown, message: string): asserts condition {
14
14
  if (!condition) throw new Error(message);
15
15
  }
16
16
 
17
+ async function sourceFiles(root: string): Promise<string[]> {
18
+ const files: string[] = [];
19
+ for (const entry of await fs.readdir(root, { withFileTypes: true })) {
20
+ const file = path.join(root, entry.name);
21
+ if (entry.isDirectory()) files.push(...(await sourceFiles(file)));
22
+ else if (/\.(?:ts|tsx|js|mjs|cjs)$/.test(entry.name)) files.push(file);
23
+ }
24
+ return files;
25
+ }
26
+
27
+ function hasDiscoverySignalAuthority(source: string): boolean {
28
+ return /(?:process\.)?kill\s*\(\s*(?:discovery|brokerDiscovery|record)\.pid\s*,\s*["']SIG(?:TERM|KILL)["']/m.test(
29
+ source,
30
+ );
31
+ }
32
+
33
+ async function assertNoDiscoverySignalAuthority(): Promise<void> {
34
+ const roots = [path.resolve(import.meta.dir, "../src/sdk"), path.resolve(import.meta.dir, "../test")];
35
+ const offenders: string[] = [];
36
+ for (const root of roots) {
37
+ for (const file of await sourceFiles(root)) {
38
+ if (hasDiscoverySignalAuthority(await fs.readFile(file, "utf8")))
39
+ offenders.push(path.relative(process.cwd(), file));
40
+ }
41
+ }
42
+ assert(offenders.length === 0, `Discovery-derived TERM/KILL authority remains in: ${offenders.join(", ")}`);
43
+ assert(
44
+ hasDiscoverySignalAuthority('process.kill(discovery.pid, "SIGTERM")'),
45
+ "Synthetic forbidden discovery signal was not detected",
46
+ );
47
+ assert(
48
+ !hasDiscoverySignalAuthority("process.kill(discovery.pid, 0)"),
49
+ "PID-zero observation was escalated to signal authority",
50
+ );
51
+ }
52
+
53
+ async function assertFixtureControlsStayTestOnly(): Promise<void> {
54
+ const productionRoot = path.resolve(import.meta.dir, "../src");
55
+ const offenders: string[] = [];
56
+ for (const file of await sourceFiles(productionRoot)) {
57
+ const source = await fs.readFile(file, "utf8");
58
+ if (/\b(?:GSF1|SSH1)\b/.test(source)) offenders.push(path.relative(process.cwd(), file));
59
+ }
60
+ assert(
61
+ offenders.length === 0,
62
+ `Fixture control frames are reachable from production source: ${offenders.join(", ")}`,
63
+ );
64
+ const ensureSource = await fs.readFile(path.resolve(import.meta.dir, "../src/sdk/broker/ensure.ts"), "utf8");
65
+ assert(
66
+ ensureSource.includes("startFixtureBrokerCommandWithLeaseForTest"),
67
+ "Dedicated fixture launch boundary is missing its explicit ForTest label",
68
+ );
69
+ assert(!/GJC_.*(?:GSF1|SSH1|SELF_REAP)/.test(ensureSource), "Production environment can select fixture controls");
70
+ }
71
+
17
72
  async function rejectedWebSocket(url: string): Promise<void> {
18
73
  const ws = new WebSocket(url);
19
74
  await new Promise<void>((resolve, reject) => {
@@ -132,6 +187,11 @@ async function main(): Promise<void> {
132
187
  assert(!discovery.url.includes("0.0.0.0"), "non-loopback broker URL exists");
133
188
  await rejectedWebSocket(`${discovery.url}/?token=wrong-token`);
134
189
  },
190
+ "broker-discovery-derived-signal-authority-forbidden": assertNoDiscoverySignalAuthority,
191
+ "broker-pid-zero-observation-allowed": async () => {
192
+ assert(!hasDiscoverySignalAuthority("process.kill(discovery.pid, 0)"), "PID-zero observation was rejected");
193
+ },
194
+ "broker-fixture-control-nonproduction-reachability": assertFixtureControlsStayTestOnly,
135
195
  };
136
196
  for (const check of manifest.checks) {
137
197
  try {
@@ -0,0 +1,42 @@
1
+ export type LaunchMode = "text" | "json" | "acp";
2
+
3
+ export interface LaunchDisposition {
4
+ autoPrint: boolean;
5
+ isInteractive: boolean;
6
+ /** Set when an interactive launch is impossible (non-TTY stdin, nothing to run). */
7
+ nonInteractiveError?: string;
8
+ }
9
+
10
+ /**
11
+ * Decides between interactive, auto-print, and fail-fast launches.
12
+ *
13
+ * A non-TTY stdin means the TUI can never receive input, so an interactive
14
+ * launch would execute the initial prompt and then block in the event loop
15
+ * forever. Prepared command-line input degrades to print mode; without any
16
+ * input to run we fail fast instead of hanging. Explicit `--print` and
17
+ * `--mode` launches are left untouched.
18
+ */
19
+ export function resolveLaunchDisposition(args: {
20
+ stdinIsTTY: boolean | undefined;
21
+ pipedInput: string | undefined;
22
+ hasPreparedInput: boolean;
23
+ print: boolean;
24
+ mode: LaunchMode | undefined;
25
+ }): LaunchDisposition {
26
+ const explicitNonInteractive = args.print || args.mode !== undefined;
27
+ const hasPreparedInput = args.pipedInput !== undefined || args.hasPreparedInput;
28
+ const hasNonTtyStdin = args.stdinIsTTY !== true;
29
+ const autoPrint = !explicitNonInteractive && hasNonTtyStdin && hasPreparedInput;
30
+ const isInteractive = !explicitNonInteractive && !autoPrint;
31
+
32
+ if (isInteractive && args.stdinIsTTY !== true) {
33
+ return {
34
+ autoPrint: false,
35
+ isInteractive: false,
36
+ nonInteractiveError:
37
+ "stdin is not a TTY and no prompt or prepared input was provided; use -p/--print (or pass a prompt or @file) for non-interactive runs.",
38
+ };
39
+ }
40
+
41
+ return { autoPrint, isInteractive };
42
+ }