@gajae-code/coding-agent 0.18.1 → 0.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/dist/types/cli/stats-cli.d.ts +0 -5
  3. package/dist/types/cli-commands.d.ts +10 -0
  4. package/dist/types/cli-ordinary.d.ts +13 -0
  5. package/dist/types/cli.d.ts +12 -3
  6. package/dist/types/commands/contribution-prep.d.ts +0 -3
  7. package/dist/types/commands/sdk.d.ts +3 -0
  8. package/dist/types/commands/update.d.ts +1 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +29 -2
  10. package/dist/types/config/model-profile-activation.d.ts +19 -0
  11. package/dist/types/config/model-registry.d.ts +16 -1
  12. package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
  13. package/dist/types/edit/modes/replace.d.ts +3 -3
  14. package/dist/types/edit/streaming.d.ts +2 -2
  15. package/dist/types/exa/mcp-client.d.ts +1 -5
  16. package/dist/types/exec/bash-shell-guardian.d.ts +26 -0
  17. package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
  18. package/dist/types/hashline/parser.d.ts +28 -5
  19. package/dist/types/modes/acp/acp-agent.d.ts +6 -1
  20. package/dist/types/sdk/broker/discovery.d.ts +8 -0
  21. package/dist/types/sdk/broker/ensure.d.ts +1 -0
  22. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
  23. package/dist/types/sdk/broker/lifecycle.d.ts +6 -1
  24. package/dist/types/sdk/broker/recovery-backoff.d.ts +62 -0
  25. package/dist/types/sdk/broker/runtime.d.ts +21 -1
  26. package/dist/types/sdk/broker/startup-budget.d.ts +5 -1
  27. package/dist/types/sdk/bus/index.d.ts +1 -0
  28. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  29. package/dist/types/sdk/bus/telegram-daemon.d.ts +1 -0
  30. package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
  31. package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
  32. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  33. package/dist/types/sdk/host/session-runtime.d.ts +7 -0
  34. package/dist/types/sdk/index.d.ts +1 -0
  35. package/dist/types/sdk/prompt-watchdog.d.ts +1 -1
  36. package/dist/types/sdk/router/session-router.d.ts +2 -0
  37. package/dist/types/sdk/session.d.ts +13 -0
  38. package/dist/types/session/agent-session.d.ts +5 -1
  39. package/dist/types/tools/ask-contract.d.ts +75 -254
  40. package/dist/types/tools/bash.d.ts +1 -0
  41. package/package.json +7 -7
  42. package/scripts/build-sdk-package-smoke.ts +5 -0
  43. package/src/cli/public-command-entry.ts +5 -0
  44. package/src/cli/ssh-cli.ts +6 -3
  45. package/src/cli/stats-cli.ts +4 -36
  46. package/src/cli/web-search-cli.ts +9 -3
  47. package/src/cli-commands.ts +14 -0
  48. package/src/cli-main.ts +4 -8
  49. package/src/cli-ordinary.ts +197 -0
  50. package/src/cli.ts +25 -186
  51. package/src/commands/commit.ts +2 -2
  52. package/src/commands/contribution-prep.ts +1 -3
  53. package/src/commands/sdk.ts +60 -2
  54. package/src/commands/update.ts +15 -1
  55. package/src/config/autorouting-tier-map.ts +20 -2
  56. package/src/config/file-lock.ts +91 -2
  57. package/src/config/model-preset-registry.ts +17 -2
  58. package/src/config/model-profile-activation.ts +73 -5
  59. package/src/config/model-profile-contract.ts +4 -1
  60. package/src/config/model-profiles.ts +29 -29
  61. package/src/config/model-registry.ts +45 -17
  62. package/src/config/model-resolver.ts +9 -1
  63. package/src/coordinator-mcp/event-webhook.ts +19 -14
  64. package/src/coordinator-mcp/policy.ts +14 -4
  65. package/src/coordinator-mcp/server.ts +142 -7
  66. package/src/coordinator-mcp/session-reaper.ts +9 -1
  67. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  68. package/src/edit/modes/replace.ts +6 -6
  69. package/src/edit/streaming.ts +15 -15
  70. package/src/exa/mcp-client.ts +41 -8
  71. package/src/exec/bash-shell-guardian.ts +184 -34
  72. package/src/exec/bash-shell-supervisor.ts +60 -3
  73. package/src/hashline/execute.ts +51 -3
  74. package/src/hashline/parser.ts +63 -16
  75. package/src/internal-urls/docs-index.generated.ts +29 -28
  76. package/src/memories/index.ts +8 -2
  77. package/src/modes/acp/acp-agent.ts +688 -371
  78. package/src/modes/components/tool-execution.ts +1 -1
  79. package/src/modes/controllers/ssh-command-controller.ts +3 -1
  80. package/src/prompts/tools/ask.md +2 -0
  81. package/src/sdk/broker/broker.ts +46 -0
  82. package/src/sdk/broker/daemon-entry.ts +67 -6
  83. package/src/sdk/broker/discovery.ts +8 -0
  84. package/src/sdk/broker/ensure.ts +156 -20
  85. package/src/sdk/broker/lifecycle-ledger.ts +110 -12
  86. package/src/sdk/broker/lifecycle.ts +108 -34
  87. package/src/sdk/broker/recovery-backoff.ts +135 -0
  88. package/src/sdk/broker/runtime.ts +88 -1
  89. package/src/sdk/broker/startup-budget.ts +6 -1
  90. package/src/sdk/broker/transport.ts +5 -1
  91. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  92. package/src/sdk/bus/index.ts +305 -19
  93. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  94. package/src/sdk/bus/telegram-daemon-control.ts +15 -12
  95. package/src/sdk/bus/telegram-daemon.ts +34 -1
  96. package/src/sdk/cli/diagnostics-cli.ts +247 -0
  97. package/src/sdk/diagnostics/observe-broker.ts +654 -0
  98. package/src/sdk/host/control/dispatch.ts +52 -4
  99. package/src/sdk/host/control/operations.ts +1 -1
  100. package/src/sdk/host/session-runtime.test.ts +40 -2
  101. package/src/sdk/host/session-runtime.ts +112 -8
  102. package/src/sdk/index.ts +1 -0
  103. package/src/sdk/lifecycle-session.ts +1 -0
  104. package/src/sdk/prompt-watchdog.ts +17 -4
  105. package/src/sdk/protocol/operation-registry.ts +1 -1
  106. package/src/sdk/router/session-router.ts +7 -1
  107. package/src/sdk/session.ts +37 -8
  108. package/src/session/agent-session.ts +128 -41
  109. package/src/session/blob-store.ts +78 -33
  110. package/src/session/internal/managed-session-storage.ts +119 -3
  111. package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
  112. package/src/setup/hermes-setup.ts +4 -1
  113. package/src/slash-commands/builtin-registry.ts +8 -5
  114. package/src/tools/ask-contract.test.ts +76 -1
  115. package/src/tools/ask-contract.ts +57 -6
  116. package/src/tools/ask.ts +16 -7
  117. package/src/tools/bash.ts +77 -29
  118. package/src/tools/descriptors.test.ts +29 -0
  119. package/src/tools/tool-catalog.generated.ts +7 -4
  120. package/src/web/search/index.ts +2 -2
  121. package/dist/types/cli/grep-cli.d.ts +0 -17
  122. package/dist/types/cli/shell-cli.d.ts +0 -8
  123. package/dist/types/commands/grep.d.ts +0 -42
  124. package/dist/types/commands/shell.d.ts +0 -21
  125. package/src/cli/grep-cli.ts +0 -160
  126. package/src/cli/shell-cli.ts +0 -176
  127. package/src/commands/grep.ts +0 -48
  128. package/src/commands/shell.ts +0 -29
package/src/tools/bash.ts CHANGED
@@ -10,10 +10,28 @@ import { type BashArtifactSaveResult, type BashResult, executeBash } from "../ex
10
10
 
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import { buildGjcRuntimeSessionEnv } from "../gjc-runtime/goal-mode-request";
13
+ import {
14
+ MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
15
+ MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV,
16
+ MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
17
+ MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
18
+ MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
19
+ } from "../gjc-runtime/managed-owner-admission";
20
+ import {
21
+ MANAGED_OWNER_CHILD_TOKEN_ENV,
22
+ MANAGED_OWNER_COMMAND_ENV,
23
+ MANAGED_OWNER_GENERATION_ENV,
24
+ MANAGED_OWNER_INCARNATION_ENV,
25
+ MANAGED_OWNER_REDACT_COMMAND_ENV,
26
+ MANAGED_OWNER_RUN_ID_ENV,
27
+ MANAGED_OWNER_STATE_DIR_ENV,
28
+ } from "../gjc-runtime/managed-owner-supervisor";
13
29
  import {
14
30
  GJC_RALPLAN_ARTIFACT_ENV,
15
31
  GJC_RESTRICTED_ROLE_AGENT_BASH_ENV,
16
32
  } from "../gjc-runtime/restricted-role-agent-bash";
33
+ import { GJC_TMUX_OWNER_SERVER_KEY_ENV } from "../gjc-runtime/session-state-sidecar";
34
+ import { GJC_TMUX_LAUNCHED_ENV } from "../gjc-runtime/windows-powershell-command";
17
35
  import { InternalUrlRouter } from "../internal-urls";
18
36
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
19
37
  import { highlightCode, type Theme } from "../modes/theme/theme";
@@ -94,6 +112,29 @@ const ARTIFACT_SAVE_DIAGNOSTIC_MAX_BYTES = 256;
94
112
  const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
95
113
  const MASTER_CAPABILITY_ENV = "GJC_MASTER_CAPABILITY";
96
114
  const MASTER_OWNER_SESSION_ENV = "GJC_MASTER_OWNER_SESSION_ID";
115
+ // Managed-owner env vars that must be scrubbed from child processes.
116
+ // Exported for testing the env scrubbing behavior.
117
+ export const MANAGED_OWNER_BASH_ENV = [
118
+ MANAGED_OWNER_STATE_DIR_ENV,
119
+ MANAGED_OWNER_GENERATION_ENV,
120
+ MANAGED_OWNER_RUN_ID_ENV,
121
+ MANAGED_OWNER_INCARNATION_ENV,
122
+ MANAGED_OWNER_CHILD_TOKEN_ENV,
123
+ MANAGED_OWNER_COMMAND_ENV,
124
+ MANAGED_OWNER_REDACT_COMMAND_ENV,
125
+ MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
126
+ MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
127
+ MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
128
+ MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV,
129
+ MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
130
+ // The rest of the tmux-owner tuple (tmux-sessions.ts managedEnvironment): leaving
131
+ // either behind makes ownerTerminalContextFromEnvironment() report "invalid" in a
132
+ // nested gjc, because a server key or GJC_TMUX_LAUNCHED=1 without generation/state
133
+ // dir is an incomplete owner context.
134
+ GJC_TMUX_OWNER_SERVER_KEY_ENV,
135
+ GJC_TMUX_LAUNCHED_ENV,
136
+ ] as const;
137
+
97
138
  const COORDINATOR_ONLY_BASH_ENV = [
98
139
  "GJC_COORDINATOR_SESSION_STATE_FILE",
99
140
  "GJC_COORDINATOR_SESSION_ID",
@@ -102,6 +143,8 @@ const COORDINATOR_ONLY_BASH_ENV = [
102
143
  "GJC_COORDINATOR_SESSION_READINESS_FILE",
103
144
  "GJC_COORDINATOR_SIDECAR_SIGNATURE_REQUIRED",
104
145
  "GJC_COORDINATOR_SIDECAR_KEY_ID",
146
+ // Managed-owner env family from managed-owner-supervisor.ts and managed-owner-admission.ts
147
+ ...MANAGED_OWNER_BASH_ENV,
105
148
  ] as const;
106
149
  const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
107
150
  const ACP_RELEASE_TIMEOUT_MS = 1_000;
@@ -1239,7 +1282,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1239
1282
  */
1240
1283
  async #waitForManagedBashJob(
1241
1284
  job: ManagedBashJobHandle,
1242
- thresholdMs: number,
1285
+ thresholdMs: number | undefined,
1243
1286
  signal?: AbortSignal,
1244
1287
  backgroundRequest?: Promise<FoldReason>,
1245
1288
  foldAdapter?: FoldAdapter,
@@ -1249,31 +1292,36 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1249
1292
  }
1250
1293
 
1251
1294
  const startedAt = Date.now();
1252
- const threshold = Promise.withResolvers<{ kind: "running"; reason: FoldReason }>();
1253
- const thresholdTimer = setTimeout(
1254
- () => {
1255
- const requestFold = this.session.requestForegroundBashBackground;
1256
- if (!foldAdapter || !requestFold) {
1257
- threshold.resolve({ kind: "running", reason: "timer" });
1258
- return;
1259
- }
1260
- void requestFold("timer", foldAdapter)
1261
- .then(folded => {
1262
- if (!folded) threshold.resolve({ kind: "running", reason: "timer" });
1263
- })
1264
- .catch(error => {
1265
- logger.warn("Timer-triggered fold failed", {
1266
- jobId: foldAdapter.jobId,
1267
- error: error instanceof Error ? error.message : String(error),
1268
- });
1269
- threshold.resolve({ kind: "running", reason: "timer" });
1270
- });
1271
- },
1272
- Math.max(0, thresholdMs),
1273
- );
1295
+ const threshold =
1296
+ thresholdMs === undefined ? undefined : Promise.withResolvers<{ kind: "running"; reason: FoldReason }>();
1297
+ const thresholdTimer =
1298
+ threshold === undefined
1299
+ ? undefined
1300
+ : setTimeout(
1301
+ () => {
1302
+ const requestFold = this.session.requestForegroundBashBackground;
1303
+ if (!foldAdapter || !requestFold) {
1304
+ threshold.resolve({ kind: "running", reason: "timer" });
1305
+ return;
1306
+ }
1307
+ void requestFold("timer", foldAdapter)
1308
+ .then(folded => {
1309
+ if (!folded) threshold.resolve({ kind: "running", reason: "timer" });
1310
+ })
1311
+ .catch(error => {
1312
+ logger.warn("Timer-triggered fold failed", {
1313
+ jobId: foldAdapter.jobId,
1314
+ error: error instanceof Error ? error.message : String(error),
1315
+ });
1316
+ threshold.resolve({ kind: "running", reason: "timer" });
1317
+ });
1318
+ },
1319
+ Math.max(0, thresholdMs ?? 0),
1320
+ );
1274
1321
  const waiters: Array<
1275
1322
  Promise<ManagedBashJobCompletion | { kind: "running"; reason: FoldReason } | { kind: "aborted" }>
1276
- > = [job.completion, threshold.promise];
1323
+ > = [job.completion];
1324
+ if (threshold) waiters.push(threshold.promise);
1277
1325
  if (backgroundRequest) {
1278
1326
  waiters.push(backgroundRequest.then(reason => ({ kind: "running" as const, reason })));
1279
1327
  }
@@ -1294,7 +1342,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1294
1342
  return await Promise.race(waiters);
1295
1343
  } finally {
1296
1344
  stopSteerWatch();
1297
- clearTimeout(thresholdTimer);
1345
+ if (thresholdTimer !== undefined) clearTimeout(thresholdTimer);
1298
1346
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
1299
1347
  }
1300
1348
  }
@@ -1855,11 +1903,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1855
1903
  // longer has to be bypassed to make folding work. A capable ACP session keeps
1856
1904
  // its terminal contract and still folds.
1857
1905
  if (!pty && ownedManager && !clientTerminalActive) {
1858
- // With auto-background off, wait past the command's own timeout so the job only
1859
- // leaves the foreground on an explicit Ctrl+B fold, never on an auto-background timer.
1906
+ // With auto-background off, the foreground wait has no timer threshold, so the job
1907
+ // only leaves the foreground on an explicit Ctrl+B or steer fold.
1860
1908
  const autoBackgroundWaitMs = this.#autoBackgroundEnabled
1861
1909
  ? this.#resolveAutoBackgroundWaitMs(timeoutMs)
1862
- : timeoutMs + 1_000;
1910
+ : undefined;
1863
1911
  const startBackgrounded = autoBackgroundWaitMs === 0;
1864
1912
  let managedForegroundSettled = false;
1865
1913
  const job = this.#startManagedBashJob({
@@ -2561,7 +2609,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
2561
2609
  try {
2562
2610
  bridgeWait = await this.#waitForManagedBashJob(
2563
2611
  bridgeHandle,
2564
- this.#autoBackgroundEnabled ? this.#resolveAutoBackgroundWaitMs(timeoutMs) : timeoutMs + 1_000,
2612
+ this.#autoBackgroundEnabled ? this.#resolveAutoBackgroundWaitMs(timeoutMs) : undefined,
2565
2613
  signal,
2566
2614
  bridgeFoldRequest.promise,
2567
2615
  bridgeFoldAdapter,
@@ -736,6 +736,35 @@ describe("tool descriptor compatibility gate", () => {
736
736
  }),
737
737
  ).toThrow('Validation failed for tool "ask"');
738
738
  });
739
+ test("loaded and deferred asks reject missing interview metadata and empty siblings with actionable corrections", async () => {
740
+ for (const stage of ["topology", "post-topology"] as const) {
741
+ const session = makeSession({ "tools.discoveryMode": "all" });
742
+ session.hasUI = true;
743
+ session.getDeepInterviewAskStage = () => stage;
744
+ const eager = await BUILTIN_TOOL_DESCRIPTORS.ask.load(session);
745
+ const lazy = (await createTools(session)).find(tool => tool.name === "ask");
746
+ if (!eager || !lazy) throw new Error("expected eager and deferred asks");
747
+ const ordinary = {
748
+ id: "dot-gjc-topology",
749
+ question: "Round 0 | Topology confirmation\n\nDoes this scope match?",
750
+ options: [{ label: "Looks right" }],
751
+ workflowGate: { stage: "deep-interview", kind: "question" },
752
+ };
753
+ for (const [arguments_, correction] of [
754
+ [{ questions: [ordinary] }, "require structured deepInterview metadata"],
755
+ [{ questions: [{ ...ordinary, deepInterview: null }] }, "require structured deepInterview metadata"],
756
+ [{ questions: [ordinary, { id: "metadata", question: "", options: [] }] }, "remove empty extra questions"],
757
+ [{ questions: [ordinary, ordinary] }, "require exactly one question"],
758
+ ] as const) {
759
+ for (const tool of [eager, lazy]) {
760
+ expect(() =>
761
+ validateToolArguments(tool, { type: "toolCall", id: "incident", name: "ask", arguments: arguments_ }),
762
+ ).toThrow(correction);
763
+ }
764
+ }
765
+ }
766
+ });
767
+
739
768
  test("deferred ask validation matches eager AskTool on adversarial contracts", async () => {
740
769
  const session = makeSession({ "tools.discoveryMode": "all" });
741
770
  session.hasUI = true;
@@ -310,7 +310,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
310
310
  "ask": {
311
311
  "name": "ask",
312
312
  "label": "Ask",
313
- "description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include the automatic custom-input control or labels that imitate it** such as \"Other (type your own)\", \"Other (specify)\", \"type your own\", \"custom input\", or \"직접 입력\" — UI automatically adds \"Other (type your own)\" to every question. A plain domain value such as \"Other\" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.\n</critical>\n\n<examples>\n# Single question\nquestions: [{\"id\": \"auth_method\", \"question\": \"Which authentication method should this API use?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"OAuth2\"}, {\"label\": \"Session cookies\"}], \"recommended\": 0}]\n\n# Multiple questions\nquestions: [{\"id\": \"storage_type\", \"question\": \"Which storage backend?\", \"options\": [{\"label\": \"SQLite\"}, {\"label\": \"PostgreSQL\"}]}, {\"id\": \"auth_method\", \"question\": \"Which auth method?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"Session cookies\"}]}]\n</examples>",
313
+ "description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n- Every question body must contain meaningful, non-whitespace text. Never add empty `metadata`/`ignore` questions.\n- While deep-interview is in the `interviewing` phase, submit exactly one question with the required `deepInterview` metadata on that question. Round 0 needs `round: 0`, `component: \"review-topology\"`, `dimension: \"topology\"`, `ambiguity`, and `intent_contract` containing the displayed items and affirmative labels. Later questions need a positive round, component, dimension, and ambiguity. Question prose and `workflowGate` alone cannot record an interview answer. After final-spec persistence transitions to `handoff`, next-workflow/approval choices are ordinary asks without interview round metadata.\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include the automatic custom-input control or labels that imitate it** such as \"Other (type your own)\", \"Other (specify)\", \"type your own\", \"custom input\", or \"직접 입력\" — UI automatically adds \"Other (type your own)\" to every question. A plain domain value such as \"Other\" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.\n</critical>\n\n<examples>\n# Single question\nquestions: [{\"id\": \"auth_method\", \"question\": \"Which authentication method should this API use?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"OAuth2\"}, {\"label\": \"Session cookies\"}], \"recommended\": 0}]\n\n# Multiple questions\nquestions: [{\"id\": \"storage_type\", \"question\": \"Which storage backend?\", \"options\": [{\"label\": \"SQLite\"}, {\"label\": \"PostgreSQL\"}]}, {\"id\": \"auth_method\", \"question\": \"Which auth method?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"Session cookies\"}]}]\n</examples>",
314
314
  "parameters": {
315
315
  "type": "object",
316
316
  "properties": {
@@ -326,7 +326,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
326
326
  },
327
327
  "question": {
328
328
  "type": "string",
329
- "description": "question text"
329
+ "minLength": 1,
330
+ "description": "non-empty question text"
330
331
  },
331
332
  "options": {
332
333
  "type": "array",
@@ -2660,7 +2661,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2660
2661
  "description": "recency filter"
2661
2662
  },
2662
2663
  "limit": {
2663
- "type": "number",
2664
+ "type": "integer",
2665
+ "minimum": 1,
2664
2666
  "description": "max results"
2665
2667
  },
2666
2668
  "max_tokens": {
@@ -2672,7 +2674,8 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2672
2674
  "description": "sampling temperature"
2673
2675
  },
2674
2676
  "num_search_results": {
2675
- "type": "number",
2677
+ "type": "integer",
2678
+ "minimum": 1,
2676
2679
  "description": "number of search results"
2677
2680
  },
2678
2681
  "xai_search_mode": {
@@ -44,10 +44,10 @@ import {
44
44
  export const webSearchSchema = z.object({
45
45
  query: z.string().describe("search query"),
46
46
  recency: z.enum(["day", "week", "month", "year"]).describe("recency filter").optional(),
47
- limit: z.number().describe("max results").optional(),
47
+ limit: z.number().int().min(1).describe("max results").optional(),
48
48
  max_tokens: z.number().describe("max output tokens").optional(),
49
49
  temperature: z.number().describe("sampling temperature").optional(),
50
- num_search_results: z.number().describe("number of search results").optional(),
50
+ num_search_results: z.number().int().min(1).describe("number of search results").optional(),
51
51
  xai_search_mode: z
52
52
  .enum(["web", "x", "web_and_x"])
53
53
  .describe("xAI only: use web_search, x_search, or both")
@@ -1,17 +0,0 @@
1
- import { GrepOutputMode } from "@gajae-code/natives";
2
- export interface GrepCommandArgs {
3
- pattern: string;
4
- path: string;
5
- glob?: string;
6
- limit: number;
7
- context: number;
8
- mode: GrepOutputMode;
9
- gitignore: boolean;
10
- }
11
- /**
12
- * Parse grep subcommand arguments.
13
- * Returns undefined if not a grep command.
14
- */
15
- export declare function parseGrepArgs(args: string[]): GrepCommandArgs | undefined;
16
- export declare function runGrepCommand(cmd: GrepCommandArgs): Promise<void>;
17
- export declare function printGrepHelp(): void;
@@ -1,8 +0,0 @@
1
- export interface ShellCommandArgs {
2
- cwd?: string;
3
- timeoutMs?: number;
4
- noSnapshot?: boolean;
5
- }
6
- export declare function parseShellArgs(args: string[]): ShellCommandArgs | undefined;
7
- export declare function runShellCommand(cmd: ShellCommandArgs): Promise<void>;
8
- export declare function printShellHelp(): void;
@@ -1,42 +0,0 @@
1
- import { Command } from "@gajae-code/utils/cli";
2
- export default class Grep extends Command {
3
- static description: string;
4
- static args: {
5
- pattern: import("@gajae-code/utils/cli").ArgDescriptor & {
6
- description: string;
7
- required: false;
8
- };
9
- path: import("@gajae-code/utils/cli").ArgDescriptor & {
10
- description: string;
11
- required: false;
12
- };
13
- };
14
- static flags: {
15
- glob: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
16
- char: string;
17
- description: string;
18
- };
19
- limit: import("@gajae-code/utils/cli").FlagDescriptor<"integer"> & {
20
- char: string;
21
- description: string;
22
- default: number;
23
- };
24
- context: import("@gajae-code/utils/cli").FlagDescriptor<"integer"> & {
25
- char: string;
26
- description: string;
27
- default: number;
28
- };
29
- files: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
30
- char: string;
31
- description: string;
32
- };
33
- count: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
34
- char: string;
35
- description: string;
36
- };
37
- "no-gitignore": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
38
- description: string;
39
- };
40
- };
41
- run(): Promise<void>;
42
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Interactive shell console.
3
- */
4
- import { Command } from "@gajae-code/utils/cli";
5
- export default class Shell extends Command {
6
- static description: string;
7
- static flags: {
8
- cwd: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
9
- char: string;
10
- description: string;
11
- };
12
- timeout: import("@gajae-code/utils/cli").FlagDescriptor<"integer"> & {
13
- char: string;
14
- description: string;
15
- };
16
- "no-snapshot": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
17
- description: string;
18
- };
19
- };
20
- run(): Promise<void>;
21
- }
@@ -1,160 +0,0 @@
1
- /**
2
- * Grep CLI command handlers.
3
- *
4
- * Handles `gjc grep` subcommand for testing grep tool on Windows.
5
- */
6
- import * as path from "node:path";
7
- import { GrepOutputMode, grep } from "@gajae-code/natives";
8
- import { APP_NAME } from "@gajae-code/utils";
9
- import chalk from "chalk";
10
-
11
- export interface GrepCommandArgs {
12
- pattern: string;
13
- path: string;
14
- glob?: string;
15
- limit: number;
16
- context: number;
17
- mode: GrepOutputMode;
18
- gitignore: boolean;
19
- }
20
-
21
- /**
22
- * Parse grep subcommand arguments.
23
- * Returns undefined if not a grep command.
24
- */
25
- export function parseGrepArgs(args: string[]): GrepCommandArgs | undefined {
26
- if (args.length === 0 || args[0] !== "grep") {
27
- return undefined;
28
- }
29
-
30
- const result: GrepCommandArgs = {
31
- pattern: "",
32
- path: ".",
33
- limit: 20,
34
- context: 2,
35
- mode: GrepOutputMode.Content,
36
- gitignore: true,
37
- };
38
-
39
- const positional: string[] = [];
40
-
41
- for (let i = 1; i < args.length; i++) {
42
- const arg = args[i];
43
- if (arg === "--glob" || arg === "-g") {
44
- result.glob = args[++i];
45
- } else if (arg === "--limit" || arg === "-l") {
46
- result.limit = parseInt(args[++i], 10);
47
- } else if (arg === "--context" || arg === "-C") {
48
- result.context = parseInt(args[++i], 10);
49
- } else if (arg === "--files" || arg === "-f") {
50
- result.mode = GrepOutputMode.FilesWithMatches;
51
- } else if (arg === "--count" || arg === "-c") {
52
- result.mode = GrepOutputMode.Count;
53
- } else if (arg === "--no-gitignore") {
54
- result.gitignore = false;
55
- } else if (!arg.startsWith("-")) {
56
- positional.push(arg);
57
- }
58
- }
59
-
60
- if (positional.length >= 1) {
61
- result.pattern = positional[0];
62
- }
63
- if (positional.length >= 2) {
64
- result.path = positional[1];
65
- }
66
-
67
- return result;
68
- }
69
-
70
- export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
71
- if (!cmd.pattern) {
72
- console.error(chalk.red("Error: Pattern is required"));
73
- process.exit(1);
74
- }
75
-
76
- const searchPath = path.resolve(cmd.path);
77
- console.log(chalk.dim(`Searching in: ${searchPath}`));
78
- console.log(chalk.dim(`Pattern: ${cmd.pattern}`));
79
- console.log(
80
- chalk.dim(`Mode: ${cmd.mode}, Limit: ${cmd.limit}, Context: ${cmd.context}, Gitignore: ${cmd.gitignore}`),
81
- );
82
-
83
- console.log("");
84
-
85
- try {
86
- const result = await grep({
87
- pattern: cmd.pattern,
88
- path: searchPath,
89
- glob: cmd.glob,
90
- mode: cmd.mode,
91
- maxCount: cmd.limit,
92
- context: cmd.mode === GrepOutputMode.Content ? cmd.context : undefined,
93
- hidden: true,
94
- gitignore: cmd.gitignore,
95
- });
96
-
97
- console.log(chalk.green(`Total matches: ${result.totalMatches}`));
98
- console.log(chalk.green(`Files with matches: ${result.filesWithMatches}`));
99
- console.log(chalk.green(`Files searched: ${result.filesSearched}`));
100
- if (result.limitReached) {
101
- console.log(chalk.yellow(`Result limit reached (${cmd.limit} matches). Use --limit to show more.`));
102
- }
103
- console.log("");
104
-
105
- for (const match of result.matches) {
106
- const displayPath = match.path.replace(/\\/g, "/");
107
-
108
- if (cmd.mode === GrepOutputMode.Content) {
109
- if (match.contextBefore) {
110
- for (const ctx of match.contextBefore) {
111
- console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
112
- }
113
- }
114
- console.log(`${chalk.cyan(displayPath)}:${chalk.yellow(String(match.lineNumber))}: ${match.line}`);
115
- if (match.contextAfter) {
116
- for (const ctx of match.contextAfter) {
117
- console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
118
- }
119
- }
120
- console.log("");
121
- } else if (cmd.mode === GrepOutputMode.Count) {
122
- console.log(`${chalk.cyan(displayPath)}: ${match.matchCount ?? 0} matches`);
123
- } else {
124
- console.log(chalk.cyan(displayPath));
125
- }
126
- }
127
- } catch (err) {
128
- console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
129
- process.exit(1);
130
- }
131
- }
132
-
133
- export function printGrepHelp(): void {
134
- console.log(`${chalk.bold(`${APP_NAME} grep`)} - Test grep tool
135
-
136
- ${chalk.bold("Usage:")}
137
- ${APP_NAME} grep <pattern> [path] [options]
138
-
139
- ${chalk.bold("Arguments:")}
140
- pattern Regex pattern to search for
141
- path Directory or file to search (default: .)
142
-
143
- ${chalk.bold("Options:")}
144
- -g, --glob <pattern> Filter files by glob pattern
145
- -l, --limit <n> Max matches (default: 20)
146
- -C, --context <n> Context lines (default: 2)
147
- -f, --files Output file names only
148
- -c, --count Output match counts per file
149
- -h, --help Show this help
150
- --no-gitignore Include files excluded by .gitignore
151
-
152
- ${chalk.bold("Environment:")}
153
- PI_GREP_WORKERS=N Set filesystem walker workers (default 4, 0 = auto)
154
-
155
- ${chalk.bold("Examples:")}
156
- ${APP_NAME} grep "import" src/
157
- ${APP_NAME} grep "TODO" . --glob "*.ts"
158
- ${APP_NAME} grep "function" --files
159
- `);
160
- }