@kylecheng3146/agent-ops 0.3.6 → 0.3.7
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.
- package/dist/runtime/src/install/doctor.js +4 -0
- package/dist/runtime/src/review/execute.js +6 -1
- package/dist/runtime/src/review/host-sandbox.js +36 -1
- package/dist/runtime/src/review/probe.js +13 -4
- package/dist/runtime/src/review/render.js +5 -0
- package/docs/en/guides/configuration.md +11 -4
- package/docs/zh-TW/guides/configuration.md +8 -1
- package/package.json +1 -1
|
@@ -475,6 +475,10 @@ async function checkReviewTargets(config, probe, checkAuth, selectedTargets = []
|
|
|
475
475
|
failures.push(check("review-targets", "FAIL", `${target} did not answer in time.`, undefined, "Re-run: agent-ops doctor --check-auth"));
|
|
476
476
|
continue;
|
|
477
477
|
}
|
|
478
|
+
if (result === "network-unreachable") {
|
|
479
|
+
failures.push(check("review-targets", "FAIL", `${target} could not reach the network, so its login state is unknown.`, undefined, "Run agent-ops outside the sandbox, or grant it escalated execution, then re-run: agent-ops doctor --check-auth"));
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
478
482
|
if (result === "capability-unavailable") {
|
|
479
483
|
failures.push(check("review-targets", "FAIL", `${target} could not complete its read-only capability probe.`, undefined, "Run agent-ops review on a host with network and loopback permission, then retry."));
|
|
480
484
|
continue;
|
|
@@ -8,7 +8,7 @@ import { answeredNothing, conversationIdOf, deniedToolsOf, extractFinalMessage,
|
|
|
8
8
|
import { buildTargetInvocation } from "./invocation.js";
|
|
9
9
|
import { reviewReportResults, reviewReportStatus, validateReviewReport } from "./report.js";
|
|
10
10
|
import { detectHostTarget, planReviewTargets } from "./roles.js";
|
|
11
|
-
import { detectHostRestriction } from "./host-sandbox.js";
|
|
11
|
+
import { detectHostRestriction, isNetworkFailure } from "./host-sandbox.js";
|
|
12
12
|
import { buildAdversarialPrompt, buildReviewPrompt } from "./runner.js";
|
|
13
13
|
import { MAX_ADVERSARIAL_PROMPT_BYTES } from "./runner.js";
|
|
14
14
|
import { extractUsage, sumUsage } from "./usage.js";
|
|
@@ -143,6 +143,8 @@ function preflightUnavailableReason(result) {
|
|
|
143
143
|
return "timeout";
|
|
144
144
|
case "unauthenticated":
|
|
145
145
|
return "login-required";
|
|
146
|
+
case "network-unreachable":
|
|
147
|
+
return "network-unreachable";
|
|
146
148
|
case "ineligible":
|
|
147
149
|
return "capability-unavailable";
|
|
148
150
|
case "capability-unavailable":
|
|
@@ -171,6 +173,9 @@ function rejectedCallReason(output) {
|
|
|
171
173
|
if (/\b(?:quota|rate limit|usage limit|too many requests)\b/iu.test(output)) {
|
|
172
174
|
return "quota-exhausted";
|
|
173
175
|
}
|
|
176
|
+
if (isNetworkFailure(output)) {
|
|
177
|
+
return "network-unreachable";
|
|
178
|
+
}
|
|
174
179
|
if (/\b(?:not logged in|login required|log in to|authentication required|unauthenticated|unauthorized)\b/iu.test(output) ||
|
|
175
180
|
/\b(?:invalid|expired)\s+(?:api key|token|credential)/iu.test(output) ||
|
|
176
181
|
/\b401\b/u.test(output)) {
|
|
@@ -1,7 +1,42 @@
|
|
|
1
|
-
import { createServer } from "node:net";
|
|
1
|
+
import { connect, createServer } from "node:net";
|
|
2
2
|
/** Targets that need a loopback listener of their own to answer at all. */
|
|
3
3
|
export const BIND_DEPENDENT_TARGETS = ["agy"];
|
|
4
4
|
const BIND_PROBE_TIMEOUT_MS = 2_000;
|
|
5
|
+
/**
|
|
6
|
+
* A reviewer that cannot reach its API host says so in its own words. Reading
|
|
7
|
+
* that as "not logged in" sent users to re-login an authenticated CLI when the
|
|
8
|
+
* host sandbox was what withheld the network.
|
|
9
|
+
*/
|
|
10
|
+
const NETWORK_FAILURE = /\b(?:ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH)\b|getaddrinfo|fetch failed|network is unreachable|could not resolve host|no such host|dial tcp/iu;
|
|
11
|
+
export function isNetworkFailure(output) {
|
|
12
|
+
return NETWORK_FAILURE.test(output);
|
|
13
|
+
}
|
|
14
|
+
/** The host each reviewer talks to, so a reachability check tests its real path. */
|
|
15
|
+
export const TARGET_API_HOST = {
|
|
16
|
+
agy: "cloudcode-pa.googleapis.com",
|
|
17
|
+
codex: "api.openai.com",
|
|
18
|
+
claude: "api.anthropic.com"
|
|
19
|
+
};
|
|
20
|
+
export const REACH_TIMEOUT_MS = 2_000;
|
|
21
|
+
/**
|
|
22
|
+
* DNS plus a TCP connect to `host:443`, nothing sent. Diagnostic only: it
|
|
23
|
+
* refines a failed probe's reason and never gates a review, so a proxy-only
|
|
24
|
+
* network that fails it still gets the review attempted.
|
|
25
|
+
*/
|
|
26
|
+
export async function probeHostReachable(host, port = 443) {
|
|
27
|
+
return await new Promise((resolve) => {
|
|
28
|
+
const socket = connect({ host, port });
|
|
29
|
+
const finish = (value) => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
socket.destroy();
|
|
32
|
+
resolve(value);
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => finish(false), REACH_TIMEOUT_MS);
|
|
35
|
+
timer.unref();
|
|
36
|
+
socket.once("connect", () => finish(true));
|
|
37
|
+
socket.once("error", () => finish(false));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
/**
|
|
6
41
|
* Opens and immediately closes a loopback listener on an ephemeral port.
|
|
7
42
|
* Cheap enough to run before every review, and it exercises exactly the
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { extractFinalMessage } from "./extract.js";
|
|
6
6
|
import { isolatedReviewEnvironment } from "./execute.js";
|
|
7
|
+
import { isNetworkFailure, probeHostReachable, TARGET_API_HOST } from "./host-sandbox.js";
|
|
7
8
|
import { buildProbeInvocation } from "./invocation.js";
|
|
8
9
|
const PROBE_PROMPT = "Reply with the single word OK and nothing else.";
|
|
9
10
|
/**
|
|
@@ -99,10 +100,18 @@ export async function probeReviewTarget(target, options) {
|
|
|
99
100
|
if (!deep) {
|
|
100
101
|
return spawned.status === "PASS" ? "ok" : "unauthenticated";
|
|
101
102
|
}
|
|
102
|
-
|
|
103
|
-
extractFinalMessage(target, spawned.stdout) !== undefined
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
if (spawned.status === "PASS" &&
|
|
104
|
+
extractFinalMessage(target, spawned.stdout) !== undefined) {
|
|
105
|
+
return "ok";
|
|
106
|
+
}
|
|
107
|
+
// An unreachable API host outranks whatever the CLI printed: a sandbox that
|
|
108
|
+
// withholds the network makes an authenticated CLI say "not logged in".
|
|
109
|
+
if (isNetworkFailure(`${spawned.stderr}\n${spawned.stdout}`)) {
|
|
110
|
+
return "network-unreachable";
|
|
111
|
+
}
|
|
112
|
+
return (await (options.reachable ?? probeHostReachable)(TARGET_API_HOST[target]))
|
|
113
|
+
? "unauthenticated"
|
|
114
|
+
: "network-unreachable";
|
|
106
115
|
}
|
|
107
116
|
finally {
|
|
108
117
|
if (deep) {
|
|
@@ -83,6 +83,11 @@ export function renderReviewResult(result) {
|
|
|
83
83
|
result.attempts?.some((attempt) => attempt.reason === "login-required")) {
|
|
84
84
|
lines.push("Run: agent-ops doctor --check-auth to verify target authentication.");
|
|
85
85
|
}
|
|
86
|
+
if (result.reason === "network-unreachable" ||
|
|
87
|
+
result.attempts?.some((attempt) => attempt.reason === "network-unreachable")) {
|
|
88
|
+
lines.push("The reviewer could not reach the network. This is not a login " +
|
|
89
|
+
"problem: run agent-ops outside the sandbox, or grant it escalated execution.");
|
|
90
|
+
}
|
|
86
91
|
// Evidence is pinned to the source it was produced from, so any edit to a
|
|
87
92
|
// changed file after the verifier ran — a doc rewritten by a later step
|
|
88
93
|
// counts — voids it. Without the next command the caller reads
|
|
@@ -130,10 +130,17 @@ never rewritten.
|
|
|
130
130
|
targets are permitted, `--yes` decides whether to spend money now.
|
|
131
131
|
|
|
132
132
|
Reviewer failures are classified conservatively from their output: recognizable
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
network errors (`ENOTFOUND`, `fetch failed`, `getaddrinfo` and similar) become
|
|
134
|
+
`network-unreachable`, authentication messages become `login-required`, quota
|
|
135
|
+
messages become `quota-exhausted`, and other non-zero exits become
|
|
136
|
+
`capability-unavailable`. A sandbox that withholds the network therefore is not
|
|
137
|
+
reported as a logged-out CLI: `network-unreachable` tells you to run outside the
|
|
138
|
+
sandbox instead of running `<target> login`. When a `--check-auth` probe or the
|
|
139
|
+
review preflight finds a target unauthenticated, a 2-second TCP connection to
|
|
140
|
+
that target's API host on port 443 (`cloudcode-pa.googleapis.com` for agy,
|
|
141
|
+
`api.openai.com` for codex, `api.anthropic.com` for claude) decides between the
|
|
142
|
+
two; it only refines the reported reason and never blocks a review. The review suggests
|
|
143
|
+
`doctor --check-auth` only when an authentication failure was detected. Confirm target authentication with:
|
|
137
144
|
|
|
138
145
|
```bash
|
|
139
146
|
agent-ops doctor # presence only: no tokens, no network
|
|
@@ -111,8 +111,15 @@ task ID 必須明確指定,且永遠使用該 task 的原始 criteria;不能
|
|
|
111
111
|
每次執行 review 仍需 `--yes`:init 的勾選決定「允許哪些目標」,
|
|
112
112
|
`--yes` 決定「現在是否要花錢」。
|
|
113
113
|
|
|
114
|
-
Reviewer
|
|
114
|
+
Reviewer 失敗會依輸出採保守分類:網路錯誤(`ENOTFOUND`、`fetch failed`、
|
|
115
|
+
`getaddrinfo` 等)為 `network-unreachable`,可辨識的認證訊息為 `login-required`,
|
|
115
116
|
quota 訊息為 `quota-exhausted`,其他非零退出則為 `capability-unavailable`。
|
|
117
|
+
因此沙盒擋住網路時不會被誤報成 CLI 未登入:`network-unreachable` 會提示改在
|
|
118
|
+
沙盒外執行,而不是執行 `<target> login`。當 `--check-auth` 探測或 review
|
|
119
|
+
preflight 判定目標未認證時,會再對該目標的 API 主機(agy 為
|
|
120
|
+
`cloudcode-pa.googleapis.com`、codex 為 `api.openai.com`、claude 為
|
|
121
|
+
`api.anthropic.com`)的 443 埠做 2 秒 TCP 連線測試,據此在兩種原因間擇一;
|
|
122
|
+
它只修正回報的原因,不會阻擋 review。
|
|
116
123
|
只有偵測到認證失敗時,review 才會建議執行 `doctor --check-auth`。
|
|
117
124
|
可用以下指令確認目標的認證狀態:
|
|
118
125
|
|