@kylecheng3146/agent-ops 0.1.17 → 0.1.19

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.
@@ -2,6 +2,13 @@ import { AgentOpsError } from "./paths.js";
2
2
  function escapeRegExp(value) {
3
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
4
  }
5
+ export function normalizeToLF(source) {
6
+ return source.replace(/\r\n/g, "\n");
7
+ }
8
+ /** Newline sequence to use for newly rendered content, inferred from the file. */
9
+ function detectNewline(source) {
10
+ return source.includes("\r\n") ? "\r\n" : "\n";
11
+ }
5
12
  function assertBlockId(id) {
6
13
  if (!/^[a-z][a-z0-9-]{0,127}$/.test(id)) {
7
14
  throw new AgentOpsError("INVALID_BLOCK_ID", `Invalid block ID: ${id}`);
@@ -70,25 +77,30 @@ function locateMarkers(source, id, version, markerStyle = "html") {
70
77
  }
71
78
  return { start, end, startIndex, endIndex };
72
79
  }
73
- function renderBlock(options) {
80
+ function renderBlock(options, nl) {
74
81
  if (/(?:<!--|#)\s*agent-ops:/.test(options.content)) {
75
82
  throw new AgentOpsError("AMBIGUOUS_MANAGED_CONTENT", "Managed content must not contain agent-ops marker boundaries.");
76
83
  }
77
84
  const { start, end } = managedBlockMarkers(options.id, options.version, options.markerStyle);
78
- const content = options.content.replace(/\r\n/g, "\n").replace(/\n+$/g, "");
79
- return `${start}\n${content}\n${end}`;
85
+ const content = normalizeToLF(options.content)
86
+ .replace(/\n+$/g, "")
87
+ .split("\n")
88
+ .join(nl);
89
+ return `${start}${nl}${content}${nl}${end}`;
80
90
  }
81
91
  export function applyManagedBlock(source, options) {
82
- const block = renderBlock(options);
92
+ const nl = detectNewline(source);
93
+ const block = renderBlock(options, nl);
83
94
  const located = locateMarkers(source, options.id, options.version, options.markerStyle);
84
95
  if (located === null) {
85
96
  if (source.length === 0) {
86
- return `${block}\n`;
97
+ return `${block}${nl}`;
87
98
  }
88
- return `${source.replace(/\n*$/, "\n\n")}${block}\n`;
99
+ return `${source.replace(/(?:\r\n|\n)*$/, `${nl}${nl}`)}${block}${nl}`;
89
100
  }
90
101
  return `${source.slice(0, located.startIndex)}${block}${source.slice(located.endIndex + located.end.length)}`;
91
102
  }
103
+ const NEWLINE_STYLES = ["\r\n", "\n"];
92
104
  export function removeManagedBlock(source, id, markerStyle = "html") {
93
105
  const located = locateMarkers(source, id, undefined, markerStyle);
94
106
  if (located === null) {
@@ -96,12 +108,13 @@ export function removeManagedBlock(source, id, markerStyle = "html") {
96
108
  }
97
109
  let before = source.slice(0, located.startIndex);
98
110
  let after = source.slice(located.endIndex + located.end.length);
99
- if (before.length === 0 && after === "\n") {
111
+ if (before.length === 0 && NEWLINE_STYLES.some((nl) => after === nl)) {
100
112
  return "";
101
113
  }
102
- if (before.endsWith("\n\n") && after.startsWith("\n")) {
103
- before = before.slice(0, -1);
104
- after = after.slice(1);
114
+ const collapseNl = NEWLINE_STYLES.find((nl) => before.endsWith(`${nl}${nl}`) && after.startsWith(nl));
115
+ if (collapseNl !== undefined) {
116
+ before = before.slice(0, -collapseNl.length);
117
+ after = after.slice(collapseNl.length);
105
118
  }
106
119
  return `${before}${after}`;
107
120
  }
@@ -1,4 +1,4 @@
1
- import { applyManagedBlock, managedBlockMarkers } from "../fs/managed-block.js";
1
+ import { applyManagedBlock, managedBlockMarkers, normalizeToLF } from "../fs/managed-block.js";
2
2
  import { AgentOpsError } from "../fs/paths.js";
3
3
  import { harnessDescriptor, harnessHookPath, routingBlockId, selectHarnessHookSurface, rulesArtifactId } from "./harness.js";
4
4
  import { isOpencodePluginPath } from "../adapters/opencode/config.js";
@@ -211,18 +211,18 @@ export function assertExpectedManagedBlock(source, marker, expected) {
211
211
  startIndex >= endIndex) {
212
212
  throw new AgentOpsError("MANAGED_BLOCK_CHANGED", `Managed block boundaries changed after installation: ${marker.path}`);
213
213
  }
214
- const currentBlock = source.slice(startIndex, endIndex + marker.endMarker.length);
214
+ const currentBlock = normalizeToLF(source.slice(startIndex, endIndex + marker.endMarker.length));
215
215
  const candidates = [
216
216
  ["desired", expected.content],
217
217
  ...expected.legacyContent.map((content) => ["legacy", content])
218
218
  ];
219
219
  for (const [kind, content] of candidates) {
220
- const expectedBlock = applyManagedBlock("", {
220
+ const expectedBlock = normalizeToLF(applyManagedBlock("", {
221
221
  id: expected.id,
222
222
  version: 1,
223
223
  content,
224
224
  markerStyle: expected.markerStyle
225
- }).replace(/\n$/u, "");
225
+ }).replace(/\n$/u, ""));
226
226
  if (currentBlock === expectedBlock) {
227
227
  return kind;
228
228
  }
@@ -103,6 +103,17 @@ function firstComplaint(...streams) {
103
103
  }
104
104
  return undefined;
105
105
  }
106
+ function rejectedCallReason(output) {
107
+ if (/\b(?:quota|rate limit|usage limit|too many requests)\b/iu.test(output)) {
108
+ return "quota-exhausted";
109
+ }
110
+ if (/\b(?:not logged in|login required|log in to|authentication required|unauthenticated|unauthorized)\b/iu.test(output) ||
111
+ /\b(?:invalid|expired)\s+(?:api key|token|credential)/iu.test(output) ||
112
+ /\b401\b/u.test(output)) {
113
+ return "login-required";
114
+ }
115
+ return "capability-unavailable";
116
+ }
106
117
  async function snapshotRepository(request, destination, options) {
107
118
  const cloned = await runVerificationCommand({
108
119
  id: `review-snapshot-${request.label}`,
@@ -256,11 +267,8 @@ async function attemptTarget(request, options) {
256
267
  return skip("output-too-large", `${spawned.stdoutTruncated ? "stdout" : "stderr"} exceeded the capture limit`);
257
268
  }
258
269
  if (spawned.failureClass === "nonzero-exit") {
259
- // A rejected call is usually missing authentication, but a stale flag
260
- // shape exits non-zero too, and reporting only "login-required" sends
261
- // the reader to `doctor --check-auth` for a problem it cannot see. The
262
- // target's own first line of complaint distinguishes the two.
263
- return skip("login-required", firstComplaint(spawned.stderr, spawned.stdout) ??
270
+ const output = `${spawned.stderr}\n${spawned.stdout}`;
271
+ return skip(rejectedCallReason(output), firstComplaint(spawned.stderr, spawned.stdout) ??
264
272
  `the call was rejected with exit ${spawned.exitCode ?? "unknown"} and no output`);
265
273
  }
266
274
  const payload = extractReviewObject(target, spawned.stdout);
@@ -40,7 +40,10 @@ export function renderReviewResult(result) {
40
40
  for (const error of result.validationErrors ?? []) {
41
41
  lines.push(`- ${safe(error.path)}: ${safe(error.code)} — ${safe(error.message)}`);
42
42
  }
43
- lines.push("Run: agent-ops doctor --check-auth to verify target authentication.");
43
+ if (result.reason === "login-required" ||
44
+ result.attempts?.some((attempt) => attempt.reason === "login-required")) {
45
+ lines.push("Run: agent-ops doctor --check-auth to verify target authentication.");
46
+ }
44
47
  return `${lines.join("\n")}\n`;
45
48
  }
46
49
  const report = result.report;
@@ -94,8 +94,11 @@ human-readable report is transient. Completed tasks are never rewritten.
94
94
  `--yes` is still required for every review run: init selection decides which
95
95
  targets are permitted, `--yes` decides whether to spend money now.
96
96
 
97
- Because target authentication is not sniffed from stderr, an unauthenticated
98
- CLI surfaces as one review failure. Diagnose it with:
97
+ Reviewer failures are classified conservatively from their output: recognizable
98
+ authentication messages become `login-required`, quota messages become
99
+ `quota-exhausted`, and other non-zero exits become `capability-unavailable`.
100
+ The review suggests `doctor --check-auth` only when an authentication failure
101
+ was detected. Confirm target authentication with:
99
102
 
100
103
  ```bash
101
104
  agent-ops doctor # presence only: no tokens, no network
@@ -82,8 +82,10 @@ policy 設定仍相同的 task;`--criterion` 用來篩選 id。請先執行
82
82
  每次執行 review 仍需 `--yes`:init 的勾選決定「允許哪些目標」,
83
83
  `--yes` 決定「現在是否要花錢」。
84
84
 
85
- 因為不從 stderr 嗅探認證狀態,未登入的 CLI 會表現為一次 review 失敗。
86
- 用以下指令診斷:
85
+ Reviewer 失敗會依輸出採保守分類:可辨識的認證訊息為 `login-required`,
86
+ quota 訊息為 `quota-exhausted`,其他非零退出則為 `capability-unavailable`。
87
+ 只有偵測到認證失敗時,review 才會建議執行 `doctor --check-auth`。
88
+ 可用以下指令確認目標的認證狀態:
87
89
 
88
90
  ```bash
89
91
  agent-ops doctor # 只驗執行檔存在:零 token、零網路
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylecheng3146/agent-ops",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Evidence-driven development loops for Codex, Claude Code, and opencode",
5
5
  "type": "module",
6
6
  "license": "MIT",