@zvada/agent-server 0.3.2 → 0.3.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.
- package/AGENTS.md +34 -0
- package/CHANGELOG.md +19 -0
- package/README.md +14 -3
- package/docs/consuming.md +13 -0
- package/docs/deploy.md +21 -2
- package/docs/harnesses.md +43 -0
- package/package.json +3 -9
- package/src/client/transports.ts +6 -1
- package/src/core/agents/acp/acp-agent.ts +57 -24
- package/src/core/agents/acp/adapter.ts +4 -0
- package/src/core/agents/claude-code/claude-agent.ts +24 -4
- package/src/core/agents/claude-code/generator-session.ts +1 -1
- package/src/core/agents/claude-code/session-manager.ts +15 -4
- package/src/core/agents/codex-app-server/adapter.ts +8 -5
- package/src/core/agents/codex-app-server/client.ts +3 -1
- package/src/core/agents/codex-app-server/codex-app-server-agent.ts +256 -74
- package/src/core/agents/codex-sdk/adapter.ts +8 -8
- package/src/core/agents/codex-sdk/codex-sdk-agent.ts +71 -29
- package/src/core/agents/session-store.ts +35 -8
- package/src/core/index.ts +3 -0
- package/src/core/presets.ts +7 -2
- package/src/core/provision/pins.ts +4 -4
- package/src/core/provision/provisioner.ts +140 -55
- package/src/core/runtime/event-processor.ts +6 -1
- package/src/protocol/reduce.ts +5 -1
- package/src/protocol/verify.ts +36 -0
- package/src/protocol/wire.ts +11 -5
- package/src/server/acp/translate.ts +32 -22
- package/src/server/agent-server.ts +23 -5
- package/src/server/bin.ts +40 -5
- package/src/server/feedback.ts +76 -0
|
@@ -111,16 +111,31 @@ export class AgentServer {
|
|
|
111
111
|
if (this.shuttingDown) return;
|
|
112
112
|
this.shuttingDown = true;
|
|
113
113
|
// Drain first so cancelled turns still broadcast their turn.ended.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this
|
|
114
|
+
// The drain (and each transport's close) is a library extension point
|
|
115
|
+
// that may throw; the finally still closes every transport and runs the
|
|
116
|
+
// host hook even then — every other external call in this file is
|
|
117
|
+
// defended the same way (broadcast/observers/sink/handleLine).
|
|
118
|
+
try {
|
|
119
|
+
await this.runtime.shutdown();
|
|
120
|
+
} finally {
|
|
121
|
+
for (const transport of [...this.transports]) {
|
|
122
|
+
try {
|
|
123
|
+
transport.close();
|
|
124
|
+
} catch {
|
|
125
|
+
// One faulty transport's close must not skip the others or the
|
|
126
|
+
// host's shutdown hook — same isolation broadcast uses for send.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.options.onShutdown?.();
|
|
130
|
+
}
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
private async handleLine(transport: WireTransport, line: string): Promise<void> {
|
|
120
134
|
if (!line.trim()) return;
|
|
121
135
|
const msg = decodeWireMessage(line);
|
|
122
136
|
if (msg.kind === "invalid") {
|
|
123
|
-
|
|
137
|
+
const code = msg.parseError ? WIRE_ERROR_CODES.parseError : WIRE_ERROR_CODES.invalidRequest;
|
|
138
|
+
transport.send(encodeErrorResponse(null, code, msg.error));
|
|
124
139
|
return;
|
|
125
140
|
}
|
|
126
141
|
// Clients only send requests on this wire; stray notifications/responses
|
|
@@ -375,7 +390,10 @@ export class AgentServer {
|
|
|
375
390
|
|
|
376
391
|
case WIRE_METHODS.shutdown: {
|
|
377
392
|
transport.send(encodeResponse(id, {}));
|
|
378
|
-
|
|
393
|
+
// The success ack is already committed on the wire; a throw out of
|
|
394
|
+
// shutdown() must not reach handleLine's catch and emit a second
|
|
395
|
+
// response for this id (a JSON-RPC 2.0 violation).
|
|
396
|
+
await this.shutdown().catch(() => {});
|
|
379
397
|
return;
|
|
380
398
|
}
|
|
381
399
|
|
package/src/server/bin.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// agent-server install # prefetch the pinned agent CLIs
|
|
12
12
|
//
|
|
13
13
|
// All logs go to stderr; stdout is reserved for the wire in stdio mode.
|
|
14
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
14
15
|
import {
|
|
15
16
|
KNOWN_ACP_AGENTS,
|
|
16
17
|
type ProvisionMode,
|
|
@@ -19,13 +20,14 @@ import {
|
|
|
19
20
|
} from "../core/index.ts";
|
|
20
21
|
import { isAgentHarness } from "../protocol/index.ts";
|
|
21
22
|
import { AgentServer } from "./agent-server.ts";
|
|
23
|
+
import { runFeedback } from "./feedback.ts";
|
|
22
24
|
import { runInstall } from "./install.ts";
|
|
23
25
|
import { dial, listen, stdioTransport } from "./transports.ts";
|
|
24
26
|
|
|
25
|
-
const VERSION =
|
|
27
|
+
const VERSION: string = pkg.version;
|
|
26
28
|
|
|
27
29
|
interface Args {
|
|
28
|
-
command: "serve" | "install";
|
|
30
|
+
command: "serve" | "install" | "feedback";
|
|
29
31
|
mode: "stdio" | "listen" | "dial" | "acp";
|
|
30
32
|
listen?: { hostname?: string; port: number };
|
|
31
33
|
dialUrl?: string;
|
|
@@ -33,21 +35,27 @@ interface Args {
|
|
|
33
35
|
acpAgent?: string;
|
|
34
36
|
bufferSize?: number;
|
|
35
37
|
provisionMode?: ProvisionMode;
|
|
38
|
+
feedback?: { message?: string; category?: string; subject?: string; resume?: string };
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function usage(exitCode: number): never {
|
|
39
42
|
console.error(
|
|
40
43
|
[
|
|
41
|
-
"usage: agent-server [install] [--listen [host:]port | --dial <ws-url>] [options]",
|
|
44
|
+
"usage: agent-server [install|feedback] [--listen [host:]port | --dial <ws-url>] [options]",
|
|
42
45
|
"",
|
|
43
46
|
" (default) serve NDJSON JSON-RPC on stdio",
|
|
44
47
|
" --acp speak ACP v1 on stdio instead (be the agent)",
|
|
45
48
|
" install download the pinned agent CLIs into the managed cache",
|
|
46
49
|
" (always downloads — run at image build time to erase",
|
|
47
50
|
" sandbox cold starts; --provision applies to serving only)",
|
|
51
|
+
' feedback "<report>" report friction to the agent-server maintainers',
|
|
52
|
+
" (--category api|cli|docs|other, --subject <exact item>,",
|
|
53
|
+
" --resume <threadId>; no auth; structured eval reports",
|
|
54
|
+
" ride `npx hivenet --to agent-server --task ...`)",
|
|
48
55
|
" --listen [host:]port accept WebSocket clients",
|
|
49
56
|
" --dial <ws-url> dial out to a consumer (auto-reconnect)",
|
|
50
57
|
" --harness <a,b> restrict harnesses (default: all installed)",
|
|
58
|
+
" --acp-agent <spec> ACP agent: known name (pi|gemini) or launch command",
|
|
51
59
|
" --provision <mode> CLI provisioning: auto | pinned | system (default auto)",
|
|
52
60
|
" --buffer <n> replay buffer size per session (default 4096)",
|
|
53
61
|
" --version, --help",
|
|
@@ -69,7 +77,17 @@ function parseArgs(argv: string[]): Args {
|
|
|
69
77
|
return v;
|
|
70
78
|
};
|
|
71
79
|
if (arg === "install" && i === 0) args.command = "install";
|
|
72
|
-
else if (arg === "
|
|
80
|
+
else if (arg === "feedback" && i === 0) {
|
|
81
|
+
args.command = "feedback";
|
|
82
|
+
args.feedback = {};
|
|
83
|
+
} else if (args.command === "feedback" && args.feedback) {
|
|
84
|
+
if (arg === "--category") args.feedback.category = next();
|
|
85
|
+
else if (arg === "--subject") args.feedback.subject = next();
|
|
86
|
+
else if (arg === "--resume") args.feedback.resume = next();
|
|
87
|
+
else if (!arg.startsWith("--") && args.feedback.message === undefined)
|
|
88
|
+
args.feedback.message = arg;
|
|
89
|
+
else usage(1);
|
|
90
|
+
} else if (arg === "--stdio") args.mode = "stdio";
|
|
73
91
|
else if (arg === "--acp") args.mode = "acp";
|
|
74
92
|
else if (arg === "--listen") {
|
|
75
93
|
args.mode = "listen";
|
|
@@ -80,7 +98,11 @@ function parseArgs(argv: string[]): Args {
|
|
|
80
98
|
args.mode = "dial";
|
|
81
99
|
args.dialUrl = next();
|
|
82
100
|
} else if (arg === "--harness") args.harnesses = next().split(",").filter(Boolean);
|
|
83
|
-
else if (arg === "--
|
|
101
|
+
else if (arg === "--acp-agent" || arg.startsWith("--acp-agent=")) {
|
|
102
|
+
const spec = arg === "--acp-agent" ? next() : arg.slice(12);
|
|
103
|
+
if (!spec.trim() || spec.startsWith("-")) usage(1);
|
|
104
|
+
args.acpAgent = spec;
|
|
105
|
+
} else if (arg === "--provision") {
|
|
84
106
|
const mode = next();
|
|
85
107
|
if (mode !== "auto" && mode !== "pinned" && mode !== "system") usage(1);
|
|
86
108
|
args.provisionMode = mode;
|
|
@@ -98,6 +120,19 @@ function parseArgs(argv: string[]): Args {
|
|
|
98
120
|
|
|
99
121
|
const args = parseArgs(process.argv.slice(2));
|
|
100
122
|
|
|
123
|
+
if (args.command === "feedback") {
|
|
124
|
+
const message = args.feedback?.message;
|
|
125
|
+
if (!message) usage(1);
|
|
126
|
+
try {
|
|
127
|
+
const result = await runFeedback({ ...args.feedback, message, clientVersion: VERSION });
|
|
128
|
+
for (const line of result.lines) console.error(line);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`feedback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
101
136
|
const harnesses = args.harnesses?.map((h) => {
|
|
102
137
|
if (isAgentHarness(h)) return h;
|
|
103
138
|
if (KNOWN_ACP_AGENTS[h]) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// `agent-server feedback` — report friction to the maintainers over hivenet.
|
|
2
|
+
//
|
|
3
|
+
// Deliberately zero-dep (raw fetch, no `hivenet` package): the compiled
|
|
4
|
+
// single-binary deploys have no npm/npx, so this subcommand is the only
|
|
5
|
+
// feedback path there. Wire shape per hivenet's v1 feedback API.
|
|
6
|
+
|
|
7
|
+
const ENDPOINT = "https://hivenet.app/v1/feedback";
|
|
8
|
+
const PROJECT = "agent-server";
|
|
9
|
+
const TIMEOUT_MS = 10_000;
|
|
10
|
+
|
|
11
|
+
export interface FeedbackOptions {
|
|
12
|
+
message: string;
|
|
13
|
+
/** Suggested: api | cli | docs | other. Defaults to cli. */
|
|
14
|
+
category?: string;
|
|
15
|
+
/** The exact item: export, error code, harness, or command. */
|
|
16
|
+
subject?: string;
|
|
17
|
+
/** Continue an existing thread instead of opening a new one. */
|
|
18
|
+
resume?: string;
|
|
19
|
+
clientVersion?: string;
|
|
20
|
+
/** Injectable transport boundary for tests. */
|
|
21
|
+
fetchImpl?: (url: string, init: RequestInit) => Promise<Response>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FeedbackResult {
|
|
25
|
+
threadId: string;
|
|
26
|
+
/** Human-readable lines to print (thread id, guidance, ask). */
|
|
27
|
+
lines: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** POST one feedback report; returns the lines the CLI should print. */
|
|
31
|
+
export async function runFeedback(options: FeedbackOptions): Promise<FeedbackResult> {
|
|
32
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
33
|
+
const threadId = options.resume ?? crypto.randomUUID().replaceAll("-", "");
|
|
34
|
+
const response = await doFetch(ENDPOINT, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "content-type": "application/json" },
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
v: 1,
|
|
39
|
+
to: PROJECT,
|
|
40
|
+
feedback: options.message,
|
|
41
|
+
category: options.category ?? "cli",
|
|
42
|
+
...(options.subject ? { subject: options.subject } : {}),
|
|
43
|
+
thread: { id: threadId },
|
|
44
|
+
client: { name: "agent-server-cli", version: options.clientVersion ?? "0" },
|
|
45
|
+
consent: { telemetry: false },
|
|
46
|
+
}),
|
|
47
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`feedback rejected: ${response.status} ${await safeText(response)}`);
|
|
51
|
+
}
|
|
52
|
+
const body = (await response.json()) as {
|
|
53
|
+
thread?: string;
|
|
54
|
+
guidance?: string;
|
|
55
|
+
ask?: { prompt?: string; command?: string };
|
|
56
|
+
};
|
|
57
|
+
const thread = body.thread ?? threadId;
|
|
58
|
+
const lines = [
|
|
59
|
+
`delivered to ${PROJECT} (thread ${thread})`,
|
|
60
|
+
`continue this thread: agent-server feedback --resume ${thread} "<follow-up>"`,
|
|
61
|
+
];
|
|
62
|
+
if (body.guidance) lines.push(`guidance from the maintainers: ${body.guidance}`);
|
|
63
|
+
if (body.ask?.prompt) {
|
|
64
|
+
lines.push(`the maintainers ask: ${body.ask.prompt}`);
|
|
65
|
+
if (body.ask.command) lines.push(` answer (only if grounded): ${body.ask.command}`);
|
|
66
|
+
}
|
|
67
|
+
return { threadId: thread, lines };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function safeText(response: Response): Promise<string> {
|
|
71
|
+
try {
|
|
72
|
+
return (await response.text()).slice(0, 200);
|
|
73
|
+
} catch {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
}
|