@automatalabs/acp-agents 0.5.0 → 0.6.0
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/README.md +1 -0
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +40 -6
- package/dist/backend.d.ts +11 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backends/custom.d.ts +7 -0
- package/dist/backends/custom.d.ts.map +1 -1
- package/dist/backends/custom.js +27 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +10 -7
- package/dist/registry.d.ts +8 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +18 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +29 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -99,6 +99,7 @@ Also exported: `AcpAgentPool` / `resolvePoolSize`, `PooledConnection` / `Session
|
|
|
99
99
|
| --- | --- |
|
|
100
100
|
| `AGENTPRISM_DEFAULT_BACKEND` | Backend when `model`/`tier` don't pick one (`codex` selects Codex; a registered custom name selects that backend; anything else is Claude). |
|
|
101
101
|
| `AGENTPRISM_BACKENDS` | Custom ACP backends as JSON: `{"<name>": {"command": "…", "args": […], "env": {…}, "sessionMeta": {…}}}`. |
|
|
102
|
+
| `AGENTPRISM_ACP_INIT_TIMEOUT_MS` | Deadline (default `60000`) for a backend's one-time ACP `initialize` handshake — a non-ACP command fails fast instead of hanging. |
|
|
102
103
|
| `AGENTPRISM_ACP_POOL_SIZE` | Long-lived processes to keep per backend (default `1`). |
|
|
103
104
|
| `AGENTPRISM_CLAUDE_ACP_CMD` / `AGENTPRISM_CLAUDE_ACP_ARGS` | Override the command (and args) used to spawn the Claude ACP server. |
|
|
104
105
|
| `AGENTPRISM_CODEX_ACP_CMD` / `AGENTPRISM_CODEX_ACP_ARGS` | Override the command (and args) used to spawn the Codex ACP server. |
|
package/dist/acp-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"AAwBA,OAAO,EACL,oBAAoB,EAOpB,KAAK,cAAc,EAGnB,KAAK,mBAAmB,EAGxB,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAa,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACrG,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACzF,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiB9C,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;oFACoF;AACpF,cAAM,YAAY;IAUd,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAXzB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAM;IACnC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAM;IAC3C,QAAQ,CAAC,KAAK,mBAA0B;IACxC,gBAAgB,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,cAAc,CAAK;IAE3B;6EACyE;gBAE9D,MAAM,EAAE,UAAU,EAClB,KAAK,CAAC,EAAE,MAAM,YAAA,EACd,KAAK,CAAC,EAAE,MAAM,YAAA;IAGzB,+FAA+F;IAC/F,SAAS,IAAI,IAAI;IAKjB,eAAe,IAAI,MAAM;IAIzB,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,IAAI;IAoCxD,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI;CAK7D;
|
|
1
|
+
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-client.js
CHANGED
|
@@ -171,6 +171,18 @@ class MultiplexClient {
|
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
const DEFAULT_INIT_TIMEOUT_MS = 60_000;
|
|
175
|
+
/** Deadline for the one-time ACP `initialize` handshake per pooled process. Overridable via
|
|
176
|
+
* AGENTPRISM_ACP_INIT_TIMEOUT_MS (e.g. for slow cold-start backends). Clamped to >= 1s. */
|
|
177
|
+
function initializeTimeoutMs() {
|
|
178
|
+
const env = process.env.AGENTPRISM_ACP_INIT_TIMEOUT_MS;
|
|
179
|
+
if (env !== undefined) {
|
|
180
|
+
const parsed = Number.parseInt(env, 10);
|
|
181
|
+
if (Number.isFinite(parsed) && parsed >= 1000)
|
|
182
|
+
return parsed;
|
|
183
|
+
}
|
|
184
|
+
return DEFAULT_INIT_TIMEOUT_MS;
|
|
185
|
+
}
|
|
174
186
|
/** Shallow-merge `_meta` layers lowest-to-highest precedence, treating empty layers as absent
|
|
175
187
|
* so an unconfigured session keeps sending NO `_meta` at all. */
|
|
176
188
|
function layerMeta(...layers) {
|
|
@@ -307,12 +319,34 @@ export class PooledConnection {
|
|
|
307
319
|
return Promise.race([op, dead]);
|
|
308
320
|
}
|
|
309
321
|
async initialize() {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
// Handshake deadline: a command that is NOT an ACP server never answers `initialize`, and
|
|
323
|
+
// without a deadline the first openSession() would hang forever. On timeout the process is
|
|
324
|
+
// killed and a legible error surfaces (fail-fast hygiene — this is NOT a security gate;
|
|
325
|
+
// the process has already been spawned by then). Tunable for slow cold starts.
|
|
326
|
+
const timeoutMs = initializeTimeoutMs();
|
|
327
|
+
let timer;
|
|
328
|
+
const deadline = new Promise((_, reject) => {
|
|
329
|
+
timer = setTimeout(() => {
|
|
330
|
+
reject(new Error(`ACP agent (${this.backendId}) did not complete the ACP initialize handshake within ${timeoutMs}ms — ` +
|
|
331
|
+
`is the configured command an ACP server?${this.stderrSuffix()}`));
|
|
332
|
+
this.killNow();
|
|
333
|
+
}, timeoutMs);
|
|
334
|
+
timer.unref?.();
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
const response = await Promise.race([
|
|
338
|
+
this.race(this.rpc.initialize({
|
|
339
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
340
|
+
clientCapabilities: {},
|
|
341
|
+
clientInfo: { ...CLIENT_INFO },
|
|
342
|
+
})),
|
|
343
|
+
deadline,
|
|
344
|
+
]);
|
|
345
|
+
this.supportsClose = Boolean(response.agentCapabilities?.sessionCapabilities?.close);
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
}
|
|
316
350
|
}
|
|
317
351
|
/**
|
|
318
352
|
* Open a new per-agent session on this pooled connection: session/new { cwd }, register its
|
package/dist/backend.d.ts
CHANGED
|
@@ -26,6 +26,17 @@ export interface SessionMetaInputs {
|
|
|
26
26
|
}
|
|
27
27
|
export interface Backend {
|
|
28
28
|
readonly id: BackendId;
|
|
29
|
+
/** Pool identity for this backend's long-lived processes. Defaults to `id` — but a CUSTOM
|
|
30
|
+
* backend sets it to id + a spawn-config hash, because two runs may declare the SAME name
|
|
31
|
+
* with DIFFERENT commands (script-declared `meta.backends`): keying the pool by name alone
|
|
32
|
+
* would hand run B a pooled process spawned from run A's command. */
|
|
33
|
+
readonly poolKey?: string;
|
|
34
|
+
/** When true, the runner EMBEDS the JSON Schema in the prompt text on schema runs. For the
|
|
35
|
+
* built-ins the native constraint channel is authoritative and this stays unset; a CUSTOM
|
|
36
|
+
* backend sets it because its agent may ignore the `_meta.outputSchema` forward entirely —
|
|
37
|
+
* without the schema in the prompt, such an agent returns well-formed JSON with the WRONG
|
|
38
|
+
* KEYS and the repair ladder can never converge (it can fix prose, not unseen contracts). */
|
|
39
|
+
readonly embedSchemaInPrompt?: boolean;
|
|
29
40
|
/** How to launch this backend's ACP server over stdio. */
|
|
30
41
|
spawnConfig(): SpawnConfig;
|
|
31
42
|
/** OPTIONAL backend-level `_meta` DEFAULTS for session/new (e.g. a custom registry entry's
|
package/dist/backend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAClD;sFACsF;AACtF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,eAAe,IAAI,MAAM,CAAC;IAC1B,uGAAuG;IACvG,mBAAmB,IAAI,OAAO,CAAC;CAChC;AAED;+EAC+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sFAAsF;IACtF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,0DAA0D;IAC1D,WAAW,IAAI,WAAW,CAAC;IAC3B;;sEAEkE;IAClE,mBAAmB,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC5D;;;mGAG+F;IAC/F,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC1G,kGAAkG;IAClG,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC7E,oGAAoG;IACpG,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;CACrD;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAE7D"}
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAClD;sFACsF;AACtF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,eAAe,IAAI,MAAM,CAAC;IAC1B,uGAAuG;IACvG,mBAAmB,IAAI,OAAO,CAAC;CAChC;AAED;+EAC+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sFAAsF;IACtF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB;;;0EAGsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;kGAI8F;IAC9F,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,0DAA0D;IAC1D,WAAW,IAAI,WAAW,CAAC;IAC3B;;sEAEkE;IAClE,mBAAmB,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC5D;;;mGAG+F;IAC/F,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC1G,kGAAkG;IAClG,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC7E,oGAAoG;IACpG,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;CACrD;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAE7D"}
|
|
@@ -4,6 +4,13 @@ import type { RegisteredBackend } from "../registry.js";
|
|
|
4
4
|
export declare class CustomAcpBackend implements Backend {
|
|
5
5
|
private readonly config;
|
|
6
6
|
readonly id: string;
|
|
7
|
+
/** id + spawn-config hash: two registries (e.g. two scripts' `meta.backends`) may declare
|
|
8
|
+
* the SAME name with DIFFERENT commands — the pool must never share a process across them. */
|
|
9
|
+
readonly poolKey: string;
|
|
10
|
+
/** The agent may ignore the `_meta.outputSchema` forward, so the runner must also state the
|
|
11
|
+
* schema in the prompt — otherwise the model returns JSON with keys it invented and the
|
|
12
|
+
* repair ladder can never converge on a contract the model was never shown. */
|
|
13
|
+
readonly embedSchemaInPrompt = true;
|
|
7
14
|
constructor(config: RegisteredBackend);
|
|
8
15
|
spawnConfig(): SpawnConfig;
|
|
9
16
|
sessionMetaDefaults(): Record<string, unknown> | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/backends/custom.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/backends/custom.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,qBAAa,gBAAiB,YAAW,OAAO;IAUlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IATnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;mGAC+F;IAC/F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;oFAEgF;IAChF,QAAQ,CAAC,mBAAmB,QAAQ;gBAEP,MAAM,EAAE,iBAAiB;IAUtD,WAAW,IAAI,WAAW;IAS1B,mBAAmB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAQ1D,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAM9E,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAQ5E,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO;CAGpD"}
|
package/dist/backends/custom.js
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
|
+
// CustomAcpBackend — drives ANY registered ACP agent (registry.ts), not just the built-in
|
|
2
|
+
// Claude/Codex pair. The generic dialect it speaks is the one this repo already publishes:
|
|
3
|
+
// - schema IN: the bare turn-level `_meta.outputSchema` (same key the @automatalabs/codex-acp
|
|
4
|
+
// fork reads), as a plain JSON Schema. An agent that honors it constrains its
|
|
5
|
+
// final message natively; an agent that ignores it still works — the runner's
|
|
6
|
+
// validate/re-prompt ladder repairs off the final text.
|
|
7
|
+
// - result OUT: JSON.parse of the final assistant message (with a balanced-block fallback),
|
|
8
|
+
// exactly like Codex — no vendor extension notification required.
|
|
9
|
+
// - config-level `_meta`: the registry entry's static `sessionMeta` rides every session/new
|
|
10
|
+
// (per-call RunOptions.meta merges over it in the ACP client; backend-computed
|
|
11
|
+
// keys and the runId stamp win over both).
|
|
12
|
+
// SessionMetaInputs (Codex base/developer instruction overrides) are IGNORED — they are a
|
|
13
|
+
// codex-acp vendor contract; a custom agent's knobs travel through the generic meta channels.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
1
15
|
import { META_KEYS } from "@automatalabs/shared-types";
|
|
2
16
|
import { toJsonSchema } from "../schema-strict.js";
|
|
3
17
|
import { parseFinalJson } from "../structured-output.js";
|
|
4
18
|
export class CustomAcpBackend {
|
|
5
19
|
config;
|
|
6
20
|
id;
|
|
21
|
+
/** id + spawn-config hash: two registries (e.g. two scripts' `meta.backends`) may declare
|
|
22
|
+
* the SAME name with DIFFERENT commands — the pool must never share a process across them. */
|
|
23
|
+
poolKey;
|
|
24
|
+
/** The agent may ignore the `_meta.outputSchema` forward, so the runner must also state the
|
|
25
|
+
* schema in the prompt — otherwise the model returns JSON with keys it invented and the
|
|
26
|
+
* repair ladder can never converge on a contract the model was never shown. */
|
|
27
|
+
embedSchemaInPrompt = true;
|
|
7
28
|
constructor(config) {
|
|
8
29
|
this.config = config;
|
|
9
30
|
this.id = config.name;
|
|
31
|
+
const spawnIdentity = JSON.stringify({
|
|
32
|
+
command: config.command,
|
|
33
|
+
args: config.args ?? [],
|
|
34
|
+
env: config.env ?? {},
|
|
35
|
+
});
|
|
36
|
+
this.poolKey = `${config.name}#${createHash("sha256").update(spawnIdentity).digest("hex").slice(0, 12)}`;
|
|
10
37
|
}
|
|
11
38
|
spawnConfig() {
|
|
12
39
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { AcpAgentRunner, createAcpRunner, selectBackend } from "./runner.js";
|
|
2
2
|
export type { AcpRunnerOptions } from "./runner.js";
|
|
3
|
-
export { BACKENDS_ENV, resolveBackendRegistry } from "./registry.js";
|
|
3
|
+
export { BACKENDS_ENV, registryWithRunBackends, resolveBackendRegistry } from "./registry.js";
|
|
4
4
|
export type { BackendRegistry, CustomBackendConfig, RegisteredBackend } from "./registry.js";
|
|
5
5
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
6
6
|
export type { AcpSessionOptions, PooledConnectionDeps } from "./acp-client.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// @automatalabs/workflows facade (which mcp-server builds on) via createAcpRunner().
|
|
7
7
|
export { AcpAgentRunner, createAcpRunner, selectBackend } from "./runner.js";
|
|
8
8
|
// The custom-backend registry: run ANY ACP agent as an agent() target.
|
|
9
|
-
export { BACKENDS_ENV, resolveBackendRegistry } from "./registry.js";
|
|
9
|
+
export { BACKENDS_ENV, registryWithRunBackends, resolveBackendRegistry } from "./registry.js";
|
|
10
10
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
11
11
|
export { AcpAgentPool, resolvePoolSize } from "./pool.js";
|
|
12
12
|
// The typed ACP event bus surfaced on AcpAgentRunner (`runner.on(name, evt => …)`).
|
package/dist/pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,cAAc,CAAC;AACvD,OAAO,EAAoB,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,cAAc;IAC7B,mGAAmG;IACnG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;kGACkG;AAClG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED,qBAAa,YAAY;IASrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IARvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAS;gBAGvB,OAAO,GAAE,cAAmB,EACX,IAAI,GAAE,WAAgB;IAKzC,8FAA8F;IACxF,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAMhF;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,cAAc,CAAC;AACvD,OAAO,EAAoB,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,cAAc;IAC7B,mGAAmG;IACnG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;kGACkG;AAClG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED,qBAAa,YAAY;IASrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IARvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAS;gBAGvB,OAAO,GAAE,cAAmB,EACX,IAAI,GAAE,WAAgB;IAKzC,8FAA8F;IACxF,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAMhF;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,cAAc;IAStB,+DAA+D;IAC/D,OAAO,CAAC,IAAI;IAOZ,iEAAiE;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,cAAc;IAMtB,gGAAgG;IAChG,OAAO,CAAC,WAAW;CAGpB"}
|
package/dist/pool.js
CHANGED
|
@@ -38,7 +38,10 @@ export class AcpAgentPool {
|
|
|
38
38
|
* double-book a connection.
|
|
39
39
|
*/
|
|
40
40
|
selectConnection(backend) {
|
|
41
|
-
|
|
41
|
+
// Pool identity: poolKey (id + spawn-config hash for custom backends) over bare id, so two
|
|
42
|
+
// runs declaring the same NAME with different COMMANDS never share a process.
|
|
43
|
+
const key = backend.poolKey ?? backend.id;
|
|
44
|
+
const connections = this.connectionsFor(key);
|
|
42
45
|
const live = connections.filter((c) => c.alive);
|
|
43
46
|
const idle = live.find((c) => c.activeSessions === 0);
|
|
44
47
|
if (idle)
|
|
@@ -46,7 +49,7 @@ export class AcpAgentPool {
|
|
|
46
49
|
if (live.length < this.size) {
|
|
47
50
|
this.installExitHook();
|
|
48
51
|
const connection = PooledConnection.create(backend, {
|
|
49
|
-
onDead: (dead) => this.drop(
|
|
52
|
+
onDead: (dead) => this.drop(key, dead),
|
|
50
53
|
onEvent: this.deps.onEvent,
|
|
51
54
|
});
|
|
52
55
|
connections.push(connection);
|
|
@@ -55,17 +58,17 @@ export class AcpAgentPool {
|
|
|
55
58
|
// At capacity with every connection busy: multiplex onto the least-loaded one.
|
|
56
59
|
return live.reduce((least, c) => (c.activeSessions < least.activeSessions ? c : least));
|
|
57
60
|
}
|
|
58
|
-
connectionsFor(
|
|
59
|
-
let arr = this.byBackend.get(
|
|
61
|
+
connectionsFor(key) {
|
|
62
|
+
let arr = this.byBackend.get(key);
|
|
60
63
|
if (!arr) {
|
|
61
64
|
arr = [];
|
|
62
|
-
this.byBackend.set(
|
|
65
|
+
this.byBackend.set(key, arr);
|
|
63
66
|
}
|
|
64
67
|
return arr;
|
|
65
68
|
}
|
|
66
69
|
/** Evict a dead connection so it is never handed out again. */
|
|
67
|
-
drop(
|
|
68
|
-
const arr = this.byBackend.get(
|
|
70
|
+
drop(key, connection) {
|
|
71
|
+
const arr = this.byBackend.get(key);
|
|
69
72
|
if (!arr)
|
|
70
73
|
return;
|
|
71
74
|
const index = arr.indexOf(connection);
|
package/dist/registry.d.ts
CHANGED
|
@@ -23,4 +23,12 @@ export type BackendRegistry = ReadonlyMap<string, RegisteredBackend>;
|
|
|
23
23
|
* misroute at run time.
|
|
24
24
|
*/
|
|
25
25
|
export declare function resolveBackendRegistry(option?: Record<string, CustomBackendConfig>, env?: NodeJS.ProcessEnv): BackendRegistry;
|
|
26
|
+
/**
|
|
27
|
+
* Layer a RUN-SCOPED registry (an approved script-declared `meta.backends`, arriving via
|
|
28
|
+
* RunOptions.backends) UNDER the host registry: run entries are validated with the same rules
|
|
29
|
+
* (reserved names rejected), and a host-registered name always wins on conflict — a script can
|
|
30
|
+
* never hijack a name the operator configured. Returns the host registry unchanged when the
|
|
31
|
+
* run declares nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function registryWithRunBackends(host: BackendRegistry, run?: Record<string, CustomBackendConfig>): BackendRegistry;
|
|
26
34
|
//# sourceMappingURL=registry.d.ts.map
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAKlD,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;2FACuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,eAAe,CAwBjB"}
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAKlD,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;2FACuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,eAAe,CAwBjB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,eAAe,EACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GACxC,eAAe,CAQjB"}
|
package/dist/registry.js
CHANGED
|
@@ -39,6 +39,24 @@ export function resolveBackendRegistry(option, env = process.env) {
|
|
|
39
39
|
}
|
|
40
40
|
return registry;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Layer a RUN-SCOPED registry (an approved script-declared `meta.backends`, arriving via
|
|
44
|
+
* RunOptions.backends) UNDER the host registry: run entries are validated with the same rules
|
|
45
|
+
* (reserved names rejected), and a host-registered name always wins on conflict — a script can
|
|
46
|
+
* never hijack a name the operator configured. Returns the host registry unchanged when the
|
|
47
|
+
* run declares nothing.
|
|
48
|
+
*/
|
|
49
|
+
export function registryWithRunBackends(host, run) {
|
|
50
|
+
if (!run || Object.keys(run).length === 0)
|
|
51
|
+
return host;
|
|
52
|
+
const merged = new Map();
|
|
53
|
+
for (const [name, config] of Object.entries(run)) {
|
|
54
|
+
merged.set(...validateEntry(name, config, "script backends (meta.backends)"));
|
|
55
|
+
}
|
|
56
|
+
for (const [name, entry] of host)
|
|
57
|
+
merged.set(name, entry); // host wins on conflict
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
42
60
|
function validateEntry(rawName, config, source) {
|
|
43
61
|
const name = rawName.toLowerCase();
|
|
44
62
|
if (!NAME_PATTERN.test(name)) {
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,OAAO,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAC;AAQvB;0FAC0F;AAC1F,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD;6FACyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAChD;AAED,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C;uGACmG;IACnG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgE;gBAE9E,OAAO,GAAE,gBAAqB;IAK1C;;;;;;;;OAQG;IACH,EAAE,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAI9E,+EAA+E;IAC/E,IAAI,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAIhF,GAAG,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIzE,kBAAkB,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI;IAI7C,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAInC,GAAG,CAAC,CAAC,SAAS,OAAO,GAAG,SAAS,GAAG,SAAS,EACjD,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,UAAU,CAAC,CAAC,CAAM,GAC1B,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IA2G1B;qGACiG;IAC3F,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B;AAED;;;qDAGqD;AACrD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,cAAc,CAE1E;AAmFD;;;;oCAIoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAM1G"}
|
package/dist/runner.js
CHANGED
|
@@ -22,8 +22,9 @@ import { TypedEventEmitter, } from "./events.js";
|
|
|
22
22
|
import { ClaudeBackend } from "./backends/claude.js";
|
|
23
23
|
import { CodexBackend } from "./backends/codex.js";
|
|
24
24
|
import { CustomAcpBackend } from "./backends/custom.js";
|
|
25
|
-
import { resolveBackendRegistry } from "./registry.js";
|
|
25
|
+
import { registryWithRunBackends, resolveBackendRegistry, } from "./registry.js";
|
|
26
26
|
import { mapThrownError } from "./errors-map.js";
|
|
27
|
+
import { toJsonSchema } from "./schema-strict.js";
|
|
27
28
|
import { resolveStructuredOutput } from "./structured-output.js";
|
|
28
29
|
export class AcpAgentRunner {
|
|
29
30
|
pool;
|
|
@@ -65,7 +66,20 @@ export class AcpAgentRunner {
|
|
|
65
66
|
async run(prompt, options = {}) {
|
|
66
67
|
const opts = options;
|
|
67
68
|
const schema = opts.schema;
|
|
68
|
-
|
|
69
|
+
// Layer any run-scoped backends (an APPROVED script-declared meta.backends) under the
|
|
70
|
+
// host registry. Malformed/reserved entries fail the call loudly and are NOT retried —
|
|
71
|
+
// re-running a misdeclared registry can never succeed.
|
|
72
|
+
let registry;
|
|
73
|
+
try {
|
|
74
|
+
registry = registryWithRunBackends(this.backends, opts.backends);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new WorkflowError(error.message, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, {
|
|
78
|
+
recoverable: false,
|
|
79
|
+
agentLabel: opts.label,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const backend = selectBackend(opts, registry);
|
|
69
83
|
const policy = { allow: opts.toolNames, deny: opts.disallowedToolNames };
|
|
70
84
|
const cwd = opts.cwd ?? process.cwd();
|
|
71
85
|
const session = await this.pool.acquire(backend, {
|
|
@@ -92,7 +106,7 @@ export class AcpAgentRunner {
|
|
|
92
106
|
// model id: "browser" selects nothing; "browser/foo" selects "foo". Built-ins get the
|
|
93
107
|
// full spec unchanged (their catalogs match provider-prefixed and bare ids).
|
|
94
108
|
await applyModelSelection(session, innerModelSpec(opts.model ?? opts.tier, backend), opts);
|
|
95
|
-
const text = buildPrompt(prompt, opts,
|
|
109
|
+
const text = buildPrompt(prompt, opts, schema, backend);
|
|
96
110
|
// Generic turn-scoped _meta passthrough merged UNDER the backend-computed keys (e.g. the
|
|
97
111
|
// outputSchema forward when a schema is set) — user meta never clobbers the schema channel.
|
|
98
112
|
const promptMeta = mergeTurnMeta(opts.promptMeta, backend.promptMeta(schema));
|
|
@@ -218,20 +232,28 @@ async function applyModelSelection(session, spec, opts) {
|
|
|
218
232
|
for (const fallback of modifierFallbacks ?? [])
|
|
219
233
|
opts.onModelFallback?.(fallback);
|
|
220
234
|
}
|
|
221
|
-
function buildPrompt(prompt, opts,
|
|
235
|
+
function buildPrompt(prompt, opts, schema, backend) {
|
|
222
236
|
const parts = [];
|
|
223
237
|
if (opts.instructions)
|
|
224
238
|
parts.push(opts.instructions);
|
|
225
239
|
if (opts.label)
|
|
226
240
|
parts.push(`Task label: ${opts.label}`);
|
|
227
241
|
parts.push(prompt);
|
|
228
|
-
if (
|
|
229
|
-
|
|
242
|
+
if (schema) {
|
|
243
|
+
const contract = [
|
|
230
244
|
"Final output contract:",
|
|
231
245
|
"- Your FINAL message MUST be a single JSON object that conforms to the required output schema.",
|
|
232
246
|
"- Output ONLY that JSON object — no prose, no explanation, and no markdown code fences.",
|
|
233
247
|
"- If you need to inspect files or run commands first, do so, then emit the JSON object as your final message.",
|
|
234
|
-
]
|
|
248
|
+
];
|
|
249
|
+
if (backend.embedSchemaInPrompt) {
|
|
250
|
+
// The agent behind a custom backend may ignore the `_meta.outputSchema` forward, so the
|
|
251
|
+
// schema must be STATED, not just wired — otherwise the model invents its own keys and
|
|
252
|
+
// the repair ladder can never converge. Built-ins skip this: their native constraint
|
|
253
|
+
// channel is authoritative.
|
|
254
|
+
contract.push(`- The required output schema (JSON Schema):\n${JSON.stringify(toJsonSchema(schema))}`);
|
|
255
|
+
}
|
|
256
|
+
parts.push(contract.join("\n"));
|
|
235
257
|
}
|
|
236
258
|
return parts.join("\n\n");
|
|
237
259
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@agentclientprotocol/claude-agent-acp": "0.53.0",
|
|
29
29
|
"@automatalabs/codex-acp": "1.2.0",
|
|
30
30
|
"typebox": "1.3.2",
|
|
31
|
-
"@automatalabs/shared-types": "0.
|
|
31
|
+
"@automatalabs/shared-types": "0.5.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc -b",
|