@gajae-code/coding-agent 0.18.0 → 0.18.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 (86) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/types/cli/stats-cli.d.ts +0 -5
  3. package/dist/types/config/autorouting-tier-map.d.ts +29 -2
  4. package/dist/types/config/model-registry.d.ts +16 -1
  5. package/dist/types/config/settings-schema.d.ts +2 -2
  6. package/dist/types/exa/mcp-client.d.ts +1 -5
  7. package/dist/types/exec/bash-shell-guardian.d.ts +26 -0
  8. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +1 -0
  9. package/dist/types/hashline/parser.d.ts +28 -5
  10. package/dist/types/modes/acp/acp-agent.d.ts +6 -1
  11. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  12. package/dist/types/sdk/broker/lifecycle.d.ts +6 -1
  13. package/dist/types/sdk/broker/recovery-backoff.d.ts +62 -0
  14. package/dist/types/sdk/broker/runtime.d.ts +20 -0
  15. package/dist/types/sdk/broker/startup-budget.d.ts +5 -1
  16. package/dist/types/sdk/bus/index.d.ts +1 -0
  17. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  18. package/dist/types/sdk/client/client.d.ts +5 -1
  19. package/dist/types/sdk/host/session-runtime.d.ts +7 -0
  20. package/dist/types/sdk/prompt-watchdog.d.ts +1 -1
  21. package/dist/types/session/agent-session.d.ts +2 -0
  22. package/dist/types/task/index.d.ts +0 -1
  23. package/dist/types/tools/bash.d.ts +1 -0
  24. package/dist/types/tools/descriptors.d.ts +5 -0
  25. package/dist/types/tools/eval-backends.d.ts +37 -0
  26. package/dist/types/tools/eval.d.ts +1 -6
  27. package/dist/types/tools/index.d.ts +1 -36
  28. package/dist/types/tools/session-descriptions.d.ts +22 -0
  29. package/package.json +7 -7
  30. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  31. package/src/cli/doctor/managed-link.ts +4 -4
  32. package/src/cli/doctor/repairs.ts +9 -1
  33. package/src/cli/stats-cli.ts +4 -36
  34. package/src/cli-main.ts +4 -8
  35. package/src/cli.ts +4 -8
  36. package/src/commands/commit.ts +2 -2
  37. package/src/commands/sdk.ts +1 -2
  38. package/src/config/autorouting-tier-map.ts +20 -2
  39. package/src/config/model-preset-registry.ts +17 -2
  40. package/src/config/model-profile-activation.ts +3 -1
  41. package/src/config/model-profiles.ts +11 -3
  42. package/src/config/model-registry.ts +45 -17
  43. package/src/config/settings-schema.ts +8 -2
  44. package/src/exa/mcp-client.ts +41 -8
  45. package/src/exec/bash-shell-guardian.ts +214 -48
  46. package/src/gjc-runtime/session-state-sidecar.ts +88 -58
  47. package/src/hashline/execute.ts +51 -3
  48. package/src/hashline/parser.ts +63 -16
  49. package/src/internal-urls/docs-index.generated.ts +25 -25
  50. package/src/modes/acp/acp-agent.ts +136 -20
  51. package/src/modes/controllers/ssh-command-controller.ts +3 -1
  52. package/src/modes/interactive-mode.ts +10 -0
  53. package/src/modes/utils/context-usage.ts +2 -6
  54. package/src/prompts/tools/task.md +8 -27
  55. package/src/sdk/acp/adapter.ts +38 -3
  56. package/src/sdk/broker/doctor-restart.ts +38 -3
  57. package/src/sdk/broker/lifecycle.ts +408 -218
  58. package/src/sdk/broker/recovery-backoff.ts +135 -0
  59. package/src/sdk/broker/runtime.ts +83 -0
  60. package/src/sdk/broker/startup-budget.ts +6 -1
  61. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  62. package/src/sdk/bus/index.ts +308 -22
  63. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  64. package/src/sdk/client/client.ts +53 -35
  65. package/src/sdk/host/session-runtime.ts +99 -3
  66. package/src/sdk/prompt-watchdog.ts +17 -4
  67. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  68. package/src/session/agent-session.ts +79 -17
  69. package/src/session/internal/managed-session-storage.ts +23 -0
  70. package/src/task/index.ts +21 -66
  71. package/src/tools/bash.ts +39 -0
  72. package/src/tools/descriptors.ts +27 -1
  73. package/src/tools/eval-backends.ts +95 -0
  74. package/src/tools/eval.ts +3 -15
  75. package/src/tools/index.ts +9 -88
  76. package/src/tools/search.ts +2 -7
  77. package/src/tools/session-descriptions.ts +135 -0
  78. package/src/tools/tool-catalog.generated.ts +1 -1
  79. package/dist/types/cli/grep-cli.d.ts +0 -17
  80. package/dist/types/cli/shell-cli.d.ts +0 -8
  81. package/dist/types/commands/grep.d.ts +0 -42
  82. package/dist/types/commands/shell.d.ts +0 -21
  83. package/src/cli/grep-cli.ts +0 -160
  84. package/src/cli/shell-cli.ts +0 -176
  85. package/src/commands/grep.ts +0 -48
  86. package/src/commands/shell.ts +0 -29
@@ -11,12 +11,12 @@ import { assertEditableFileContent } from "../tools/auto-generated-guard";
11
11
  import { invalidateFsScanAfterWrite } from "../tools/fs-cache-invalidation";
12
12
  import { outputMeta } from "../tools/output-meta";
13
13
  import { enforcePlanModeWrite, resolvePlanPath } from "../tools/plan-mode-guard";
14
- import { HashlineMismatchError } from "./anchors";
14
+ import { formatFullAnchorRequirement, HashlineMismatchError } from "./anchors";
15
15
  import { applyHashlineEdits, type HashlineApplyResult } from "./apply";
16
16
  import { buildCompactHashlineDiffPreview } from "./diff-preview";
17
17
  import { computeLineHash, HL_BODY_SEP } from "./hash";
18
18
  import { type HashlineInputSection, splitHashlineInputs } from "./input";
19
- import { HashlineMissingHashError, parseHashlineWithWarnings } from "./parser";
19
+ import { HashlineMissingHashError, HashlineMissingLineError, parseHashlineWithWarnings } from "./parser";
20
20
  import { tryRecoverHashlineWithCache } from "./recovery";
21
21
  import type {
22
22
  ExecuteHashlineSingleOptions,
@@ -88,6 +88,17 @@ function formatMissingHashAnchors(fileLines: string[], start: number, end: numbe
88
88
  return [...head, "...", format(end)];
89
89
  }
90
90
 
91
+ function formatMissingHashRetryHeader(
92
+ fileLines: string[],
93
+ start: number,
94
+ end: number,
95
+ opSigil: HashlineMissingHashError["opSigil"],
96
+ ): string {
97
+ const format = (line: number): string => `${line}${computeLineHash(line, fileLines[line - 1] ?? "")}`;
98
+ const anchorRange = start === end ? format(start) : `${format(start)}..${format(end)}`;
99
+ return `${opSigil}${anchorRange}`;
100
+ }
101
+
91
102
  /**
92
103
  * Parse an edit section. When an op names lines by number only, answer with
93
104
  * the current full anchors for those lines so the model can retry without
@@ -101,7 +112,43 @@ async function parseHashlineSection(
101
112
  try {
102
113
  return parseHashlineWithWarnings(diff);
103
114
  } catch (err) {
115
+ if (err instanceof HashlineMissingLineError) {
116
+ // When part of a range has a missing hash, suppress the "Did you mean" suggestion
117
+ // and require the full range to be re-supplied.
118
+ if (err.rangeRaw) {
119
+ throw new Error(
120
+ `Anchor "${err.hash}" lacks its line number. The range ${err.rangeRaw.start}..${err.rangeRaw.end} is incomplete. ` +
121
+ `Expected ${formatFullAnchorRequirement(err.hash)} Re-read the target to get the full range. ` +
122
+ `The edit was NOT applied.`,
123
+ );
124
+ }
125
+ const source = await readHashlineFile(absolutePath, pathText);
126
+ const fileLines = source.exists ? normalizeToLF(stripBom(source.rawContent).text).split("\n") : [];
127
+ let matchCount = 0;
128
+ let matchLine: number | undefined;
129
+ for (let index = 0; index < fileLines.length; index++) {
130
+ if (computeLineHash(index + 1, fileLines[index] ?? "") !== err.hash) continue;
131
+ matchCount++;
132
+ matchLine = index + 1;
133
+ }
134
+ if (matchCount === 1 && matchLine !== undefined) {
135
+ const suggestion = new HashlineMissingLineError(err.hash, matchLine, err.opSigil);
136
+ throw new Error(`${suggestion.message} The edit was NOT applied.`);
137
+ }
138
+ throw new Error(
139
+ `${err.message} Expected ${formatFullAnchorRequirement(err.hash)} ` +
140
+ `The hash matches ${matchCount} lines; re-read the target. The edit was NOT applied.`,
141
+ );
142
+ }
104
143
  if (!(err instanceof HashlineMissingHashError)) throw err;
144
+ // When part of a range has a missing hash, suppress the copy-ready suggestion
145
+ // and require re-reading to get the full range.
146
+ if (err.rangeRaw) {
147
+ throw new Error(
148
+ `${err.message} The anchors are missing from the range ${err.rangeRaw.start}..${err.rangeRaw.end}. ` +
149
+ `Re-read the target to get the full range with hashes. The edit was NOT applied.`,
150
+ );
151
+ }
105
152
  const source = await readHashlineFile(absolutePath, pathText);
106
153
  if (!source.exists) throw err;
107
154
  const fileLines = normalizeToLF(stripBom(source.rawContent).text).split("\n");
@@ -112,8 +159,9 @@ async function parseHashlineSection(
112
159
  );
113
160
  }
114
161
  const anchors = formatMissingHashAnchors(fileLines, start, end);
162
+ const retryHeader = formatMissingHashRetryHeader(fileLines, start, end, err.opSigil);
115
163
  throw new Error(
116
- `${err.message}\nThe edit was NOT applied. Current anchors for ${pathText}:\n${anchors.join("\n")}`,
164
+ `${err.message}\nUse ${retryHeader}\nThe edit was NOT applied. Current anchors for ${pathText}:\n${anchors.join("\n")}`,
117
165
  );
118
166
  }
119
167
  }
@@ -20,35 +20,59 @@ import type { Anchor, HashlineCursor, HashlineEdit } from "./types";
20
20
  const LID_CAPTURE_RE = new RegExp(`^\\s*[>+\\-*]*\\s*${HL_HASH_CAPTURE_RE_RAW}(?:\\|.*)?\\s*$`);
21
21
  const regexEscape = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22
22
 
23
- const BARE_LINE_REF_RE = /^\s*[>+\-*]*\s*([1-9]\d*)(?:\s*(?:-|\.\.)\s*([1-9]\d*))?\s*$/;
23
+ // Dots in copied text are literal unless followed by a possible range endpoint.
24
+ // Ambiguous copied ranges still require a re-read instead of a one-line retry.
25
+ const BARE_LINE_REF_RE =
26
+ /^\s*[>+\-*]*\s*([1-9]\d*)(?:\s*(?:-|\.\.)\s*([1-9]\d*))?(?:\|(?:(?!\.\.\s*[>+\-*]*\s*(?:[1-9]\d*|[a-z]{2}(?:\||\s*$))).)*)?\s*$/;
27
+ const HASH_ONLY_REF_RE = /^\s*[>+\-*]*\s*([a-z]{2})(?:\|.*)?\s*$/;
28
+ type HashlineOpSigil = typeof HL_OP_INSERT_BEFORE | typeof HL_OP_INSERT_AFTER | typeof HL_OP_REPLACE;
24
29
 
25
30
  /**
26
- * An op referenced lines by number alone, without the content hash. The edit
27
- * is never applied on a line number alone; the executor answers with the
28
- * current full anchors for {@link lines} so the model can retry without an
29
- * extra read.
31
+ * An op referenced lines by number alone. The edit is never applied on a line
32
+ * number alone; the executor answers with the current full anchors so the model
33
+ * can retry without another read.
30
34
  */
31
35
  export class HashlineMissingHashError extends Error {
32
36
  constructor(
33
37
  message: string,
34
38
  readonly lines: { start: number; end: number },
39
+ readonly opSigil: HashlineOpSigil,
40
+ readonly rangeRaw?: { start: string; end: string },
35
41
  ) {
36
42
  super(message);
37
43
  this.name = "HashlineMissingHashError";
38
44
  }
39
45
  }
40
46
 
41
- function parseLid(raw: string, lineNum: number): Anchor {
47
+ export class HashlineMissingLineError extends Error {
48
+ constructor(
49
+ readonly hash: string,
50
+ readonly lineNum: number | undefined,
51
+ readonly opSigil: HashlineOpSigil,
52
+ readonly rangeRaw?: { start: string; end: string },
53
+ ) {
54
+ super(
55
+ lineNum === undefined
56
+ ? `Anchor "${hash}" lacks its line number.`
57
+ : `Anchor "${hash}" lacks its line number. Did you mean ${opSigil}${lineNum}${hash}?`,
58
+ );
59
+ this.name = "HashlineMissingLineError";
60
+ }
61
+ }
62
+
63
+ function parseLid(raw: string, lineNum: number, opSigil: HashlineOpSigil): Anchor {
42
64
  const match = LID_CAPTURE_RE.exec(raw);
43
65
  if (!match) {
66
+ const hashOnly = HASH_ONLY_REF_RE.exec(raw);
67
+ if (hashOnly) throw new HashlineMissingLineError(hashOnly[1], undefined, opSigil);
44
68
  const bare = BARE_LINE_REF_RE.exec(raw);
45
69
  if (bare) {
46
70
  const start = Number.parseInt(bare[1], 10);
47
71
  const end = bare[2] === undefined ? start : Number.parseInt(bare[2], 10);
48
72
  throw new HashlineMissingHashError(
49
- `line ${lineNum}: anchor ${JSON.stringify(raw.trim())} is missing its hash; ` +
50
- `use the full anchor such as ${describeAnchorExamples(String(start))}.`,
73
+ `line ${lineNum}: anchor ${JSON.stringify(raw.trim())} is missing its hash.`,
51
74
  { start: Math.min(start, end), end: Math.max(start, end) },
75
+ opSigil,
52
76
  );
53
77
  }
54
78
  throw new Error(
@@ -64,12 +88,12 @@ interface ParsedRange {
64
88
  end: Anchor;
65
89
  }
66
90
 
67
- function parseRange(raw: string, lineNum: number): ParsedRange {
91
+ function parseRange(raw: string, lineNum: number, opSigil: HashlineOpSigil): ParsedRange {
68
92
  // Reject an all-numeric range (`16..18`, `16-18`) as a whole so the error
69
93
  // carries the full span rather than just its first endpoint.
70
- if (BARE_LINE_REF_RE.test(raw)) parseLid(raw, lineNum);
94
+ if (BARE_LINE_REF_RE.test(raw)) parseLid(raw, lineNum, opSigil);
71
95
  if (!raw.includes("..")) {
72
- const start = parseLid(raw, lineNum);
96
+ const start = parseLid(raw, lineNum, opSigil);
73
97
  return { start, end: { ...start } };
74
98
  }
75
99
  const [startRaw, endRaw, extra] = raw.split("..");
@@ -79,8 +103,30 @@ function parseRange(raw: string, lineNum: number): ParsedRange {
79
103
  `For a one-line edit, repeat the same anchor on both sides.`,
80
104
  );
81
105
  }
82
- const start = parseLid(startRaw, lineNum);
83
- const end = parseLid(endRaw, lineNum);
106
+ let start: Anchor;
107
+ try {
108
+ start = parseLid(startRaw, lineNum, opSigil);
109
+ } catch (err) {
110
+ if (err instanceof HashlineMissingHashError) {
111
+ throw new HashlineMissingHashError(err.message, err.lines, err.opSigil, { start: startRaw, end: endRaw });
112
+ }
113
+ if (err instanceof HashlineMissingLineError) {
114
+ throw new HashlineMissingLineError(err.hash, err.lineNum, err.opSigil, { start: startRaw, end: endRaw });
115
+ }
116
+ throw err;
117
+ }
118
+ let end: Anchor;
119
+ try {
120
+ end = parseLid(endRaw, lineNum, opSigil);
121
+ } catch (err) {
122
+ if (err instanceof HashlineMissingHashError) {
123
+ throw new HashlineMissingHashError(err.message, err.lines, err.opSigil, { start: startRaw, end: endRaw });
124
+ }
125
+ if (err instanceof HashlineMissingLineError) {
126
+ throw new HashlineMissingLineError(err.hash, err.lineNum, err.opSigil, { start: startRaw, end: endRaw });
127
+ }
128
+ throw err;
129
+ }
84
130
  if (end.line < start.line) {
85
131
  throw new Error(`line ${lineNum}: range ${startRaw}..${endRaw} ends before it starts.`);
86
132
  }
@@ -104,7 +150,8 @@ function parseInsertTarget(raw: string, lineNum: number, kind: "before" | "after
104
150
  if (raw === "BOF") return { kind: "bof" };
105
151
  if (raw === "EOF") return { kind: "eof" };
106
152
  const cursorKind = kind === "before" ? "before_anchor" : "after_anchor";
107
- return { kind: cursorKind, anchor: parseLid(raw, lineNum) };
153
+ const opSigil = kind === "before" ? HL_OP_INSERT_BEFORE : HL_OP_INSERT_AFTER;
154
+ return { kind: cursorKind, anchor: parseLid(raw, lineNum, opSigil) };
108
155
  }
109
156
 
110
157
  /**
@@ -137,7 +184,7 @@ const INSERT_BEFORE_OP_RE = new RegExp(
137
184
  const INSERT_AFTER_OP_RE = new RegExp(
138
185
  `^${regexEscape(HL_OP_INSERT_AFTER)}\\s*([^|\\s]+)(?:${HL_BODY_SEP_RE_RAW}(.*))?\\s*$`,
139
186
  );
140
- const REPLACE_OP_RE = new RegExp(`^${regexEscape(HL_OP_REPLACE)}\\s*([^\\s+<\\-=]\\S*)\\s*$`);
187
+ const REPLACE_OP_RE = new RegExp(`^${regexEscape(HL_OP_REPLACE)}\\s*([^\\s+<\\-=]\\S*(?:\\|.*)?)\\s*$`);
141
188
 
142
189
  function isEnvelopeOrAbortMarkerLine(line: string): boolean {
143
190
  const trimmed = line.trimEnd();
@@ -240,7 +287,7 @@ export function parseHashlineWithWarnings(diff: string): { edits: HashlineEdit[]
240
287
 
241
288
  const replaceMatch = REPLACE_OP_RE.exec(line);
242
289
  if (replaceMatch) {
243
- const range = parseRange(replaceMatch[1], lineNum);
290
+ const range = parseRange(replaceMatch[1], lineNum, HL_OP_REPLACE);
244
291
  const { payload, nextIndex } = collectPayload(lines, i + 1, lineNum, false);
245
292
  if (payload.length > 0) {
246
293
  for (const text of payload) {