@agentconnect.md/daemon 1.0.0-rc.56 → 1.0.0-rc.57
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/index.js +1121 -1089
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
12
12
|
import { cpus, freemem, homedir, hostname, tmpdir, totalmem, type } from "node:os";
|
|
13
13
|
import { spawn as spawn$1 } from "child_process";
|
|
14
|
-
import { Readable, Writable } from "node:stream";
|
|
15
14
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
15
|
+
import { Readable, Writable } from "node:stream";
|
|
16
16
|
import { lstat, open, readdir, realpath, stat as stat$1 } from "node:fs/promises";
|
|
17
17
|
import { DatabaseSync } from "node:sqlite";
|
|
18
18
|
import net from "node:net";
|
|
@@ -12788,437 +12788,1194 @@ function gitInstanceFactory(baseDir, options) {
|
|
|
12788
12788
|
init_git_response_error();
|
|
12789
12789
|
var simpleGit = gitInstanceFactory;
|
|
12790
12790
|
//#endregion
|
|
12791
|
-
//#region
|
|
12792
|
-
const PULL_TIMEOUT_MS = 4500;
|
|
12793
|
-
const cloneInFlight = /* @__PURE__ */ new Map();
|
|
12794
|
-
async function prepareWorkspace(agent) {
|
|
12795
|
-
const cwd = agent.workspace.path;
|
|
12796
|
-
mkdirSync(cwd, { recursive: true });
|
|
12797
|
-
if (agent.workspace.mode === "from-scratch") {
|
|
12798
|
-
const mem = join(cwd, "memory.md");
|
|
12799
|
-
if (!existsSync(mem)) writeFileSync(mem, `# ${agent.name} memory\n`);
|
|
12800
|
-
return cwd;
|
|
12801
|
-
}
|
|
12802
|
-
if (!existsSync(join(cwd, ".git"))) {
|
|
12803
|
-
await cloneRepo(agent);
|
|
12804
|
-
return cwd;
|
|
12805
|
-
}
|
|
12806
|
-
if (agent.workspace.pullOnNewSession) try {
|
|
12807
|
-
await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS))]);
|
|
12808
|
-
} catch {}
|
|
12809
|
-
return cwd;
|
|
12810
|
-
}
|
|
12791
|
+
//#region ../protocol/dist/envelope.js
|
|
12811
12792
|
/**
|
|
12812
|
-
*
|
|
12813
|
-
*
|
|
12814
|
-
*
|
|
12815
|
-
*
|
|
12816
|
-
*
|
|
12817
|
-
*
|
|
12818
|
-
*
|
|
12819
|
-
* on the first session as the backstop.
|
|
12793
|
+
* The protocol envelope — every frame, both directions, is wrapped in this.
|
|
12794
|
+
* Mirrors daemon-cp-ws-protocol.md §1.1.
|
|
12795
|
+
*
|
|
12796
|
+
* `payload` is left as `unknown` here and validated by the per-type schema in
|
|
12797
|
+
* `frames/*` via `FRAME_SCHEMAS[type]` (see `frame.ts`). This two-step parse is
|
|
12798
|
+
* what lets the codec answer an unknown `type` with `UNKNOWN_FRAME` (a REP)
|
|
12799
|
+
* instead of a hard close — forward-compat, protocol §1.
|
|
12820
12800
|
*/
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
await simpleGit().clone(gitRepo, cwd, [
|
|
12838
|
-
"--branch",
|
|
12839
|
-
branch,
|
|
12840
|
-
"--single-branch"
|
|
12841
|
-
]);
|
|
12842
|
-
})().finally(() => {
|
|
12843
|
-
cloneInFlight.delete(cwd);
|
|
12844
|
-
});
|
|
12845
|
-
cloneInFlight.set(cwd, p);
|
|
12846
|
-
return p;
|
|
12847
|
-
}
|
|
12848
|
-
//#endregion
|
|
12849
|
-
//#region ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.0.0_zod@4.4.3/node_modules/@agentclientprotocol/sdk/dist/schema/index.js
|
|
12850
|
-
const AGENT_METHODS = {
|
|
12851
|
-
initialize: "initialize",
|
|
12852
|
-
authenticate: "authenticate",
|
|
12853
|
-
providers_list: "providers/list",
|
|
12854
|
-
providers_set: "providers/set",
|
|
12855
|
-
providers_disable: "providers/disable",
|
|
12856
|
-
session_new: "session/new",
|
|
12857
|
-
session_load: "session/load",
|
|
12858
|
-
session_set_mode: "session/set_mode",
|
|
12859
|
-
session_set_config_option: "session/set_config_option",
|
|
12860
|
-
session_prompt: "session/prompt",
|
|
12861
|
-
session_cancel: "session/cancel",
|
|
12862
|
-
mcp_message: "mcp/message",
|
|
12863
|
-
session_list: "session/list",
|
|
12864
|
-
session_delete: "session/delete",
|
|
12865
|
-
session_fork: "session/fork",
|
|
12866
|
-
session_resume: "session/resume",
|
|
12867
|
-
session_close: "session/close",
|
|
12868
|
-
logout: "logout",
|
|
12869
|
-
nes_start: "nes/start",
|
|
12870
|
-
nes_suggest: "nes/suggest",
|
|
12871
|
-
nes_accept: "nes/accept",
|
|
12872
|
-
nes_reject: "nes/reject",
|
|
12873
|
-
nes_close: "nes/close",
|
|
12874
|
-
document_did_open: "document/didOpen",
|
|
12875
|
-
document_did_change: "document/didChange",
|
|
12876
|
-
document_did_close: "document/didClose",
|
|
12877
|
-
document_did_save: "document/didSave",
|
|
12878
|
-
document_did_focus: "document/didFocus"
|
|
12879
|
-
};
|
|
12880
|
-
const CLIENT_METHODS = {
|
|
12881
|
-
session_request_permission: "session/request_permission",
|
|
12882
|
-
session_update: "session/update",
|
|
12883
|
-
fs_write_text_file: "fs/write_text_file",
|
|
12884
|
-
fs_read_text_file: "fs/read_text_file",
|
|
12885
|
-
terminal_create: "terminal/create",
|
|
12886
|
-
terminal_output: "terminal/output",
|
|
12887
|
-
terminal_release: "terminal/release",
|
|
12888
|
-
terminal_wait_for_exit: "terminal/wait_for_exit",
|
|
12889
|
-
terminal_kill: "terminal/kill",
|
|
12890
|
-
mcp_connect: "mcp/connect",
|
|
12891
|
-
mcp_message: "mcp/message",
|
|
12892
|
-
mcp_disconnect: "mcp/disconnect",
|
|
12893
|
-
elicitation_create: "elicitation/create",
|
|
12894
|
-
elicitation_complete: "elicitation/complete"
|
|
12895
|
-
};
|
|
12896
|
-
const PROTOCOL_METHODS = { cancel_request: "$/cancel_request" };
|
|
12897
|
-
//#endregion
|
|
12898
|
-
//#region ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.0.0_zod@4.4.3/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js
|
|
12899
|
-
const skippedItem = Symbol("skippedItem");
|
|
12900
|
-
function defaultOnError(schema, fallback) {
|
|
12901
|
-
return schema.catch(fallback);
|
|
12902
|
-
}
|
|
12903
|
-
function requiredDefaultOnError(schema, fallback) {
|
|
12904
|
-
const schemaWithCatch = schema.catch(fallback);
|
|
12905
|
-
return unknown().transform((value, context) => {
|
|
12906
|
-
if (value !== void 0) return schemaWithCatch.parse(value);
|
|
12907
|
-
context.addIssue({
|
|
12908
|
-
code: "custom",
|
|
12909
|
-
message: "Required value is missing"
|
|
12910
|
-
});
|
|
12911
|
-
return NEVER;
|
|
12912
|
-
});
|
|
12913
|
-
}
|
|
12914
|
-
function vecSkipError(itemSchema) {
|
|
12915
|
-
return array(itemSchema.catch(skippedItem)).transform((items) => items.filter((item) => item !== skippedItem));
|
|
12916
|
-
}
|
|
12801
|
+
const Envelope = object({
|
|
12802
|
+
v: literal(1),
|
|
12803
|
+
id: string().uuid(),
|
|
12804
|
+
ts: string().datetime(),
|
|
12805
|
+
type: string(),
|
|
12806
|
+
corr: string().uuid().optional(),
|
|
12807
|
+
payload: unknown()
|
|
12808
|
+
});
|
|
12809
|
+
object({
|
|
12810
|
+
epoch: number().int(),
|
|
12811
|
+
seq: number().int().optional(),
|
|
12812
|
+
agentId: string().uuid().optional(),
|
|
12813
|
+
launchId: string().uuid().optional()
|
|
12814
|
+
});
|
|
12815
|
+
/** The all-zero UUID used when a frame is malformed past the point of reading `id`. */
|
|
12816
|
+
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
12917
12817
|
//#endregion
|
|
12918
|
-
//#region
|
|
12818
|
+
//#region ../protocol/dist/frames/auth.js
|
|
12919
12819
|
/**
|
|
12920
|
-
*
|
|
12921
|
-
*
|
|
12922
|
-
* An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. The value SHOULD normally not be Null \[1\] and Numbers SHOULD NOT contain fractional parts \[2\]
|
|
12923
|
-
*
|
|
12924
|
-
* The Server MUST reply with the same value in the Response object if included. This member is used to correlate the context between the two objects.
|
|
12925
|
-
*
|
|
12926
|
-
* \[1\] The use of Null as a value for the id member in a Request object is discouraged, because this specification uses a value of Null for Responses with an unknown id. Also, because JSON-RPC 1.0 uses an id value of Null for Notifications this could cause confusion in handling.
|
|
12820
|
+
* Auth & identity — protocol §3.1 / §3.2.
|
|
12927
12821
|
*
|
|
12928
|
-
*
|
|
12822
|
+
* `auth` is the first frame after the socket opens. `auth/ok` carries the
|
|
12823
|
+
* minted `sessionEpoch` (the global fencing token), heartbeat cadence, and the
|
|
12824
|
+
* resume verdict.
|
|
12929
12825
|
*/
|
|
12930
|
-
const
|
|
12826
|
+
const AuthReq = object({
|
|
12827
|
+
apiKey: string(),
|
|
12828
|
+
daemonId: string().uuid().optional(),
|
|
12829
|
+
machineId: string().uuid().optional(),
|
|
12830
|
+
attestation: string().optional(),
|
|
12831
|
+
agentVersion: string(),
|
|
12832
|
+
resume: object({
|
|
12833
|
+
lastEpoch: number().int(),
|
|
12834
|
+
lastRecvSeq: record(string(), number())
|
|
12835
|
+
}).optional()
|
|
12836
|
+
});
|
|
12837
|
+
const AuthOk = object({
|
|
12838
|
+
daemonId: string().uuid(),
|
|
12839
|
+
sessionEpoch: number().int(),
|
|
12840
|
+
heartbeatSec: number().int(),
|
|
12841
|
+
serverTime: string().datetime(),
|
|
12842
|
+
resume: object({
|
|
12843
|
+
accepted: boolean(),
|
|
12844
|
+
redeliverFromSeq: record(string(), number()).optional()
|
|
12845
|
+
}).optional()
|
|
12846
|
+
});
|
|
12847
|
+
//#endregion
|
|
12848
|
+
//#region ../protocol/dist/frames/route.js
|
|
12931
12849
|
/**
|
|
12932
|
-
*
|
|
12933
|
-
*
|
|
12934
|
-
* Sessions maintain their own context, conversation history, and state,
|
|
12935
|
-
* allowing multiple independent interactions with the same agent.
|
|
12850
|
+
* Routing & orchestration (C→D control) — protocol §5.
|
|
12936
12851
|
*
|
|
12937
|
-
*
|
|
12852
|
+
* `SessionKey` is the canonical session primitive shared across route/*,
|
|
12853
|
+
* agent/*, and event/session. Its canonical string form is
|
|
12854
|
+
* `${platform}:${channel}:${thread ?? "-"}`.
|
|
12938
12855
|
*/
|
|
12939
|
-
const
|
|
12856
|
+
const Platform = _enum(["slack", "telegram"]);
|
|
12857
|
+
const SessionKey = object({
|
|
12858
|
+
platform: Platform,
|
|
12859
|
+
channel: string(),
|
|
12860
|
+
thread: string().optional()
|
|
12861
|
+
});
|
|
12862
|
+
/** Trigger-matching rule for a binding (protocol §5.1). */
|
|
12863
|
+
const BindRule = object({ match: discriminatedUnion("kind", [
|
|
12864
|
+
object({ kind: literal("mention") }),
|
|
12865
|
+
object({ kind: literal("dm") }),
|
|
12866
|
+
object({
|
|
12867
|
+
kind: literal("keyword"),
|
|
12868
|
+
value: string()
|
|
12869
|
+
}),
|
|
12870
|
+
object({ kind: literal("auto") })
|
|
12871
|
+
]) });
|
|
12872
|
+
const RouteAssign = object({
|
|
12873
|
+
sessionKey: SessionKey,
|
|
12874
|
+
agentId: string().uuid(),
|
|
12875
|
+
workspaceId: string().uuid(),
|
|
12876
|
+
bindRules: array(BindRule).default([])
|
|
12877
|
+
});
|
|
12878
|
+
const RouteAssignAck = object({
|
|
12879
|
+
ok: boolean(),
|
|
12880
|
+
sessionKey: SessionKey,
|
|
12881
|
+
reason: string().optional()
|
|
12882
|
+
});
|
|
12883
|
+
const RouteUpdate = object({
|
|
12884
|
+
routingEpoch: number().int(),
|
|
12885
|
+
rules: array(object({
|
|
12886
|
+
match: unknown(),
|
|
12887
|
+
agentId: string().uuid()
|
|
12888
|
+
}))
|
|
12889
|
+
});
|
|
12890
|
+
/** Graceful scale-down / rebalance — protocol §5.3. */
|
|
12891
|
+
const Drain = object({
|
|
12892
|
+
scope: union([
|
|
12893
|
+
object({
|
|
12894
|
+
kind: literal("agent"),
|
|
12895
|
+
agentId: string().uuid()
|
|
12896
|
+
}),
|
|
12897
|
+
object({ kind: literal("daemon") }),
|
|
12898
|
+
object({
|
|
12899
|
+
kind: literal("session"),
|
|
12900
|
+
sessionKey: SessionKey
|
|
12901
|
+
})
|
|
12902
|
+
]),
|
|
12903
|
+
deadline: string().datetime()
|
|
12904
|
+
});
|
|
12905
|
+
const DrainProgress = object({
|
|
12906
|
+
remaining: number().int(),
|
|
12907
|
+
drained: array(SessionKey)
|
|
12908
|
+
});
|
|
12909
|
+
const DrainDone = object({ released: array(SessionKey) });
|
|
12910
|
+
//#endregion
|
|
12911
|
+
//#region ../protocol/dist/frames/cron.js
|
|
12940
12912
|
/**
|
|
12941
|
-
*
|
|
12913
|
+
* Cron sinks to the daemon (D5) — protocol §5.4.
|
|
12942
12914
|
*
|
|
12943
|
-
*
|
|
12915
|
+
* The CP owns the definition; the daemon owns firing + last-run persistence so
|
|
12916
|
+
* crons fire even when the CP is down.
|
|
12944
12917
|
*/
|
|
12945
|
-
const
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12918
|
+
const CronUpsert = object({
|
|
12919
|
+
cronId: string().uuid(),
|
|
12920
|
+
schedule: string(),
|
|
12921
|
+
target: object({ channel: string() }),
|
|
12922
|
+
trigger: string(),
|
|
12923
|
+
enabled: boolean().default(true)
|
|
12950
12924
|
});
|
|
12925
|
+
const CronRemove = object({ cronId: string().uuid() });
|
|
12926
|
+
//#endregion
|
|
12927
|
+
//#region ../protocol/dist/frames/secrets.js
|
|
12951
12928
|
/**
|
|
12952
|
-
*
|
|
12929
|
+
* Secrets (C5 ↔ D10) — protocol §6.
|
|
12953
12930
|
*
|
|
12954
|
-
*
|
|
12931
|
+
* Lease-based, no plaintext on the wire or in PG. Every frame carries a
|
|
12932
|
+
* REFERENCE to a Vault/KMS path, never the secret material itself.
|
|
12955
12933
|
*/
|
|
12956
|
-
const
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12934
|
+
const SecretsRequest = object({ scope: object({
|
|
12935
|
+
platform: Platform,
|
|
12936
|
+
workspaceId: string().uuid()
|
|
12937
|
+
}) });
|
|
12938
|
+
const SecretsGrant = object({
|
|
12939
|
+
leaseId: string().uuid(),
|
|
12940
|
+
scope: object({
|
|
12941
|
+
platform: string(),
|
|
12942
|
+
workspaceId: string().uuid()
|
|
12943
|
+
}),
|
|
12944
|
+
ref: string(),
|
|
12945
|
+
ttl: number().int(),
|
|
12946
|
+
renewBeforeSec: number().int()
|
|
12947
|
+
});
|
|
12948
|
+
const SecretsRenew = object({ leaseId: string().uuid() });
|
|
12949
|
+
const SecretsRevoke = object({
|
|
12950
|
+
leaseId: string().uuid(),
|
|
12951
|
+
reason: string()
|
|
12952
|
+
});
|
|
12953
|
+
/** 🅼 Direct-to-store upload/download grant — protocol §3.2 / frame #25. */
|
|
12954
|
+
const ScopeAttestation = object({
|
|
12955
|
+
machineId: string().uuid(),
|
|
12956
|
+
scope: _enum([
|
|
12957
|
+
"attachment.put",
|
|
12958
|
+
"attachment.get",
|
|
12959
|
+
"facts.put"
|
|
12960
|
+
]),
|
|
12961
|
+
resourceRef: string(),
|
|
12962
|
+
jws: string(),
|
|
12963
|
+
exp: string().datetime()
|
|
12962
12964
|
});
|
|
12965
|
+
//#endregion
|
|
12966
|
+
//#region ../protocol/dist/frames/agent.js
|
|
12963
12967
|
/**
|
|
12964
|
-
*
|
|
12968
|
+
* Agent lifecycle + delivery (protocol §4.3, §4.4, §7.4, §8).
|
|
12969
|
+
*
|
|
12970
|
+
* Body-locality invariant (protocol §12): no frame here carries
|
|
12971
|
+
* `NormalizedMessage.text`. `agent/prompt` ships a `NormalizedMessageRef` — a
|
|
12972
|
+
* reference the daemon resolves against its local body store.
|
|
12965
12973
|
*/
|
|
12966
|
-
const zToolCallId = string();
|
|
12967
12974
|
/**
|
|
12968
|
-
*
|
|
12969
|
-
*
|
|
12970
|
-
* Tool kinds help clients choose appropriate icons and optimize how they
|
|
12971
|
-
* display tool execution progress.
|
|
12972
|
-
*
|
|
12973
|
-
* See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-calls#creating)
|
|
12975
|
+
* A REFERENCE/digest of a normalized message, NOT the body (protocol §4.3).
|
|
12976
|
+
* Enough for D4/D6 to fetch the local body and prompt.
|
|
12974
12977
|
*/
|
|
12975
|
-
const
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
literal("search"),
|
|
12981
|
-
literal("execute"),
|
|
12982
|
-
literal("think"),
|
|
12983
|
-
literal("fetch"),
|
|
12984
|
-
literal("switch_mode"),
|
|
12985
|
-
literal("other")
|
|
12986
|
-
]);
|
|
12978
|
+
const NormalizedMessageRef = object({
|
|
12979
|
+
sessionKey: SessionKey,
|
|
12980
|
+
platformMsgId: string(),
|
|
12981
|
+
seenUpToSeq: number().int()
|
|
12982
|
+
});
|
|
12987
12983
|
/**
|
|
12988
|
-
*
|
|
12989
|
-
*
|
|
12990
|
-
* Tool calls progress through different statuses during their lifecycle.
|
|
12984
|
+
* Where the agent runs. Two modes; the **path is always daemon-generated** —
|
|
12985
|
+
* never specified by the caller (UX picks the mode, the machine owns the dir).
|
|
12991
12986
|
*
|
|
12992
|
-
*
|
|
12987
|
+
* - `scratch`: a fresh empty working dir on the machine, no repo.
|
|
12988
|
+
* - `github`: the daemon clones `gitRepo` @ `branch` and runs the agent in
|
|
12989
|
+
* `agentDir` (a subdir of the repo, repo-root if omitted). **Multiple agents
|
|
12990
|
+
* may share one repo** — they differ by `agentDir`, so the repo is not an
|
|
12991
|
+
* owned entity, just shared config on each agent.
|
|
12993
12992
|
*/
|
|
12994
|
-
const
|
|
12995
|
-
literal("
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
]);
|
|
12993
|
+
const AgentWorkspace = discriminatedUnion("mode", [object({ mode: literal("scratch") }), object({
|
|
12994
|
+
mode: literal("github"),
|
|
12995
|
+
gitRepo: string(),
|
|
12996
|
+
branch: string().default("main"),
|
|
12997
|
+
agentDir: string().optional()
|
|
12998
|
+
})]);
|
|
13000
12999
|
/**
|
|
13001
|
-
*
|
|
13000
|
+
* The editable agent definition the CP owns and the daemon needs to run it:
|
|
13001
|
+
* prompt + runtime selection. Raft ships this in the launch config and the
|
|
13002
|
+
* daemon synthesizes the system prompt locally; `description` IS the prompt.
|
|
13002
13003
|
*/
|
|
13003
|
-
const
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13004
|
+
const AgentSpec = object({
|
|
13005
|
+
name: string(),
|
|
13006
|
+
description: string().optional(),
|
|
13007
|
+
runtime: string().optional(),
|
|
13008
|
+
model: string().optional(),
|
|
13009
|
+
reasoningEffort: string().optional(),
|
|
13010
|
+
executionMode: string().optional(),
|
|
13011
|
+
workspace: AgentWorkspace.optional(),
|
|
13012
|
+
env: record(string(), string()).optional()
|
|
13008
13013
|
});
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13014
|
+
const AgentLaunch = object({
|
|
13015
|
+
agentId: string().uuid(),
|
|
13016
|
+
runtime: string(),
|
|
13017
|
+
workspaceId: string().uuid(),
|
|
13018
|
+
capabilities: array(string()),
|
|
13019
|
+
spec: AgentSpec,
|
|
13020
|
+
mode: _enum(["long_lived", "per_turn"]).default("long_lived")
|
|
13016
13021
|
});
|
|
13017
13022
|
/**
|
|
13018
|
-
*
|
|
13023
|
+
* Live agent CRUD (C→D): the console edited an agent's spec; push it so a
|
|
13024
|
+
* running daemon reloads without waiting for the next launch. `agent/remove`
|
|
13025
|
+
* tears the agent down. Deleting an agent never relaunches it.
|
|
13019
13026
|
*/
|
|
13020
|
-
const
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
mimeType: string(),
|
|
13024
|
-
uri: string().nullish(),
|
|
13025
|
-
_meta: record(string(), unknown()).nullish()
|
|
13027
|
+
const AgentUpsert = object({
|
|
13028
|
+
agentId: string().uuid(),
|
|
13029
|
+
spec: AgentSpec
|
|
13026
13030
|
});
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
_meta: record(string(), unknown()).nullish()
|
|
13031
|
+
const AgentRemove = object({ agentId: string().uuid() });
|
|
13032
|
+
const AgentLaunched = object({
|
|
13033
|
+
agentId: string().uuid(),
|
|
13034
|
+
launchId: string().uuid(),
|
|
13035
|
+
acpSessionId: string().optional(),
|
|
13036
|
+
startedAt: string().datetime(),
|
|
13037
|
+
runtime: string()
|
|
13035
13038
|
});
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13041
|
-
description: string().nullish(),
|
|
13042
|
-
mimeType: string().nullish(),
|
|
13043
|
-
name: string(),
|
|
13044
|
-
size: number().nullish(),
|
|
13045
|
-
title: string().nullish(),
|
|
13046
|
-
uri: string(),
|
|
13047
|
-
_meta: record(string(), unknown()).nullish()
|
|
13039
|
+
const AgentStop = object({
|
|
13040
|
+
agentId: string().uuid(),
|
|
13041
|
+
launchId: string().uuid(),
|
|
13042
|
+
reason: string()
|
|
13048
13043
|
});
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
const
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13044
|
+
const AgentPrompt = object({
|
|
13045
|
+
sessionKey: SessionKey,
|
|
13046
|
+
agentId: string().uuid(),
|
|
13047
|
+
content: NormalizedMessageRef,
|
|
13048
|
+
seenUpToSeq: number().int()
|
|
13049
|
+
});
|
|
13050
|
+
const AgentPromptAck = object({
|
|
13051
|
+
accepted: boolean(),
|
|
13052
|
+
reason: _enum([
|
|
13053
|
+
"queued",
|
|
13054
|
+
"held",
|
|
13055
|
+
"scope_denied",
|
|
13056
|
+
"no_session",
|
|
13057
|
+
"stale"
|
|
13058
|
+
]).optional(),
|
|
13059
|
+
seq: number().int()
|
|
13060
|
+
});
|
|
13061
|
+
const AgentActivity = object({
|
|
13062
|
+
agentId: string().uuid(),
|
|
13063
|
+
launchId: string().uuid(),
|
|
13064
|
+
state: _enum([
|
|
13065
|
+
"thinking",
|
|
13066
|
+
"tool_call",
|
|
13067
|
+
"awaiting_permission",
|
|
13068
|
+
"idle"
|
|
13069
|
+
]),
|
|
13070
|
+
ts: string().datetime()
|
|
13071
|
+
});
|
|
13072
|
+
const AgentScopeDenied = object({
|
|
13073
|
+
agentId: string().uuid(),
|
|
13074
|
+
launchId: string().uuid(),
|
|
13075
|
+
capability: string()
|
|
13070
13076
|
});
|
|
13077
|
+
//#endregion
|
|
13078
|
+
//#region ../protocol/dist/frames/integration.js
|
|
13071
13079
|
/**
|
|
13072
|
-
*
|
|
13080
|
+
* Platform integration distribution (C→D) — the Slack "install" flow.
|
|
13073
13081
|
*
|
|
13074
|
-
*
|
|
13075
|
-
*
|
|
13082
|
+
* The Control Plane is the source of truth for platform integrations and pushes
|
|
13083
|
+
* them to the daemon that owns the integration's agent (`integration/upsert`, and
|
|
13084
|
+
* the reconcile snapshot `RegisterOk.integrations[]`). The daemon opens the Socket
|
|
13085
|
+
* Mode connection from the delivered config (see slack/connection.ts).
|
|
13076
13086
|
*
|
|
13077
|
-
*
|
|
13078
|
-
*
|
|
13079
|
-
*
|
|
13080
|
-
*
|
|
13081
|
-
*
|
|
13082
|
-
*
|
|
13083
|
-
* agents to seamlessly forward content from MCP tool outputs without transformation.
|
|
13087
|
+
* SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
|
|
13088
|
+
* platform tokens (botToken/appToken). These payloads MUST NEVER be logged — no
|
|
13089
|
+
* body dump on decode error, no register/ok snapshot debug dump. The daemon
|
|
13090
|
+
* persists them into the owning agent's local `agent.json` (same trust boundary
|
|
13091
|
+
* as hand-authored agents, which already keep tokens there) so integrations
|
|
13092
|
+
* survive a restart with the CP down.
|
|
13084
13093
|
*
|
|
13085
|
-
*
|
|
13094
|
+
* `signingSecret` is intentionally absent: Socket Mode authenticates with the
|
|
13095
|
+
* app-level token, so the daemon never needs a signing secret.
|
|
13086
13096
|
*/
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13097
|
+
/** Trigger match — mirrors the daemon BindRuleConfig.match (agents/agent-schema.ts). */
|
|
13098
|
+
const BindMatch = discriminatedUnion("kind", [
|
|
13099
|
+
object({ kind: literal("mention") }),
|
|
13100
|
+
object({ kind: literal("dm") }),
|
|
13101
|
+
object({
|
|
13102
|
+
kind: literal("keyword"),
|
|
13103
|
+
value: string()
|
|
13104
|
+
}),
|
|
13105
|
+
object({ kind: literal("auto") })
|
|
13093
13106
|
]);
|
|
13094
|
-
/**
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
_meta: record(string(), unknown()).nullish()
|
|
13107
|
+
/** One channel/thread trigger binding — mirrors the daemon BindRuleConfig. */
|
|
13108
|
+
const IntegrationBindRule = object({
|
|
13109
|
+
channel: string().optional(),
|
|
13110
|
+
thread: string().optional(),
|
|
13111
|
+
match: BindMatch
|
|
13100
13112
|
});
|
|
13101
|
-
/**
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
const zDiff = object({
|
|
13109
|
-
path: string(),
|
|
13110
|
-
oldText: string().nullish(),
|
|
13111
|
-
newText: string(),
|
|
13112
|
-
_meta: record(string(), unknown()).nullish()
|
|
13113
|
+
/** The Slack config the daemon needs to open + route a socket (no signingSecret). */
|
|
13114
|
+
const IntegrationSlackConfig = object({
|
|
13115
|
+
botToken: string(),
|
|
13116
|
+
appToken: string(),
|
|
13117
|
+
allowedUserIds: array(string()).default([]),
|
|
13118
|
+
notificationChannelId: string().optional(),
|
|
13119
|
+
bindRules: array(IntegrationBindRule).default([])
|
|
13113
13120
|
});
|
|
13114
13121
|
/**
|
|
13115
|
-
*
|
|
13122
|
+
* One platform integration, owned by exactly one agent. Also the element type of
|
|
13123
|
+
* `RegisterOk.integrations[]` (the per-daemon reconcile set).
|
|
13116
13124
|
*/
|
|
13117
|
-
const
|
|
13125
|
+
const IntegrationSpec = object({
|
|
13126
|
+
integrationId: string().uuid(),
|
|
13127
|
+
agentId: string().uuid(),
|
|
13128
|
+
platform: literal("slack"),
|
|
13129
|
+
slack: IntegrationSlackConfig
|
|
13130
|
+
});
|
|
13131
|
+
/** C→D EVT — install/update an integration on the owning agent's daemon. */
|
|
13132
|
+
const IntegrationUpsert = IntegrationSpec;
|
|
13133
|
+
/** C→D EVT — remove an integration from the daemon. */
|
|
13134
|
+
const IntegrationRemove = object({ integrationId: string().uuid() });
|
|
13135
|
+
//#endregion
|
|
13136
|
+
//#region ../protocol/dist/frames/register.js
|
|
13118
13137
|
/**
|
|
13119
|
-
*
|
|
13120
|
-
*
|
|
13121
|
-
* The terminal must be added before calling `terminal/release`.
|
|
13138
|
+
* Capability upload + the reconcile snapshot — protocol §3.3.
|
|
13122
13139
|
*
|
|
13123
|
-
*
|
|
13140
|
+
* `register/ok` is the authoritative source of truth: the daemon converges its
|
|
13141
|
+
* local cache to it. CP wins all conflicts, so re-issuing the same snapshot is
|
|
13142
|
+
* idempotent.
|
|
13124
13143
|
*/
|
|
13125
|
-
const
|
|
13126
|
-
|
|
13127
|
-
|
|
13144
|
+
const RegisterReq = object({
|
|
13145
|
+
host: string(),
|
|
13146
|
+
capabilities: object({
|
|
13147
|
+
platforms: array(Platform),
|
|
13148
|
+
runtimes: array(string()),
|
|
13149
|
+
acp: boolean(),
|
|
13150
|
+
features: array(string()).default([])
|
|
13151
|
+
}),
|
|
13152
|
+
maxAgents: number().int(),
|
|
13153
|
+
localState: object({
|
|
13154
|
+
assignments: array(string()),
|
|
13155
|
+
crons: array(string()),
|
|
13156
|
+
leases: array(string())
|
|
13157
|
+
})
|
|
13158
|
+
});
|
|
13159
|
+
const RegisterOk = object({
|
|
13160
|
+
routingEpoch: number().int(),
|
|
13161
|
+
assignments: array(RouteAssign),
|
|
13162
|
+
agents: array(AgentSpec.extend({ agentId: string().uuid() })).default([]),
|
|
13163
|
+
crons: array(CronUpsert),
|
|
13164
|
+
integrations: array(IntegrationSpec).default([]),
|
|
13165
|
+
leases: array(SecretsGrant),
|
|
13166
|
+
drop: object({
|
|
13167
|
+
assignments: array(string()),
|
|
13168
|
+
crons: array(string())
|
|
13169
|
+
})
|
|
13128
13170
|
});
|
|
13171
|
+
//#endregion
|
|
13172
|
+
//#region ../protocol/dist/frames/session.js
|
|
13129
13173
|
/**
|
|
13130
|
-
*
|
|
13174
|
+
* Session read-back (C→D REQ → REP) — the console's on-demand pulls.
|
|
13131
13175
|
*
|
|
13132
|
-
*
|
|
13133
|
-
*
|
|
13176
|
+
* The CP stores NO session data — neither the list nor the bodies. Sessions are
|
|
13177
|
+
* created on the Slack→daemon path and live solely in the daemon's local store
|
|
13178
|
+
* (body-locality, §1/§12). So both the session **list** and a session's chat
|
|
13179
|
+
* **history** are pulled live from the owning daemon(s) and proxied to the
|
|
13180
|
+
* console — never persisted on the CP, never on the orchestration hot path.
|
|
13134
13181
|
*
|
|
13135
|
-
*
|
|
13182
|
+
* - `session/list`: the daemon's live sessions (CP fans this out to all the org's
|
|
13183
|
+
* online daemons and merges, for "all sessions across the workspace").
|
|
13184
|
+
* - `session/history`: one cursor-paginated page of a session's transcript.
|
|
13136
13185
|
*/
|
|
13137
|
-
const zToolCallContent = union([
|
|
13138
|
-
zContent.and(object({ type: literal("content") })),
|
|
13139
|
-
zDiff.and(object({ type: literal("diff") })),
|
|
13140
|
-
zTerminal.and(object({ type: literal("terminal") }))
|
|
13141
|
-
]);
|
|
13142
13186
|
/**
|
|
13143
|
-
*
|
|
13144
|
-
*
|
|
13145
|
-
*
|
|
13146
|
-
*
|
|
13147
|
-
*
|
|
13148
|
-
* See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent)
|
|
13187
|
+
* Per-session token accounting, metered by the daemon from the agent's ACP usage
|
|
13188
|
+
* stream. Token counts are CUMULATIVE across the session's turns (summed from each
|
|
13189
|
+
* turn's `PromptResponse.usage`); context/cost are the LATEST snapshot from the
|
|
13190
|
+
* agent's `usage_update` stream. Every field is optional — a runtime that reports
|
|
13191
|
+
* no usage yields an absent field, not a zero.
|
|
13149
13192
|
*/
|
|
13150
|
-
const
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
|
|
13193
|
+
const SessionUsage = object({
|
|
13194
|
+
totalTokens: number().int().optional(),
|
|
13195
|
+
inputTokens: number().int().optional(),
|
|
13196
|
+
outputTokens: number().int().optional(),
|
|
13197
|
+
thoughtTokens: number().int().optional(),
|
|
13198
|
+
cachedReadTokens: number().int().optional(),
|
|
13199
|
+
cachedWriteTokens: number().int().optional(),
|
|
13200
|
+
contextUsed: number().int().optional(),
|
|
13201
|
+
contextSize: number().int().optional(),
|
|
13202
|
+
costAmount: number().optional(),
|
|
13203
|
+
costCurrency: string().optional()
|
|
13204
|
+
});
|
|
13205
|
+
/** One row in the session list (metadata + console metrics; NOT the transcript). */
|
|
13206
|
+
const SessionListItem = object({
|
|
13207
|
+
sessionId: string(),
|
|
13208
|
+
sessionKey: SessionKey,
|
|
13209
|
+
agentId: string().uuid(),
|
|
13210
|
+
title: string().optional(),
|
|
13211
|
+
status: string().optional(),
|
|
13212
|
+
lastActivityAt: string().optional(),
|
|
13213
|
+
usage: SessionUsage.optional(),
|
|
13214
|
+
triggeredBy: string().optional()
|
|
13215
|
+
});
|
|
13216
|
+
/** C→D REQ: list the daemon's sessions (optionally just one agent's). */
|
|
13217
|
+
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
13218
|
+
/** D→C REP (corr = req id): the daemon's current sessions. */
|
|
13219
|
+
const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
13220
|
+
/** One message in a session transcript page (a body — returned only for display). */
|
|
13221
|
+
const SessionMessage = object({
|
|
13222
|
+
seq: number().int(),
|
|
13223
|
+
sender: string(),
|
|
13224
|
+
ts: string(),
|
|
13225
|
+
kind: string(),
|
|
13226
|
+
text: string()
|
|
13227
|
+
});
|
|
13228
|
+
/** C→D REQ: fetch one page of a session's history from the owning daemon. */
|
|
13229
|
+
const SessionHistoryReq = object({
|
|
13230
|
+
sessionId: string(),
|
|
13231
|
+
cursor: string().optional(),
|
|
13232
|
+
limit: number().int().positive().max(200).default(50)
|
|
13233
|
+
});
|
|
13234
|
+
/** D→C REP (corr = the req id): a page of messages + the cursor for the next page. */
|
|
13235
|
+
const SessionHistoryPage = object({
|
|
13236
|
+
sessionId: string(),
|
|
13237
|
+
messages: array(SessionMessage),
|
|
13238
|
+
nextCursor: string().optional()
|
|
13154
13239
|
});
|
|
13240
|
+
//#endregion
|
|
13241
|
+
//#region ../protocol/dist/frame.js
|
|
13155
13242
|
/**
|
|
13156
|
-
*
|
|
13243
|
+
* The single source of truth for the wire: `type` string → payload zod schema.
|
|
13157
13244
|
*
|
|
13158
|
-
*
|
|
13159
|
-
*
|
|
13245
|
+
* Mirrors the frame index in daemon-cp-ws-protocol.md §10 (the 29 numbered
|
|
13246
|
+
* frames) plus the correlated REP types named in the "Reply" column
|
|
13247
|
+
* (`route/assign/ack`, `drain/done`, and the generic `ack` replies) that also
|
|
13248
|
+
* travel on the wire and must be decodable.
|
|
13160
13249
|
*
|
|
13161
|
-
*
|
|
13162
|
-
|
|
13163
|
-
const zToolCallUpdate = object({
|
|
13164
|
-
toolCallId: zToolCallId,
|
|
13165
|
-
kind: defaultOnError(zToolKind.nullish(), () => void 0),
|
|
13166
|
-
status: defaultOnError(zToolCallStatus.nullish(), () => void 0),
|
|
13167
|
-
title: string().nullish(),
|
|
13168
|
-
content: defaultOnError(vecSkipError(zToolCallContent).nullish(), () => void 0),
|
|
13169
|
-
locations: defaultOnError(vecSkipError(zToolCallLocation).nullish(), () => void 0),
|
|
13170
|
-
rawInput: unknown().optional(),
|
|
13171
|
-
rawOutput: unknown().optional(),
|
|
13172
|
-
_meta: record(string(), unknown()).nullish()
|
|
13173
|
-
});
|
|
13174
|
-
/**
|
|
13175
|
-
* Unique identifier for a permission option.
|
|
13250
|
+
* `ws/codec.ts` validates every inbound `payload` against `FRAME_SCHEMAS[type]`;
|
|
13251
|
+
* an unknown `type` → `ErrorFrame{code:"UNKNOWN_FRAME"}` (a REP, not a close).
|
|
13176
13252
|
*/
|
|
13177
|
-
const
|
|
13253
|
+
const FRAME_SCHEMAS = {
|
|
13254
|
+
auth: AuthReq,
|
|
13255
|
+
"auth/ok": AuthOk,
|
|
13256
|
+
register: RegisterReq,
|
|
13257
|
+
"register/ok": RegisterOk,
|
|
13258
|
+
heartbeat: object({
|
|
13259
|
+
load: object({
|
|
13260
|
+
cpu: number(),
|
|
13261
|
+
mem: number(),
|
|
13262
|
+
agents: number().int()
|
|
13263
|
+
}),
|
|
13264
|
+
health: _enum(["ok", "degraded"]),
|
|
13265
|
+
activeSessions: number().int(),
|
|
13266
|
+
degradedScopes: array(string()).default([])
|
|
13267
|
+
}),
|
|
13268
|
+
"agent/launch": AgentLaunch,
|
|
13269
|
+
"agent/launched": AgentLaunched,
|
|
13270
|
+
"agent/stop": AgentStop,
|
|
13271
|
+
"agent/upsert": AgentUpsert,
|
|
13272
|
+
"agent/remove": AgentRemove,
|
|
13273
|
+
"agent/prompt": AgentPrompt,
|
|
13274
|
+
"agent/prompt/ack": AgentPromptAck,
|
|
13275
|
+
"agent/activity": AgentActivity,
|
|
13276
|
+
"agent/scope-denied": AgentScopeDenied,
|
|
13277
|
+
"route/assign": RouteAssign,
|
|
13278
|
+
"route/assign/ack": RouteAssignAck,
|
|
13279
|
+
"route/update": RouteUpdate,
|
|
13280
|
+
"daemon/drain": Drain,
|
|
13281
|
+
"drain/progress": DrainProgress,
|
|
13282
|
+
"drain/done": DrainDone,
|
|
13283
|
+
"cron/upsert": CronUpsert,
|
|
13284
|
+
"cron/remove": CronRemove,
|
|
13285
|
+
"integration/upsert": IntegrationUpsert,
|
|
13286
|
+
"integration/remove": IntegrationRemove,
|
|
13287
|
+
"secrets/request": SecretsRequest,
|
|
13288
|
+
"secrets/grant": SecretsGrant,
|
|
13289
|
+
"secrets/renew": SecretsRenew,
|
|
13290
|
+
"secrets/revoke": SecretsRevoke,
|
|
13291
|
+
"scope-attestation": ScopeAttestation,
|
|
13292
|
+
"event/session": object({
|
|
13293
|
+
sessionId: string().uuid(),
|
|
13294
|
+
agentId: string().uuid(),
|
|
13295
|
+
launchId: string().uuid(),
|
|
13296
|
+
phase: _enum([
|
|
13297
|
+
"start",
|
|
13298
|
+
"plan",
|
|
13299
|
+
"problem",
|
|
13300
|
+
"end"
|
|
13301
|
+
]),
|
|
13302
|
+
link: string().optional(),
|
|
13303
|
+
summary: string().optional(),
|
|
13304
|
+
ts: string().datetime()
|
|
13305
|
+
}),
|
|
13306
|
+
"usage/report": object({
|
|
13307
|
+
sessionId: string(),
|
|
13308
|
+
agentId: string().uuid(),
|
|
13309
|
+
platform: string().optional(),
|
|
13310
|
+
channel: string().optional(),
|
|
13311
|
+
lastActivityAt: string(),
|
|
13312
|
+
usage: SessionUsage
|
|
13313
|
+
}),
|
|
13314
|
+
"facts/runtime-profile": object({
|
|
13315
|
+
runtime: string(),
|
|
13316
|
+
version: string(),
|
|
13317
|
+
models: array(string()),
|
|
13318
|
+
contextWindow: number().int().optional(),
|
|
13319
|
+
acpSupport: _enum([
|
|
13320
|
+
"full",
|
|
13321
|
+
"partial",
|
|
13322
|
+
"none"
|
|
13323
|
+
]),
|
|
13324
|
+
toolCalling: boolean()
|
|
13325
|
+
}),
|
|
13326
|
+
"session/list": SessionListReq,
|
|
13327
|
+
"session/list/page": SessionListPage,
|
|
13328
|
+
"session/history": SessionHistoryReq,
|
|
13329
|
+
"session/history/page": SessionHistoryPage,
|
|
13330
|
+
"config/push": object({ keys: record(string(), unknown()) }),
|
|
13331
|
+
"daemon/restart": object({
|
|
13332
|
+
reason: string(),
|
|
13333
|
+
drainFirst: boolean().default(true)
|
|
13334
|
+
}),
|
|
13335
|
+
"daemon/upgrade": object({
|
|
13336
|
+
targetVersion: string(),
|
|
13337
|
+
drainFirst: boolean().default(true)
|
|
13338
|
+
}),
|
|
13339
|
+
"daemon/control/ack": object({
|
|
13340
|
+
accepted: boolean(),
|
|
13341
|
+
willDrainUntil: string().datetime().optional()
|
|
13342
|
+
}),
|
|
13343
|
+
ack: object({
|
|
13344
|
+
ok: boolean(),
|
|
13345
|
+
reason: string().optional()
|
|
13346
|
+
}),
|
|
13347
|
+
error: object({
|
|
13348
|
+
code: _enum([
|
|
13349
|
+
"UNKNOWN_FRAME",
|
|
13350
|
+
"FRAME_TOO_LARGE",
|
|
13351
|
+
"PROTOCOL_STATE",
|
|
13352
|
+
"BAD_PAYLOAD",
|
|
13353
|
+
"AUTH_FAILED",
|
|
13354
|
+
"ATTESTATION_INVALID",
|
|
13355
|
+
"STALE_EPOCH",
|
|
13356
|
+
"STALE_LAUNCH",
|
|
13357
|
+
"SEQ_GAP",
|
|
13358
|
+
"NO_SESSION",
|
|
13359
|
+
"HELD",
|
|
13360
|
+
"SCOPE_DENIED",
|
|
13361
|
+
"LEASE_EXPIRED",
|
|
13362
|
+
"LEASE_DENIED",
|
|
13363
|
+
"RATE_LIMITED",
|
|
13364
|
+
"INTERNAL"
|
|
13365
|
+
]),
|
|
13366
|
+
message: string(),
|
|
13367
|
+
retryable: boolean(),
|
|
13368
|
+
details: record(string(), unknown()).optional()
|
|
13369
|
+
})
|
|
13370
|
+
};
|
|
13371
|
+
Object.keys(FRAME_SCHEMAS);
|
|
13178
13372
|
/**
|
|
13179
|
-
*
|
|
13180
|
-
*
|
|
13181
|
-
* Helps clients choose appropriate icons and UI treatment.
|
|
13373
|
+
* Builds the envelope schema for one frame `type` with a `type` literal and the
|
|
13374
|
+
* typed payload, so the discriminated union infers `payload` precisely.
|
|
13182
13375
|
*/
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13376
|
+
function frame(type, payload) {
|
|
13377
|
+
return object({
|
|
13378
|
+
v: literal(1),
|
|
13379
|
+
id: string().uuid(),
|
|
13380
|
+
ts: string().datetime(),
|
|
13381
|
+
type: literal(type),
|
|
13382
|
+
corr: string().uuid().optional(),
|
|
13383
|
+
payload
|
|
13384
|
+
});
|
|
13385
|
+
}
|
|
13386
|
+
discriminatedUnion("type", [
|
|
13387
|
+
frame("auth", FRAME_SCHEMAS["auth"]),
|
|
13388
|
+
frame("auth/ok", FRAME_SCHEMAS["auth/ok"]),
|
|
13389
|
+
frame("register", FRAME_SCHEMAS["register"]),
|
|
13390
|
+
frame("register/ok", FRAME_SCHEMAS["register/ok"]),
|
|
13391
|
+
frame("heartbeat", FRAME_SCHEMAS["heartbeat"]),
|
|
13392
|
+
frame("agent/launch", FRAME_SCHEMAS["agent/launch"]),
|
|
13393
|
+
frame("agent/launched", FRAME_SCHEMAS["agent/launched"]),
|
|
13394
|
+
frame("agent/stop", FRAME_SCHEMAS["agent/stop"]),
|
|
13395
|
+
frame("agent/upsert", FRAME_SCHEMAS["agent/upsert"]),
|
|
13396
|
+
frame("agent/remove", FRAME_SCHEMAS["agent/remove"]),
|
|
13397
|
+
frame("agent/prompt", FRAME_SCHEMAS["agent/prompt"]),
|
|
13398
|
+
frame("agent/prompt/ack", FRAME_SCHEMAS["agent/prompt/ack"]),
|
|
13399
|
+
frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
|
|
13400
|
+
frame("agent/scope-denied", FRAME_SCHEMAS["agent/scope-denied"]),
|
|
13401
|
+
frame("route/assign", FRAME_SCHEMAS["route/assign"]),
|
|
13402
|
+
frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
|
|
13403
|
+
frame("route/update", FRAME_SCHEMAS["route/update"]),
|
|
13404
|
+
frame("daemon/drain", FRAME_SCHEMAS["daemon/drain"]),
|
|
13405
|
+
frame("drain/progress", FRAME_SCHEMAS["drain/progress"]),
|
|
13406
|
+
frame("drain/done", FRAME_SCHEMAS["drain/done"]),
|
|
13407
|
+
frame("cron/upsert", FRAME_SCHEMAS["cron/upsert"]),
|
|
13408
|
+
frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
|
|
13409
|
+
frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
|
|
13410
|
+
frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
|
|
13411
|
+
frame("secrets/request", FRAME_SCHEMAS["secrets/request"]),
|
|
13412
|
+
frame("secrets/grant", FRAME_SCHEMAS["secrets/grant"]),
|
|
13413
|
+
frame("secrets/renew", FRAME_SCHEMAS["secrets/renew"]),
|
|
13414
|
+
frame("secrets/revoke", FRAME_SCHEMAS["secrets/revoke"]),
|
|
13415
|
+
frame("scope-attestation", FRAME_SCHEMAS["scope-attestation"]),
|
|
13416
|
+
frame("event/session", FRAME_SCHEMAS["event/session"]),
|
|
13417
|
+
frame("usage/report", FRAME_SCHEMAS["usage/report"]),
|
|
13418
|
+
frame("facts/runtime-profile", FRAME_SCHEMAS["facts/runtime-profile"]),
|
|
13419
|
+
frame("session/list", FRAME_SCHEMAS["session/list"]),
|
|
13420
|
+
frame("session/list/page", FRAME_SCHEMAS["session/list/page"]),
|
|
13421
|
+
frame("session/history", FRAME_SCHEMAS["session/history"]),
|
|
13422
|
+
frame("session/history/page", FRAME_SCHEMAS["session/history/page"]),
|
|
13423
|
+
frame("config/push", FRAME_SCHEMAS["config/push"]),
|
|
13424
|
+
frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
|
|
13425
|
+
frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
|
|
13426
|
+
frame("daemon/control/ack", FRAME_SCHEMAS["daemon/control/ack"]),
|
|
13427
|
+
frame("ack", FRAME_SCHEMAS["ack"]),
|
|
13428
|
+
frame("error", FRAME_SCHEMAS["error"])
|
|
13188
13429
|
]);
|
|
13430
|
+
//#endregion
|
|
13431
|
+
//#region ../protocol/dist/codec.js
|
|
13432
|
+
/** Soft cap per frame — 256 KiB (protocol §1). Over this → FRAME_TOO_LARGE. */
|
|
13433
|
+
const MAX_FRAME_BYTES = 256 * 1024;
|
|
13434
|
+
const textEncoder = new TextEncoder();
|
|
13435
|
+
function byteLength(text) {
|
|
13436
|
+
return textEncoder.encode(text).length;
|
|
13437
|
+
}
|
|
13438
|
+
function extractControlExt(json) {
|
|
13439
|
+
if (typeof json !== "object" || json === null) return void 0;
|
|
13440
|
+
const o = json;
|
|
13441
|
+
const ext = {};
|
|
13442
|
+
if (typeof o.epoch === "number") ext.epoch = o.epoch;
|
|
13443
|
+
if (typeof o.seq === "number") ext.seq = o.seq;
|
|
13444
|
+
if (typeof o.agentId === "string") ext.agentId = o.agentId;
|
|
13445
|
+
if (typeof o.launchId === "string") ext.launchId = o.launchId;
|
|
13446
|
+
return Object.keys(ext).length > 0 ? ext : void 0;
|
|
13447
|
+
}
|
|
13448
|
+
function decodeEnvelope(text) {
|
|
13449
|
+
if (byteLength(text) > 262144) return {
|
|
13450
|
+
ok: false,
|
|
13451
|
+
id: NIL_UUID,
|
|
13452
|
+
msg: "FRAME_TOO_LARGE"
|
|
13453
|
+
};
|
|
13454
|
+
let json;
|
|
13455
|
+
try {
|
|
13456
|
+
json = JSON.parse(text);
|
|
13457
|
+
} catch {
|
|
13458
|
+
return {
|
|
13459
|
+
ok: false,
|
|
13460
|
+
id: NIL_UUID,
|
|
13461
|
+
msg: "invalid json"
|
|
13462
|
+
};
|
|
13463
|
+
}
|
|
13464
|
+
const env = Envelope.safeParse(json);
|
|
13465
|
+
if (!env.success) return {
|
|
13466
|
+
ok: false,
|
|
13467
|
+
id: typeof json === "object" && json !== null && typeof json.id === "string" ? json.id : NIL_UUID,
|
|
13468
|
+
msg: env.error.message
|
|
13469
|
+
};
|
|
13470
|
+
const schema = FRAME_SCHEMAS[env.data.type];
|
|
13471
|
+
if (!schema) return {
|
|
13472
|
+
ok: false,
|
|
13473
|
+
id: env.data.id,
|
|
13474
|
+
msg: "UNKNOWN_FRAME"
|
|
13475
|
+
};
|
|
13476
|
+
const payload = schema.safeParse(env.data.payload);
|
|
13477
|
+
if (!payload.success) return {
|
|
13478
|
+
ok: false,
|
|
13479
|
+
id: env.data.id,
|
|
13480
|
+
msg: payload.error.message
|
|
13481
|
+
};
|
|
13482
|
+
const ext = extractControlExt(json);
|
|
13483
|
+
return {
|
|
13484
|
+
ok: true,
|
|
13485
|
+
frame: {
|
|
13486
|
+
...env.data,
|
|
13487
|
+
payload: payload.data
|
|
13488
|
+
},
|
|
13489
|
+
...ext ? { ext } : {}
|
|
13490
|
+
};
|
|
13491
|
+
}
|
|
13492
|
+
function buildEnvelope(type, payload, opts = {}) {
|
|
13493
|
+
return {
|
|
13494
|
+
v: 1,
|
|
13495
|
+
id: opts.id ?? randomUUID(),
|
|
13496
|
+
ts: opts.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
13497
|
+
type,
|
|
13498
|
+
payload,
|
|
13499
|
+
...opts.corr ? { corr: opts.corr } : {},
|
|
13500
|
+
...opts.ext ?? {}
|
|
13501
|
+
};
|
|
13502
|
+
}
|
|
13503
|
+
function encode(frame) {
|
|
13504
|
+
return JSON.stringify(frame);
|
|
13505
|
+
}
|
|
13506
|
+
//#endregion
|
|
13507
|
+
//#region ../protocol/dist/git-url.js
|
|
13189
13508
|
/**
|
|
13190
|
-
*
|
|
13509
|
+
* Git repo address helpers, shared by the CP (normalize on write) and the
|
|
13510
|
+
* daemon (defensive normalize before clone).
|
|
13191
13511
|
*
|
|
13192
|
-
*
|
|
13193
|
-
*
|
|
13194
|
-
*
|
|
13512
|
+
* STORAGE INVARIANT: `workspace.gitRepo` is persisted as a FULL cloneable git
|
|
13513
|
+
* address (e.g. `https://github.com/acme/infra`), never the `acme/infra`
|
|
13514
|
+
* shorthand a user may type. UIs shorten it back to `org/repo` for display
|
|
13515
|
+
* with `gitRepoLabel`.
|
|
13195
13516
|
*/
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
optionId: zPermissionOptionId,
|
|
13201
|
-
name: string(),
|
|
13202
|
-
kind: zPermissionOptionKind,
|
|
13203
|
-
_meta: record(string(), unknown()).nullish()
|
|
13204
|
-
})),
|
|
13205
|
-
_meta: record(string(), unknown()).nullish()
|
|
13206
|
-
});
|
|
13517
|
+
/** Scheme-full address: https://, ssh://, git://, file://, … */
|
|
13518
|
+
const SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
13519
|
+
/** scp-like ssh address: git@github.com:acme/infra(.git) */
|
|
13520
|
+
const SCP_RE = /^[\w.-]+@[\w.-]+:(.+)$/;
|
|
13207
13521
|
/**
|
|
13208
|
-
*
|
|
13522
|
+
* Normalize a user-supplied repo reference into a full cloneable git address.
|
|
13523
|
+
* Idempotent — full addresses pass through unchanged.
|
|
13524
|
+
*
|
|
13525
|
+
* - `https://…` / `ssh://…` / `git://…` / `git@host:…` → as-is
|
|
13526
|
+
* - `github.com/acme/infra` (host-prefixed shorthand) → `https://github.com/acme/infra`
|
|
13527
|
+
* - `acme/infra` (bare org/repo) → `https://github.com/acme/infra`
|
|
13528
|
+
* - anything else (e.g. a local path) → as-is
|
|
13209
13529
|
*/
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
}
|
|
13530
|
+
function normalizeGitUrl(input) {
|
|
13531
|
+
const s = input.trim().replace(/\/+$/, "");
|
|
13532
|
+
if (!s || SCHEME_RE.test(s) || SCP_RE.test(s)) return s;
|
|
13533
|
+
const segments = s.split("/");
|
|
13534
|
+
if (segments.length >= 3 && segments[0].includes(".")) return `https://${s}`;
|
|
13535
|
+
if (segments.length === 2 && segments.every((p) => p.length > 0)) return `https://github.com/${s}`;
|
|
13536
|
+
return s;
|
|
13537
|
+
}
|
|
13538
|
+
//#endregion
|
|
13539
|
+
//#region ../protocol/dist/index.js
|
|
13215
13540
|
/**
|
|
13216
|
-
*
|
|
13541
|
+
* Narrowing guard factory: `isFrame("auth")(frame)` narrows a decoded
|
|
13542
|
+
* `AnyFrame` to the member whose `type` matches.
|
|
13217
13543
|
*/
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13544
|
+
function isFrame(type) {
|
|
13545
|
+
return (frame) => frame.type === type;
|
|
13546
|
+
}
|
|
13547
|
+
//#endregion
|
|
13548
|
+
//#region src/workspace/workspace-manager.ts
|
|
13549
|
+
const PULL_TIMEOUT_MS = 4500;
|
|
13550
|
+
const cloneInFlight = /* @__PURE__ */ new Map();
|
|
13551
|
+
async function prepareWorkspace(agent) {
|
|
13552
|
+
const cwd = agent.workspace.path;
|
|
13553
|
+
mkdirSync(cwd, { recursive: true });
|
|
13554
|
+
if (agent.workspace.mode === "from-scratch") {
|
|
13555
|
+
const mem = join(cwd, "memory.md");
|
|
13556
|
+
if (!existsSync(mem)) writeFileSync(mem, `# ${agent.name} memory\n`);
|
|
13557
|
+
return cwd;
|
|
13558
|
+
}
|
|
13559
|
+
if (!existsSync(join(cwd, ".git"))) {
|
|
13560
|
+
await cloneRepo(agent);
|
|
13561
|
+
return cwd;
|
|
13562
|
+
}
|
|
13563
|
+
if (agent.workspace.pullOnNewSession) try {
|
|
13564
|
+
await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS))]);
|
|
13565
|
+
} catch {}
|
|
13566
|
+
return cwd;
|
|
13567
|
+
}
|
|
13568
|
+
/**
|
|
13569
|
+
* Eagerly clone a git-repo workspace that has no checkout yet — for reconcile-time
|
|
13570
|
+
* prefetch so the repo is warm before the first message arrives. No-op for
|
|
13571
|
+
* from-scratch or an already-cloned checkout (the session-time `prepareWorkspace`
|
|
13572
|
+
* stays authoritative and still owns pull + hard-fail). Reuses the single-flight
|
|
13573
|
+
* lock so a prefetch and a concurrent session-start clone never race into the same
|
|
13574
|
+
* dir. The caller invokes this fire-and-forget and logs any rejection — clone
|
|
13575
|
+
* failure here is non-fatal because `prepareWorkspace` re-clones (and hard-fails)
|
|
13576
|
+
* on the first session as the backstop.
|
|
13577
|
+
*/
|
|
13578
|
+
async function prefetchWorkspace(agent) {
|
|
13579
|
+
if (agent.workspace.mode !== "git-repo") return;
|
|
13580
|
+
const cwd = agent.workspace.path;
|
|
13581
|
+
if (existsSync(join(cwd, ".git"))) return;
|
|
13582
|
+
mkdirSync(cwd, { recursive: true });
|
|
13583
|
+
await cloneRepo(agent);
|
|
13584
|
+
}
|
|
13585
|
+
/** Clone agent.workspace.gitRepo @ gitBranch into cwd, single-flight per cwd. */
|
|
13586
|
+
async function cloneRepo(agent) {
|
|
13587
|
+
const cwd = agent.workspace.path;
|
|
13588
|
+
const inflight = cloneInFlight.get(cwd);
|
|
13589
|
+
if (inflight) return inflight;
|
|
13590
|
+
const gitRepo = agent.workspace.gitRepo && normalizeGitUrl(agent.workspace.gitRepo);
|
|
13591
|
+
if (!gitRepo) throw new Error(`workspace clone: agent "${agent.id}" has git-repo mode but no gitRepo configured`);
|
|
13592
|
+
const branch = agent.workspace.gitBranch;
|
|
13593
|
+
const p = (async () => {
|
|
13594
|
+
await simpleGit().clone(gitRepo, cwd, [
|
|
13595
|
+
"--branch",
|
|
13596
|
+
branch,
|
|
13597
|
+
"--single-branch"
|
|
13598
|
+
]);
|
|
13599
|
+
})().finally(() => {
|
|
13600
|
+
cloneInFlight.delete(cwd);
|
|
13601
|
+
});
|
|
13602
|
+
cloneInFlight.set(cwd, p);
|
|
13603
|
+
return p;
|
|
13604
|
+
}
|
|
13605
|
+
//#endregion
|
|
13606
|
+
//#region ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.0.0_zod@4.4.3/node_modules/@agentclientprotocol/sdk/dist/schema/index.js
|
|
13607
|
+
const AGENT_METHODS = {
|
|
13608
|
+
initialize: "initialize",
|
|
13609
|
+
authenticate: "authenticate",
|
|
13610
|
+
providers_list: "providers/list",
|
|
13611
|
+
providers_set: "providers/set",
|
|
13612
|
+
providers_disable: "providers/disable",
|
|
13613
|
+
session_new: "session/new",
|
|
13614
|
+
session_load: "session/load",
|
|
13615
|
+
session_set_mode: "session/set_mode",
|
|
13616
|
+
session_set_config_option: "session/set_config_option",
|
|
13617
|
+
session_prompt: "session/prompt",
|
|
13618
|
+
session_cancel: "session/cancel",
|
|
13619
|
+
mcp_message: "mcp/message",
|
|
13620
|
+
session_list: "session/list",
|
|
13621
|
+
session_delete: "session/delete",
|
|
13622
|
+
session_fork: "session/fork",
|
|
13623
|
+
session_resume: "session/resume",
|
|
13624
|
+
session_close: "session/close",
|
|
13625
|
+
logout: "logout",
|
|
13626
|
+
nes_start: "nes/start",
|
|
13627
|
+
nes_suggest: "nes/suggest",
|
|
13628
|
+
nes_accept: "nes/accept",
|
|
13629
|
+
nes_reject: "nes/reject",
|
|
13630
|
+
nes_close: "nes/close",
|
|
13631
|
+
document_did_open: "document/didOpen",
|
|
13632
|
+
document_did_change: "document/didChange",
|
|
13633
|
+
document_did_close: "document/didClose",
|
|
13634
|
+
document_did_save: "document/didSave",
|
|
13635
|
+
document_did_focus: "document/didFocus"
|
|
13636
|
+
};
|
|
13637
|
+
const CLIENT_METHODS = {
|
|
13638
|
+
session_request_permission: "session/request_permission",
|
|
13639
|
+
session_update: "session/update",
|
|
13640
|
+
fs_write_text_file: "fs/write_text_file",
|
|
13641
|
+
fs_read_text_file: "fs/read_text_file",
|
|
13642
|
+
terminal_create: "terminal/create",
|
|
13643
|
+
terminal_output: "terminal/output",
|
|
13644
|
+
terminal_release: "terminal/release",
|
|
13645
|
+
terminal_wait_for_exit: "terminal/wait_for_exit",
|
|
13646
|
+
terminal_kill: "terminal/kill",
|
|
13647
|
+
mcp_connect: "mcp/connect",
|
|
13648
|
+
mcp_message: "mcp/message",
|
|
13649
|
+
mcp_disconnect: "mcp/disconnect",
|
|
13650
|
+
elicitation_create: "elicitation/create",
|
|
13651
|
+
elicitation_complete: "elicitation/complete"
|
|
13652
|
+
};
|
|
13653
|
+
const PROTOCOL_METHODS = { cancel_request: "$/cancel_request" };
|
|
13654
|
+
//#endregion
|
|
13655
|
+
//#region ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.0.0_zod@4.4.3/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js
|
|
13656
|
+
const skippedItem = Symbol("skippedItem");
|
|
13657
|
+
function defaultOnError(schema, fallback) {
|
|
13658
|
+
return schema.catch(fallback);
|
|
13659
|
+
}
|
|
13660
|
+
function requiredDefaultOnError(schema, fallback) {
|
|
13661
|
+
const schemaWithCatch = schema.catch(fallback);
|
|
13662
|
+
return unknown().transform((value, context) => {
|
|
13663
|
+
if (value !== void 0) return schemaWithCatch.parse(value);
|
|
13664
|
+
context.addIssue({
|
|
13665
|
+
code: "custom",
|
|
13666
|
+
message: "Required value is missing"
|
|
13667
|
+
});
|
|
13668
|
+
return NEVER;
|
|
13669
|
+
});
|
|
13670
|
+
}
|
|
13671
|
+
function vecSkipError(itemSchema) {
|
|
13672
|
+
return array(itemSchema.catch(skippedItem)).transform((items) => items.filter((item) => item !== skippedItem));
|
|
13673
|
+
}
|
|
13674
|
+
//#endregion
|
|
13675
|
+
//#region ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.0.0_zod@4.4.3/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js
|
|
13676
|
+
/**
|
|
13677
|
+
* JSON RPC Request Id
|
|
13678
|
+
*
|
|
13679
|
+
* An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. The value SHOULD normally not be Null \[1\] and Numbers SHOULD NOT contain fractional parts \[2\]
|
|
13680
|
+
*
|
|
13681
|
+
* The Server MUST reply with the same value in the Response object if included. This member is used to correlate the context between the two objects.
|
|
13682
|
+
*
|
|
13683
|
+
* \[1\] The use of Null as a value for the id member in a Request object is discouraged, because this specification uses a value of Null for Responses with an unknown id. Also, because JSON-RPC 1.0 uses an id value of Null for Notifications this could cause confusion in handling.
|
|
13684
|
+
*
|
|
13685
|
+
* \[2\] Fractional parts may be problematic, since many decimal fractions cannot be represented exactly as binary fractions.
|
|
13686
|
+
*/
|
|
13687
|
+
const zRequestId = union([number(), string()]).nullable();
|
|
13688
|
+
/**
|
|
13689
|
+
* A unique identifier for a conversation session between a client and agent.
|
|
13690
|
+
*
|
|
13691
|
+
* Sessions maintain their own context, conversation history, and state,
|
|
13692
|
+
* allowing multiple independent interactions with the same agent.
|
|
13693
|
+
*
|
|
13694
|
+
* See protocol docs: [Session ID](https://agentclientprotocol.com/protocol/session-setup#session-id)
|
|
13695
|
+
*/
|
|
13696
|
+
const zSessionId = string();
|
|
13697
|
+
/**
|
|
13698
|
+
* Request to write content to a text file.
|
|
13699
|
+
*
|
|
13700
|
+
* Only available if the client supports the `fs.writeTextFile` capability.
|
|
13701
|
+
*/
|
|
13702
|
+
const zWriteTextFileRequest = object({
|
|
13703
|
+
sessionId: zSessionId,
|
|
13704
|
+
path: string(),
|
|
13705
|
+
content: string(),
|
|
13706
|
+
_meta: record(string(), unknown()).nullish()
|
|
13707
|
+
});
|
|
13708
|
+
/**
|
|
13709
|
+
* Request to read content from a text file.
|
|
13710
|
+
*
|
|
13711
|
+
* Only available if the client supports the `fs.readTextFile` capability.
|
|
13712
|
+
*/
|
|
13713
|
+
const zReadTextFileRequest = object({
|
|
13714
|
+
sessionId: zSessionId,
|
|
13715
|
+
path: string(),
|
|
13716
|
+
line: int().gte(0).max(4294967295, { error: "Invalid value: Expected uint32 to be <= 4294967295" }).nullish(),
|
|
13717
|
+
limit: int().gte(0).max(4294967295, { error: "Invalid value: Expected uint32 to be <= 4294967295" }).nullish(),
|
|
13718
|
+
_meta: record(string(), unknown()).nullish()
|
|
13719
|
+
});
|
|
13720
|
+
/**
|
|
13721
|
+
* Unique identifier for a tool call within a session.
|
|
13722
|
+
*/
|
|
13723
|
+
const zToolCallId = string();
|
|
13724
|
+
/**
|
|
13725
|
+
* Categories of tools that can be invoked.
|
|
13726
|
+
*
|
|
13727
|
+
* Tool kinds help clients choose appropriate icons and optimize how they
|
|
13728
|
+
* display tool execution progress.
|
|
13729
|
+
*
|
|
13730
|
+
* See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-calls#creating)
|
|
13731
|
+
*/
|
|
13732
|
+
const zToolKind = union([
|
|
13733
|
+
literal("read"),
|
|
13734
|
+
literal("edit"),
|
|
13735
|
+
literal("delete"),
|
|
13736
|
+
literal("move"),
|
|
13737
|
+
literal("search"),
|
|
13738
|
+
literal("execute"),
|
|
13739
|
+
literal("think"),
|
|
13740
|
+
literal("fetch"),
|
|
13741
|
+
literal("switch_mode"),
|
|
13742
|
+
literal("other")
|
|
13743
|
+
]);
|
|
13744
|
+
/**
|
|
13745
|
+
* Execution status of a tool call.
|
|
13746
|
+
*
|
|
13747
|
+
* Tool calls progress through different statuses during their lifecycle.
|
|
13748
|
+
*
|
|
13749
|
+
* See protocol docs: [Status](https://agentclientprotocol.com/protocol/tool-calls#status)
|
|
13750
|
+
*/
|
|
13751
|
+
const zToolCallStatus = union([
|
|
13752
|
+
literal("pending"),
|
|
13753
|
+
literal("in_progress"),
|
|
13754
|
+
literal("completed"),
|
|
13755
|
+
literal("failed")
|
|
13756
|
+
]);
|
|
13757
|
+
/**
|
|
13758
|
+
* Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
|
|
13759
|
+
*/
|
|
13760
|
+
const zAnnotations = object({
|
|
13761
|
+
audience: defaultOnError(vecSkipError(union([literal("assistant"), literal("user")])).nullish(), () => void 0),
|
|
13762
|
+
lastModified: string().nullish(),
|
|
13763
|
+
priority: number().nullish(),
|
|
13764
|
+
_meta: record(string(), unknown()).nullish()
|
|
13765
|
+
});
|
|
13766
|
+
/**
|
|
13767
|
+
* Text provided to or from an LLM.
|
|
13768
|
+
*/
|
|
13769
|
+
const zTextContent = object({
|
|
13770
|
+
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13771
|
+
text: string(),
|
|
13772
|
+
_meta: record(string(), unknown()).nullish()
|
|
13773
|
+
});
|
|
13774
|
+
/**
|
|
13775
|
+
* An image provided to or from an LLM.
|
|
13776
|
+
*/
|
|
13777
|
+
const zImageContent = object({
|
|
13778
|
+
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13779
|
+
data: string(),
|
|
13780
|
+
mimeType: string(),
|
|
13781
|
+
uri: string().nullish(),
|
|
13782
|
+
_meta: record(string(), unknown()).nullish()
|
|
13783
|
+
});
|
|
13784
|
+
/**
|
|
13785
|
+
* Audio provided to or from an LLM.
|
|
13786
|
+
*/
|
|
13787
|
+
const zAudioContent = object({
|
|
13788
|
+
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13789
|
+
data: string(),
|
|
13790
|
+
mimeType: string(),
|
|
13791
|
+
_meta: record(string(), unknown()).nullish()
|
|
13792
|
+
});
|
|
13793
|
+
/**
|
|
13794
|
+
* A resource that the server is capable of reading, included in a prompt or tool call result.
|
|
13795
|
+
*/
|
|
13796
|
+
const zResourceLink = object({
|
|
13797
|
+
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13798
|
+
description: string().nullish(),
|
|
13799
|
+
mimeType: string().nullish(),
|
|
13800
|
+
name: string(),
|
|
13801
|
+
size: number().nullish(),
|
|
13802
|
+
title: string().nullish(),
|
|
13803
|
+
uri: string(),
|
|
13804
|
+
_meta: record(string(), unknown()).nullish()
|
|
13805
|
+
});
|
|
13806
|
+
/**
|
|
13807
|
+
* Resource content that can be embedded in a message.
|
|
13808
|
+
*/
|
|
13809
|
+
const zEmbeddedResourceResource = union([object({
|
|
13810
|
+
mimeType: string().nullish(),
|
|
13811
|
+
text: string(),
|
|
13812
|
+
uri: string(),
|
|
13813
|
+
_meta: record(string(), unknown()).nullish()
|
|
13814
|
+
}), object({
|
|
13815
|
+
blob: string(),
|
|
13816
|
+
mimeType: string().nullish(),
|
|
13817
|
+
uri: string(),
|
|
13818
|
+
_meta: record(string(), unknown()).nullish()
|
|
13819
|
+
})]);
|
|
13820
|
+
/**
|
|
13821
|
+
* The contents of a resource, embedded into a prompt or tool call result.
|
|
13822
|
+
*/
|
|
13823
|
+
const zEmbeddedResource = object({
|
|
13824
|
+
annotations: defaultOnError(zAnnotations.nullish(), () => void 0),
|
|
13825
|
+
resource: zEmbeddedResourceResource,
|
|
13826
|
+
_meta: record(string(), unknown()).nullish()
|
|
13827
|
+
});
|
|
13828
|
+
/**
|
|
13829
|
+
* Content blocks represent displayable information in the Agent Client Protocol.
|
|
13830
|
+
*
|
|
13831
|
+
* They provide a structured way to handle various types of user-facing content—whether
|
|
13832
|
+
* it's text from language models, images for analysis, or embedded resources for context.
|
|
13833
|
+
*
|
|
13834
|
+
* Content blocks appear in:
|
|
13835
|
+
* - User prompts sent via `session/prompt`
|
|
13836
|
+
* - Language model output streamed through `session/update` notifications
|
|
13837
|
+
* - Progress updates and results from tool calls
|
|
13838
|
+
*
|
|
13839
|
+
* This structure is compatible with the Model Context Protocol (MCP), enabling
|
|
13840
|
+
* agents to seamlessly forward content from MCP tool outputs without transformation.
|
|
13841
|
+
*
|
|
13842
|
+
* See protocol docs: [Content](https://agentclientprotocol.com/protocol/content)
|
|
13843
|
+
*/
|
|
13844
|
+
const zContentBlock = union([
|
|
13845
|
+
zTextContent.and(object({ type: literal("text") })),
|
|
13846
|
+
zImageContent.and(object({ type: literal("image") })),
|
|
13847
|
+
zAudioContent.and(object({ type: literal("audio") })),
|
|
13848
|
+
zResourceLink.and(object({ type: literal("resource_link") })),
|
|
13849
|
+
zEmbeddedResource.and(object({ type: literal("resource") }))
|
|
13850
|
+
]);
|
|
13851
|
+
/**
|
|
13852
|
+
* Standard content block (text, images, resources).
|
|
13853
|
+
*/
|
|
13854
|
+
const zContent = object({
|
|
13855
|
+
content: zContentBlock,
|
|
13856
|
+
_meta: record(string(), unknown()).nullish()
|
|
13857
|
+
});
|
|
13858
|
+
/**
|
|
13859
|
+
* A diff representing file modifications.
|
|
13860
|
+
*
|
|
13861
|
+
* Shows changes to files in a format suitable for display in the client UI.
|
|
13862
|
+
*
|
|
13863
|
+
* See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
|
|
13864
|
+
*/
|
|
13865
|
+
const zDiff = object({
|
|
13866
|
+
path: string(),
|
|
13867
|
+
oldText: string().nullish(),
|
|
13868
|
+
newText: string(),
|
|
13869
|
+
_meta: record(string(), unknown()).nullish()
|
|
13870
|
+
});
|
|
13871
|
+
/**
|
|
13872
|
+
* Typed identifier used for terminal values on the wire.
|
|
13873
|
+
*/
|
|
13874
|
+
const zTerminalId = string();
|
|
13875
|
+
/**
|
|
13876
|
+
* Embed a terminal created with `terminal/create` by its id.
|
|
13877
|
+
*
|
|
13878
|
+
* The terminal must be added before calling `terminal/release`.
|
|
13879
|
+
*
|
|
13880
|
+
* See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals)
|
|
13881
|
+
*/
|
|
13882
|
+
const zTerminal = object({
|
|
13883
|
+
terminalId: zTerminalId,
|
|
13884
|
+
_meta: record(string(), unknown()).nullish()
|
|
13885
|
+
});
|
|
13886
|
+
/**
|
|
13887
|
+
* Content produced by a tool call.
|
|
13888
|
+
*
|
|
13889
|
+
* Tool calls can produce different types of content including
|
|
13890
|
+
* standard content blocks (text, images) or file diffs.
|
|
13891
|
+
*
|
|
13892
|
+
* See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
|
|
13893
|
+
*/
|
|
13894
|
+
const zToolCallContent = union([
|
|
13895
|
+
zContent.and(object({ type: literal("content") })),
|
|
13896
|
+
zDiff.and(object({ type: literal("diff") })),
|
|
13897
|
+
zTerminal.and(object({ type: literal("terminal") }))
|
|
13898
|
+
]);
|
|
13899
|
+
/**
|
|
13900
|
+
* A file location being accessed or modified by a tool.
|
|
13901
|
+
*
|
|
13902
|
+
* Enables clients to implement "follow-along" features that track
|
|
13903
|
+
* which files the agent is working with in real-time.
|
|
13904
|
+
*
|
|
13905
|
+
* See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent)
|
|
13906
|
+
*/
|
|
13907
|
+
const zToolCallLocation = object({
|
|
13908
|
+
path: string(),
|
|
13909
|
+
line: int().gte(0).max(4294967295, { error: "Invalid value: Expected uint32 to be <= 4294967295" }).nullish(),
|
|
13910
|
+
_meta: record(string(), unknown()).nullish()
|
|
13911
|
+
});
|
|
13912
|
+
/**
|
|
13913
|
+
* An update to an existing tool call.
|
|
13914
|
+
*
|
|
13915
|
+
* Used to report progress and results as tools execute. All fields except
|
|
13916
|
+
* the tool call ID are optional - only changed fields need to be included.
|
|
13917
|
+
*
|
|
13918
|
+
* See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating)
|
|
13919
|
+
*/
|
|
13920
|
+
const zToolCallUpdate = object({
|
|
13921
|
+
toolCallId: zToolCallId,
|
|
13922
|
+
kind: defaultOnError(zToolKind.nullish(), () => void 0),
|
|
13923
|
+
status: defaultOnError(zToolCallStatus.nullish(), () => void 0),
|
|
13924
|
+
title: string().nullish(),
|
|
13925
|
+
content: defaultOnError(vecSkipError(zToolCallContent).nullish(), () => void 0),
|
|
13926
|
+
locations: defaultOnError(vecSkipError(zToolCallLocation).nullish(), () => void 0),
|
|
13927
|
+
rawInput: unknown().optional(),
|
|
13928
|
+
rawOutput: unknown().optional(),
|
|
13929
|
+
_meta: record(string(), unknown()).nullish()
|
|
13930
|
+
});
|
|
13931
|
+
/**
|
|
13932
|
+
* Unique identifier for a permission option.
|
|
13933
|
+
*/
|
|
13934
|
+
const zPermissionOptionId = string();
|
|
13935
|
+
/**
|
|
13936
|
+
* The type of permission option being presented to the user.
|
|
13937
|
+
*
|
|
13938
|
+
* Helps clients choose appropriate icons and UI treatment.
|
|
13939
|
+
*/
|
|
13940
|
+
const zPermissionOptionKind = union([
|
|
13941
|
+
literal("allow_once"),
|
|
13942
|
+
literal("allow_always"),
|
|
13943
|
+
literal("reject_once"),
|
|
13944
|
+
literal("reject_always")
|
|
13945
|
+
]);
|
|
13946
|
+
/**
|
|
13947
|
+
* Request for user permission to execute a tool call.
|
|
13948
|
+
*
|
|
13949
|
+
* Sent when the agent needs authorization before performing a sensitive operation.
|
|
13950
|
+
*
|
|
13951
|
+
* See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
|
|
13952
|
+
*/
|
|
13953
|
+
const zRequestPermissionRequest = object({
|
|
13954
|
+
sessionId: zSessionId,
|
|
13955
|
+
toolCall: zToolCallUpdate,
|
|
13956
|
+
options: array(object({
|
|
13957
|
+
optionId: zPermissionOptionId,
|
|
13958
|
+
name: string(),
|
|
13959
|
+
kind: zPermissionOptionKind,
|
|
13960
|
+
_meta: record(string(), unknown()).nullish()
|
|
13961
|
+
})),
|
|
13962
|
+
_meta: record(string(), unknown()).nullish()
|
|
13963
|
+
});
|
|
13964
|
+
/**
|
|
13965
|
+
* An environment variable to set when launching an MCP server.
|
|
13966
|
+
*/
|
|
13967
|
+
const zEnvVariable = object({
|
|
13968
|
+
name: string(),
|
|
13969
|
+
value: string(),
|
|
13970
|
+
_meta: record(string(), unknown()).nullish()
|
|
13971
|
+
});
|
|
13972
|
+
/**
|
|
13973
|
+
* Request to create a new terminal and execute a command.
|
|
13974
|
+
*/
|
|
13975
|
+
const zCreateTerminalRequest = object({
|
|
13976
|
+
sessionId: zSessionId,
|
|
13977
|
+
command: string(),
|
|
13978
|
+
args: array(string()).optional(),
|
|
13222
13979
|
env: array(zEnvVariable).optional(),
|
|
13223
13980
|
cwd: string().nullish(),
|
|
13224
13981
|
outputByteLimit: number().nullish(),
|
|
@@ -17253,731 +18010,6 @@ async function runChat(opts) {
|
|
|
17253
18010
|
//#region src/version.ts
|
|
17254
18011
|
const DAEMON_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
17255
18012
|
//#endregion
|
|
17256
|
-
//#region ../protocol/dist/envelope.js
|
|
17257
|
-
/**
|
|
17258
|
-
* The protocol envelope — every frame, both directions, is wrapped in this.
|
|
17259
|
-
* Mirrors daemon-cp-ws-protocol.md §1.1.
|
|
17260
|
-
*
|
|
17261
|
-
* `payload` is left as `unknown` here and validated by the per-type schema in
|
|
17262
|
-
* `frames/*` via `FRAME_SCHEMAS[type]` (see `frame.ts`). This two-step parse is
|
|
17263
|
-
* what lets the codec answer an unknown `type` with `UNKNOWN_FRAME` (a REP)
|
|
17264
|
-
* instead of a hard close — forward-compat, protocol §1.
|
|
17265
|
-
*/
|
|
17266
|
-
const Envelope = object({
|
|
17267
|
-
v: literal(1),
|
|
17268
|
-
id: string().uuid(),
|
|
17269
|
-
ts: string().datetime(),
|
|
17270
|
-
type: string(),
|
|
17271
|
-
corr: string().uuid().optional(),
|
|
17272
|
-
payload: unknown()
|
|
17273
|
-
});
|
|
17274
|
-
object({
|
|
17275
|
-
epoch: number().int(),
|
|
17276
|
-
seq: number().int().optional(),
|
|
17277
|
-
agentId: string().uuid().optional(),
|
|
17278
|
-
launchId: string().uuid().optional()
|
|
17279
|
-
});
|
|
17280
|
-
/** The all-zero UUID used when a frame is malformed past the point of reading `id`. */
|
|
17281
|
-
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
17282
|
-
//#endregion
|
|
17283
|
-
//#region ../protocol/dist/frames/auth.js
|
|
17284
|
-
/**
|
|
17285
|
-
* Auth & identity — protocol §3.1 / §3.2.
|
|
17286
|
-
*
|
|
17287
|
-
* `auth` is the first frame after the socket opens. `auth/ok` carries the
|
|
17288
|
-
* minted `sessionEpoch` (the global fencing token), heartbeat cadence, and the
|
|
17289
|
-
* resume verdict.
|
|
17290
|
-
*/
|
|
17291
|
-
const AuthReq = object({
|
|
17292
|
-
apiKey: string(),
|
|
17293
|
-
daemonId: string().uuid().optional(),
|
|
17294
|
-
machineId: string().uuid().optional(),
|
|
17295
|
-
attestation: string().optional(),
|
|
17296
|
-
agentVersion: string(),
|
|
17297
|
-
resume: object({
|
|
17298
|
-
lastEpoch: number().int(),
|
|
17299
|
-
lastRecvSeq: record(string(), number())
|
|
17300
|
-
}).optional()
|
|
17301
|
-
});
|
|
17302
|
-
const AuthOk = object({
|
|
17303
|
-
daemonId: string().uuid(),
|
|
17304
|
-
sessionEpoch: number().int(),
|
|
17305
|
-
heartbeatSec: number().int(),
|
|
17306
|
-
serverTime: string().datetime(),
|
|
17307
|
-
resume: object({
|
|
17308
|
-
accepted: boolean(),
|
|
17309
|
-
redeliverFromSeq: record(string(), number()).optional()
|
|
17310
|
-
}).optional()
|
|
17311
|
-
});
|
|
17312
|
-
//#endregion
|
|
17313
|
-
//#region ../protocol/dist/frames/route.js
|
|
17314
|
-
/**
|
|
17315
|
-
* Routing & orchestration (C→D control) — protocol §5.
|
|
17316
|
-
*
|
|
17317
|
-
* `SessionKey` is the canonical session primitive shared across route/*,
|
|
17318
|
-
* agent/*, and event/session. Its canonical string form is
|
|
17319
|
-
* `${platform}:${channel}:${thread ?? "-"}`.
|
|
17320
|
-
*/
|
|
17321
|
-
const Platform = _enum(["slack", "telegram"]);
|
|
17322
|
-
const SessionKey = object({
|
|
17323
|
-
platform: Platform,
|
|
17324
|
-
channel: string(),
|
|
17325
|
-
thread: string().optional()
|
|
17326
|
-
});
|
|
17327
|
-
/** Trigger-matching rule for a binding (protocol §5.1). */
|
|
17328
|
-
const BindRule = object({ match: discriminatedUnion("kind", [
|
|
17329
|
-
object({ kind: literal("mention") }),
|
|
17330
|
-
object({ kind: literal("dm") }),
|
|
17331
|
-
object({
|
|
17332
|
-
kind: literal("keyword"),
|
|
17333
|
-
value: string()
|
|
17334
|
-
}),
|
|
17335
|
-
object({ kind: literal("auto") })
|
|
17336
|
-
]) });
|
|
17337
|
-
const RouteAssign = object({
|
|
17338
|
-
sessionKey: SessionKey,
|
|
17339
|
-
agentId: string().uuid(),
|
|
17340
|
-
workspaceId: string().uuid(),
|
|
17341
|
-
bindRules: array(BindRule).default([])
|
|
17342
|
-
});
|
|
17343
|
-
const RouteAssignAck = object({
|
|
17344
|
-
ok: boolean(),
|
|
17345
|
-
sessionKey: SessionKey,
|
|
17346
|
-
reason: string().optional()
|
|
17347
|
-
});
|
|
17348
|
-
const RouteUpdate = object({
|
|
17349
|
-
routingEpoch: number().int(),
|
|
17350
|
-
rules: array(object({
|
|
17351
|
-
match: unknown(),
|
|
17352
|
-
agentId: string().uuid()
|
|
17353
|
-
}))
|
|
17354
|
-
});
|
|
17355
|
-
/** Graceful scale-down / rebalance — protocol §5.3. */
|
|
17356
|
-
const Drain = object({
|
|
17357
|
-
scope: union([
|
|
17358
|
-
object({
|
|
17359
|
-
kind: literal("agent"),
|
|
17360
|
-
agentId: string().uuid()
|
|
17361
|
-
}),
|
|
17362
|
-
object({ kind: literal("daemon") }),
|
|
17363
|
-
object({
|
|
17364
|
-
kind: literal("session"),
|
|
17365
|
-
sessionKey: SessionKey
|
|
17366
|
-
})
|
|
17367
|
-
]),
|
|
17368
|
-
deadline: string().datetime()
|
|
17369
|
-
});
|
|
17370
|
-
const DrainProgress = object({
|
|
17371
|
-
remaining: number().int(),
|
|
17372
|
-
drained: array(SessionKey)
|
|
17373
|
-
});
|
|
17374
|
-
const DrainDone = object({ released: array(SessionKey) });
|
|
17375
|
-
//#endregion
|
|
17376
|
-
//#region ../protocol/dist/frames/cron.js
|
|
17377
|
-
/**
|
|
17378
|
-
* Cron sinks to the daemon (D5) — protocol §5.4.
|
|
17379
|
-
*
|
|
17380
|
-
* The CP owns the definition; the daemon owns firing + last-run persistence so
|
|
17381
|
-
* crons fire even when the CP is down.
|
|
17382
|
-
*/
|
|
17383
|
-
const CronUpsert = object({
|
|
17384
|
-
cronId: string().uuid(),
|
|
17385
|
-
schedule: string(),
|
|
17386
|
-
target: object({ channel: string() }),
|
|
17387
|
-
trigger: string(),
|
|
17388
|
-
enabled: boolean().default(true)
|
|
17389
|
-
});
|
|
17390
|
-
const CronRemove = object({ cronId: string().uuid() });
|
|
17391
|
-
//#endregion
|
|
17392
|
-
//#region ../protocol/dist/frames/secrets.js
|
|
17393
|
-
/**
|
|
17394
|
-
* Secrets (C5 ↔ D10) — protocol §6.
|
|
17395
|
-
*
|
|
17396
|
-
* Lease-based, no plaintext on the wire or in PG. Every frame carries a
|
|
17397
|
-
* REFERENCE to a Vault/KMS path, never the secret material itself.
|
|
17398
|
-
*/
|
|
17399
|
-
const SecretsRequest = object({ scope: object({
|
|
17400
|
-
platform: Platform,
|
|
17401
|
-
workspaceId: string().uuid()
|
|
17402
|
-
}) });
|
|
17403
|
-
const SecretsGrant = object({
|
|
17404
|
-
leaseId: string().uuid(),
|
|
17405
|
-
scope: object({
|
|
17406
|
-
platform: string(),
|
|
17407
|
-
workspaceId: string().uuid()
|
|
17408
|
-
}),
|
|
17409
|
-
ref: string(),
|
|
17410
|
-
ttl: number().int(),
|
|
17411
|
-
renewBeforeSec: number().int()
|
|
17412
|
-
});
|
|
17413
|
-
const SecretsRenew = object({ leaseId: string().uuid() });
|
|
17414
|
-
const SecretsRevoke = object({
|
|
17415
|
-
leaseId: string().uuid(),
|
|
17416
|
-
reason: string()
|
|
17417
|
-
});
|
|
17418
|
-
/** 🅼 Direct-to-store upload/download grant — protocol §3.2 / frame #25. */
|
|
17419
|
-
const ScopeAttestation = object({
|
|
17420
|
-
machineId: string().uuid(),
|
|
17421
|
-
scope: _enum([
|
|
17422
|
-
"attachment.put",
|
|
17423
|
-
"attachment.get",
|
|
17424
|
-
"facts.put"
|
|
17425
|
-
]),
|
|
17426
|
-
resourceRef: string(),
|
|
17427
|
-
jws: string(),
|
|
17428
|
-
exp: string().datetime()
|
|
17429
|
-
});
|
|
17430
|
-
//#endregion
|
|
17431
|
-
//#region ../protocol/dist/frames/agent.js
|
|
17432
|
-
/**
|
|
17433
|
-
* Agent lifecycle + delivery (protocol §4.3, §4.4, §7.4, §8).
|
|
17434
|
-
*
|
|
17435
|
-
* Body-locality invariant (protocol §12): no frame here carries
|
|
17436
|
-
* `NormalizedMessage.text`. `agent/prompt` ships a `NormalizedMessageRef` — a
|
|
17437
|
-
* reference the daemon resolves against its local body store.
|
|
17438
|
-
*/
|
|
17439
|
-
/**
|
|
17440
|
-
* A REFERENCE/digest of a normalized message, NOT the body (protocol §4.3).
|
|
17441
|
-
* Enough for D4/D6 to fetch the local body and prompt.
|
|
17442
|
-
*/
|
|
17443
|
-
const NormalizedMessageRef = object({
|
|
17444
|
-
sessionKey: SessionKey,
|
|
17445
|
-
platformMsgId: string(),
|
|
17446
|
-
seenUpToSeq: number().int()
|
|
17447
|
-
});
|
|
17448
|
-
/**
|
|
17449
|
-
* Where the agent runs. Two modes; the **path is always daemon-generated** —
|
|
17450
|
-
* never specified by the caller (UX picks the mode, the machine owns the dir).
|
|
17451
|
-
*
|
|
17452
|
-
* - `scratch`: a fresh empty working dir on the machine, no repo.
|
|
17453
|
-
* - `github`: the daemon clones `gitRepo` @ `branch` and runs the agent in
|
|
17454
|
-
* `agentDir` (a subdir of the repo, repo-root if omitted). **Multiple agents
|
|
17455
|
-
* may share one repo** — they differ by `agentDir`, so the repo is not an
|
|
17456
|
-
* owned entity, just shared config on each agent.
|
|
17457
|
-
*/
|
|
17458
|
-
const AgentWorkspace = discriminatedUnion("mode", [object({ mode: literal("scratch") }), object({
|
|
17459
|
-
mode: literal("github"),
|
|
17460
|
-
gitRepo: string(),
|
|
17461
|
-
branch: string().default("main"),
|
|
17462
|
-
agentDir: string().optional()
|
|
17463
|
-
})]);
|
|
17464
|
-
/**
|
|
17465
|
-
* The editable agent definition the CP owns and the daemon needs to run it:
|
|
17466
|
-
* prompt + runtime selection. Raft ships this in the launch config and the
|
|
17467
|
-
* daemon synthesizes the system prompt locally; `description` IS the prompt.
|
|
17468
|
-
*/
|
|
17469
|
-
const AgentSpec = object({
|
|
17470
|
-
name: string(),
|
|
17471
|
-
description: string().optional(),
|
|
17472
|
-
runtime: string().optional(),
|
|
17473
|
-
model: string().optional(),
|
|
17474
|
-
reasoningEffort: string().optional(),
|
|
17475
|
-
executionMode: string().optional(),
|
|
17476
|
-
workspace: AgentWorkspace.optional(),
|
|
17477
|
-
env: record(string(), string()).optional()
|
|
17478
|
-
});
|
|
17479
|
-
const AgentLaunch = object({
|
|
17480
|
-
agentId: string().uuid(),
|
|
17481
|
-
runtime: string(),
|
|
17482
|
-
workspaceId: string().uuid(),
|
|
17483
|
-
capabilities: array(string()),
|
|
17484
|
-
spec: AgentSpec,
|
|
17485
|
-
mode: _enum(["long_lived", "per_turn"]).default("long_lived")
|
|
17486
|
-
});
|
|
17487
|
-
/**
|
|
17488
|
-
* Live agent CRUD (C→D): the console edited an agent's spec; push it so a
|
|
17489
|
-
* running daemon reloads without waiting for the next launch. `agent/remove`
|
|
17490
|
-
* tears the agent down. Deleting an agent never relaunches it.
|
|
17491
|
-
*/
|
|
17492
|
-
const AgentUpsert = object({
|
|
17493
|
-
agentId: string().uuid(),
|
|
17494
|
-
spec: AgentSpec
|
|
17495
|
-
});
|
|
17496
|
-
const AgentRemove = object({ agentId: string().uuid() });
|
|
17497
|
-
const AgentLaunched = object({
|
|
17498
|
-
agentId: string().uuid(),
|
|
17499
|
-
launchId: string().uuid(),
|
|
17500
|
-
acpSessionId: string().optional(),
|
|
17501
|
-
startedAt: string().datetime(),
|
|
17502
|
-
runtime: string()
|
|
17503
|
-
});
|
|
17504
|
-
const AgentStop = object({
|
|
17505
|
-
agentId: string().uuid(),
|
|
17506
|
-
launchId: string().uuid(),
|
|
17507
|
-
reason: string()
|
|
17508
|
-
});
|
|
17509
|
-
const AgentPrompt = object({
|
|
17510
|
-
sessionKey: SessionKey,
|
|
17511
|
-
agentId: string().uuid(),
|
|
17512
|
-
content: NormalizedMessageRef,
|
|
17513
|
-
seenUpToSeq: number().int()
|
|
17514
|
-
});
|
|
17515
|
-
const AgentPromptAck = object({
|
|
17516
|
-
accepted: boolean(),
|
|
17517
|
-
reason: _enum([
|
|
17518
|
-
"queued",
|
|
17519
|
-
"held",
|
|
17520
|
-
"scope_denied",
|
|
17521
|
-
"no_session",
|
|
17522
|
-
"stale"
|
|
17523
|
-
]).optional(),
|
|
17524
|
-
seq: number().int()
|
|
17525
|
-
});
|
|
17526
|
-
const AgentActivity = object({
|
|
17527
|
-
agentId: string().uuid(),
|
|
17528
|
-
launchId: string().uuid(),
|
|
17529
|
-
state: _enum([
|
|
17530
|
-
"thinking",
|
|
17531
|
-
"tool_call",
|
|
17532
|
-
"awaiting_permission",
|
|
17533
|
-
"idle"
|
|
17534
|
-
]),
|
|
17535
|
-
ts: string().datetime()
|
|
17536
|
-
});
|
|
17537
|
-
const AgentScopeDenied = object({
|
|
17538
|
-
agentId: string().uuid(),
|
|
17539
|
-
launchId: string().uuid(),
|
|
17540
|
-
capability: string()
|
|
17541
|
-
});
|
|
17542
|
-
//#endregion
|
|
17543
|
-
//#region ../protocol/dist/frames/integration.js
|
|
17544
|
-
/**
|
|
17545
|
-
* Platform integration distribution (C→D) — the Slack "install" flow.
|
|
17546
|
-
*
|
|
17547
|
-
* The Control Plane is the source of truth for platform integrations and pushes
|
|
17548
|
-
* them to the daemon that owns the integration's agent (`integration/upsert`, and
|
|
17549
|
-
* the reconcile snapshot `RegisterOk.integrations[]`). The daemon opens the Socket
|
|
17550
|
-
* Mode connection from the delivered config (see slack/connection.ts).
|
|
17551
|
-
*
|
|
17552
|
-
* SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
|
|
17553
|
-
* platform tokens (botToken/appToken). These payloads MUST NEVER be logged — no
|
|
17554
|
-
* body dump on decode error, no register/ok snapshot debug dump. The daemon
|
|
17555
|
-
* persists them into the owning agent's local `agent.json` (same trust boundary
|
|
17556
|
-
* as hand-authored agents, which already keep tokens there) so integrations
|
|
17557
|
-
* survive a restart with the CP down.
|
|
17558
|
-
*
|
|
17559
|
-
* `signingSecret` is intentionally absent: Socket Mode authenticates with the
|
|
17560
|
-
* app-level token, so the daemon never needs a signing secret.
|
|
17561
|
-
*/
|
|
17562
|
-
/** Trigger match — mirrors the daemon BindRuleConfig.match (agents/agent-schema.ts). */
|
|
17563
|
-
const BindMatch = discriminatedUnion("kind", [
|
|
17564
|
-
object({ kind: literal("mention") }),
|
|
17565
|
-
object({ kind: literal("dm") }),
|
|
17566
|
-
object({
|
|
17567
|
-
kind: literal("keyword"),
|
|
17568
|
-
value: string()
|
|
17569
|
-
}),
|
|
17570
|
-
object({ kind: literal("auto") })
|
|
17571
|
-
]);
|
|
17572
|
-
/** One channel/thread trigger binding — mirrors the daemon BindRuleConfig. */
|
|
17573
|
-
const IntegrationBindRule = object({
|
|
17574
|
-
channel: string().optional(),
|
|
17575
|
-
thread: string().optional(),
|
|
17576
|
-
match: BindMatch
|
|
17577
|
-
});
|
|
17578
|
-
/** The Slack config the daemon needs to open + route a socket (no signingSecret). */
|
|
17579
|
-
const IntegrationSlackConfig = object({
|
|
17580
|
-
botToken: string(),
|
|
17581
|
-
appToken: string(),
|
|
17582
|
-
allowedUserIds: array(string()).default([]),
|
|
17583
|
-
notificationChannelId: string().optional(),
|
|
17584
|
-
bindRules: array(IntegrationBindRule).default([])
|
|
17585
|
-
});
|
|
17586
|
-
/**
|
|
17587
|
-
* One platform integration, owned by exactly one agent. Also the element type of
|
|
17588
|
-
* `RegisterOk.integrations[]` (the per-daemon reconcile set).
|
|
17589
|
-
*/
|
|
17590
|
-
const IntegrationSpec = object({
|
|
17591
|
-
integrationId: string().uuid(),
|
|
17592
|
-
agentId: string().uuid(),
|
|
17593
|
-
platform: literal("slack"),
|
|
17594
|
-
slack: IntegrationSlackConfig
|
|
17595
|
-
});
|
|
17596
|
-
/** C→D EVT — install/update an integration on the owning agent's daemon. */
|
|
17597
|
-
const IntegrationUpsert = IntegrationSpec;
|
|
17598
|
-
/** C→D EVT — remove an integration from the daemon. */
|
|
17599
|
-
const IntegrationRemove = object({ integrationId: string().uuid() });
|
|
17600
|
-
//#endregion
|
|
17601
|
-
//#region ../protocol/dist/frames/register.js
|
|
17602
|
-
/**
|
|
17603
|
-
* Capability upload + the reconcile snapshot — protocol §3.3.
|
|
17604
|
-
*
|
|
17605
|
-
* `register/ok` is the authoritative source of truth: the daemon converges its
|
|
17606
|
-
* local cache to it. CP wins all conflicts, so re-issuing the same snapshot is
|
|
17607
|
-
* idempotent.
|
|
17608
|
-
*/
|
|
17609
|
-
const RegisterReq = object({
|
|
17610
|
-
host: string(),
|
|
17611
|
-
capabilities: object({
|
|
17612
|
-
platforms: array(Platform),
|
|
17613
|
-
runtimes: array(string()),
|
|
17614
|
-
acp: boolean(),
|
|
17615
|
-
features: array(string()).default([])
|
|
17616
|
-
}),
|
|
17617
|
-
maxAgents: number().int(),
|
|
17618
|
-
localState: object({
|
|
17619
|
-
assignments: array(string()),
|
|
17620
|
-
crons: array(string()),
|
|
17621
|
-
leases: array(string())
|
|
17622
|
-
})
|
|
17623
|
-
});
|
|
17624
|
-
const RegisterOk = object({
|
|
17625
|
-
routingEpoch: number().int(),
|
|
17626
|
-
assignments: array(RouteAssign),
|
|
17627
|
-
agents: array(AgentSpec.extend({ agentId: string().uuid() })).default([]),
|
|
17628
|
-
crons: array(CronUpsert),
|
|
17629
|
-
integrations: array(IntegrationSpec).default([]),
|
|
17630
|
-
leases: array(SecretsGrant),
|
|
17631
|
-
drop: object({
|
|
17632
|
-
assignments: array(string()),
|
|
17633
|
-
crons: array(string())
|
|
17634
|
-
})
|
|
17635
|
-
});
|
|
17636
|
-
//#endregion
|
|
17637
|
-
//#region ../protocol/dist/frames/session.js
|
|
17638
|
-
/**
|
|
17639
|
-
* Session read-back (C→D REQ → REP) — the console's on-demand pulls.
|
|
17640
|
-
*
|
|
17641
|
-
* The CP stores NO session data — neither the list nor the bodies. Sessions are
|
|
17642
|
-
* created on the Slack→daemon path and live solely in the daemon's local store
|
|
17643
|
-
* (body-locality, §1/§12). So both the session **list** and a session's chat
|
|
17644
|
-
* **history** are pulled live from the owning daemon(s) and proxied to the
|
|
17645
|
-
* console — never persisted on the CP, never on the orchestration hot path.
|
|
17646
|
-
*
|
|
17647
|
-
* - `session/list`: the daemon's live sessions (CP fans this out to all the org's
|
|
17648
|
-
* online daemons and merges, for "all sessions across the workspace").
|
|
17649
|
-
* - `session/history`: one cursor-paginated page of a session's transcript.
|
|
17650
|
-
*/
|
|
17651
|
-
/**
|
|
17652
|
-
* Per-session token accounting, metered by the daemon from the agent's ACP usage
|
|
17653
|
-
* stream. Token counts are CUMULATIVE across the session's turns (summed from each
|
|
17654
|
-
* turn's `PromptResponse.usage`); context/cost are the LATEST snapshot from the
|
|
17655
|
-
* agent's `usage_update` stream. Every field is optional — a runtime that reports
|
|
17656
|
-
* no usage yields an absent field, not a zero.
|
|
17657
|
-
*/
|
|
17658
|
-
const SessionUsage = object({
|
|
17659
|
-
totalTokens: number().int().optional(),
|
|
17660
|
-
inputTokens: number().int().optional(),
|
|
17661
|
-
outputTokens: number().int().optional(),
|
|
17662
|
-
thoughtTokens: number().int().optional(),
|
|
17663
|
-
cachedReadTokens: number().int().optional(),
|
|
17664
|
-
cachedWriteTokens: number().int().optional(),
|
|
17665
|
-
contextUsed: number().int().optional(),
|
|
17666
|
-
contextSize: number().int().optional(),
|
|
17667
|
-
costAmount: number().optional(),
|
|
17668
|
-
costCurrency: string().optional()
|
|
17669
|
-
});
|
|
17670
|
-
/** One row in the session list (metadata + console metrics; NOT the transcript). */
|
|
17671
|
-
const SessionListItem = object({
|
|
17672
|
-
sessionId: string(),
|
|
17673
|
-
sessionKey: SessionKey,
|
|
17674
|
-
agentId: string().uuid(),
|
|
17675
|
-
title: string().optional(),
|
|
17676
|
-
status: string().optional(),
|
|
17677
|
-
lastActivityAt: string().optional(),
|
|
17678
|
-
usage: SessionUsage.optional(),
|
|
17679
|
-
triggeredBy: string().optional()
|
|
17680
|
-
});
|
|
17681
|
-
/** C→D REQ: list the daemon's sessions (optionally just one agent's). */
|
|
17682
|
-
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
17683
|
-
/** D→C REP (corr = req id): the daemon's current sessions. */
|
|
17684
|
-
const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
17685
|
-
/** One message in a session transcript page (a body — returned only for display). */
|
|
17686
|
-
const SessionMessage = object({
|
|
17687
|
-
seq: number().int(),
|
|
17688
|
-
sender: string(),
|
|
17689
|
-
ts: string(),
|
|
17690
|
-
kind: string(),
|
|
17691
|
-
text: string()
|
|
17692
|
-
});
|
|
17693
|
-
/** C→D REQ: fetch one page of a session's history from the owning daemon. */
|
|
17694
|
-
const SessionHistoryReq = object({
|
|
17695
|
-
sessionId: string(),
|
|
17696
|
-
cursor: string().optional(),
|
|
17697
|
-
limit: number().int().positive().max(200).default(50)
|
|
17698
|
-
});
|
|
17699
|
-
/** D→C REP (corr = the req id): a page of messages + the cursor for the next page. */
|
|
17700
|
-
const SessionHistoryPage = object({
|
|
17701
|
-
sessionId: string(),
|
|
17702
|
-
messages: array(SessionMessage),
|
|
17703
|
-
nextCursor: string().optional()
|
|
17704
|
-
});
|
|
17705
|
-
//#endregion
|
|
17706
|
-
//#region ../protocol/dist/frame.js
|
|
17707
|
-
/**
|
|
17708
|
-
* The single source of truth for the wire: `type` string → payload zod schema.
|
|
17709
|
-
*
|
|
17710
|
-
* Mirrors the frame index in daemon-cp-ws-protocol.md §10 (the 29 numbered
|
|
17711
|
-
* frames) plus the correlated REP types named in the "Reply" column
|
|
17712
|
-
* (`route/assign/ack`, `drain/done`, and the generic `ack` replies) that also
|
|
17713
|
-
* travel on the wire and must be decodable.
|
|
17714
|
-
*
|
|
17715
|
-
* `ws/codec.ts` validates every inbound `payload` against `FRAME_SCHEMAS[type]`;
|
|
17716
|
-
* an unknown `type` → `ErrorFrame{code:"UNKNOWN_FRAME"}` (a REP, not a close).
|
|
17717
|
-
*/
|
|
17718
|
-
const FRAME_SCHEMAS = {
|
|
17719
|
-
auth: AuthReq,
|
|
17720
|
-
"auth/ok": AuthOk,
|
|
17721
|
-
register: RegisterReq,
|
|
17722
|
-
"register/ok": RegisterOk,
|
|
17723
|
-
heartbeat: object({
|
|
17724
|
-
load: object({
|
|
17725
|
-
cpu: number(),
|
|
17726
|
-
mem: number(),
|
|
17727
|
-
agents: number().int()
|
|
17728
|
-
}),
|
|
17729
|
-
health: _enum(["ok", "degraded"]),
|
|
17730
|
-
activeSessions: number().int(),
|
|
17731
|
-
degradedScopes: array(string()).default([])
|
|
17732
|
-
}),
|
|
17733
|
-
"agent/launch": AgentLaunch,
|
|
17734
|
-
"agent/launched": AgentLaunched,
|
|
17735
|
-
"agent/stop": AgentStop,
|
|
17736
|
-
"agent/upsert": AgentUpsert,
|
|
17737
|
-
"agent/remove": AgentRemove,
|
|
17738
|
-
"agent/prompt": AgentPrompt,
|
|
17739
|
-
"agent/prompt/ack": AgentPromptAck,
|
|
17740
|
-
"agent/activity": AgentActivity,
|
|
17741
|
-
"agent/scope-denied": AgentScopeDenied,
|
|
17742
|
-
"route/assign": RouteAssign,
|
|
17743
|
-
"route/assign/ack": RouteAssignAck,
|
|
17744
|
-
"route/update": RouteUpdate,
|
|
17745
|
-
"daemon/drain": Drain,
|
|
17746
|
-
"drain/progress": DrainProgress,
|
|
17747
|
-
"drain/done": DrainDone,
|
|
17748
|
-
"cron/upsert": CronUpsert,
|
|
17749
|
-
"cron/remove": CronRemove,
|
|
17750
|
-
"integration/upsert": IntegrationUpsert,
|
|
17751
|
-
"integration/remove": IntegrationRemove,
|
|
17752
|
-
"secrets/request": SecretsRequest,
|
|
17753
|
-
"secrets/grant": SecretsGrant,
|
|
17754
|
-
"secrets/renew": SecretsRenew,
|
|
17755
|
-
"secrets/revoke": SecretsRevoke,
|
|
17756
|
-
"scope-attestation": ScopeAttestation,
|
|
17757
|
-
"event/session": object({
|
|
17758
|
-
sessionId: string().uuid(),
|
|
17759
|
-
agentId: string().uuid(),
|
|
17760
|
-
launchId: string().uuid(),
|
|
17761
|
-
phase: _enum([
|
|
17762
|
-
"start",
|
|
17763
|
-
"plan",
|
|
17764
|
-
"problem",
|
|
17765
|
-
"end"
|
|
17766
|
-
]),
|
|
17767
|
-
link: string().optional(),
|
|
17768
|
-
summary: string().optional(),
|
|
17769
|
-
ts: string().datetime()
|
|
17770
|
-
}),
|
|
17771
|
-
"usage/report": object({
|
|
17772
|
-
sessionId: string(),
|
|
17773
|
-
agentId: string().uuid(),
|
|
17774
|
-
platform: string().optional(),
|
|
17775
|
-
channel: string().optional(),
|
|
17776
|
-
lastActivityAt: string(),
|
|
17777
|
-
usage: SessionUsage
|
|
17778
|
-
}),
|
|
17779
|
-
"facts/runtime-profile": object({
|
|
17780
|
-
runtime: string(),
|
|
17781
|
-
version: string(),
|
|
17782
|
-
models: array(string()),
|
|
17783
|
-
contextWindow: number().int().optional(),
|
|
17784
|
-
acpSupport: _enum([
|
|
17785
|
-
"full",
|
|
17786
|
-
"partial",
|
|
17787
|
-
"none"
|
|
17788
|
-
]),
|
|
17789
|
-
toolCalling: boolean()
|
|
17790
|
-
}),
|
|
17791
|
-
"session/list": SessionListReq,
|
|
17792
|
-
"session/list/page": SessionListPage,
|
|
17793
|
-
"session/history": SessionHistoryReq,
|
|
17794
|
-
"session/history/page": SessionHistoryPage,
|
|
17795
|
-
"config/push": object({ keys: record(string(), unknown()) }),
|
|
17796
|
-
"daemon/restart": object({
|
|
17797
|
-
reason: string(),
|
|
17798
|
-
drainFirst: boolean().default(true)
|
|
17799
|
-
}),
|
|
17800
|
-
"daemon/upgrade": object({
|
|
17801
|
-
targetVersion: string(),
|
|
17802
|
-
drainFirst: boolean().default(true)
|
|
17803
|
-
}),
|
|
17804
|
-
"daemon/control/ack": object({
|
|
17805
|
-
accepted: boolean(),
|
|
17806
|
-
willDrainUntil: string().datetime().optional()
|
|
17807
|
-
}),
|
|
17808
|
-
ack: object({
|
|
17809
|
-
ok: boolean(),
|
|
17810
|
-
reason: string().optional()
|
|
17811
|
-
}),
|
|
17812
|
-
error: object({
|
|
17813
|
-
code: _enum([
|
|
17814
|
-
"UNKNOWN_FRAME",
|
|
17815
|
-
"FRAME_TOO_LARGE",
|
|
17816
|
-
"PROTOCOL_STATE",
|
|
17817
|
-
"BAD_PAYLOAD",
|
|
17818
|
-
"AUTH_FAILED",
|
|
17819
|
-
"ATTESTATION_INVALID",
|
|
17820
|
-
"STALE_EPOCH",
|
|
17821
|
-
"STALE_LAUNCH",
|
|
17822
|
-
"SEQ_GAP",
|
|
17823
|
-
"NO_SESSION",
|
|
17824
|
-
"HELD",
|
|
17825
|
-
"SCOPE_DENIED",
|
|
17826
|
-
"LEASE_EXPIRED",
|
|
17827
|
-
"LEASE_DENIED",
|
|
17828
|
-
"RATE_LIMITED",
|
|
17829
|
-
"INTERNAL"
|
|
17830
|
-
]),
|
|
17831
|
-
message: string(),
|
|
17832
|
-
retryable: boolean(),
|
|
17833
|
-
details: record(string(), unknown()).optional()
|
|
17834
|
-
})
|
|
17835
|
-
};
|
|
17836
|
-
Object.keys(FRAME_SCHEMAS);
|
|
17837
|
-
/**
|
|
17838
|
-
* Builds the envelope schema for one frame `type` with a `type` literal and the
|
|
17839
|
-
* typed payload, so the discriminated union infers `payload` precisely.
|
|
17840
|
-
*/
|
|
17841
|
-
function frame(type, payload) {
|
|
17842
|
-
return object({
|
|
17843
|
-
v: literal(1),
|
|
17844
|
-
id: string().uuid(),
|
|
17845
|
-
ts: string().datetime(),
|
|
17846
|
-
type: literal(type),
|
|
17847
|
-
corr: string().uuid().optional(),
|
|
17848
|
-
payload
|
|
17849
|
-
});
|
|
17850
|
-
}
|
|
17851
|
-
discriminatedUnion("type", [
|
|
17852
|
-
frame("auth", FRAME_SCHEMAS["auth"]),
|
|
17853
|
-
frame("auth/ok", FRAME_SCHEMAS["auth/ok"]),
|
|
17854
|
-
frame("register", FRAME_SCHEMAS["register"]),
|
|
17855
|
-
frame("register/ok", FRAME_SCHEMAS["register/ok"]),
|
|
17856
|
-
frame("heartbeat", FRAME_SCHEMAS["heartbeat"]),
|
|
17857
|
-
frame("agent/launch", FRAME_SCHEMAS["agent/launch"]),
|
|
17858
|
-
frame("agent/launched", FRAME_SCHEMAS["agent/launched"]),
|
|
17859
|
-
frame("agent/stop", FRAME_SCHEMAS["agent/stop"]),
|
|
17860
|
-
frame("agent/upsert", FRAME_SCHEMAS["agent/upsert"]),
|
|
17861
|
-
frame("agent/remove", FRAME_SCHEMAS["agent/remove"]),
|
|
17862
|
-
frame("agent/prompt", FRAME_SCHEMAS["agent/prompt"]),
|
|
17863
|
-
frame("agent/prompt/ack", FRAME_SCHEMAS["agent/prompt/ack"]),
|
|
17864
|
-
frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
|
|
17865
|
-
frame("agent/scope-denied", FRAME_SCHEMAS["agent/scope-denied"]),
|
|
17866
|
-
frame("route/assign", FRAME_SCHEMAS["route/assign"]),
|
|
17867
|
-
frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
|
|
17868
|
-
frame("route/update", FRAME_SCHEMAS["route/update"]),
|
|
17869
|
-
frame("daemon/drain", FRAME_SCHEMAS["daemon/drain"]),
|
|
17870
|
-
frame("drain/progress", FRAME_SCHEMAS["drain/progress"]),
|
|
17871
|
-
frame("drain/done", FRAME_SCHEMAS["drain/done"]),
|
|
17872
|
-
frame("cron/upsert", FRAME_SCHEMAS["cron/upsert"]),
|
|
17873
|
-
frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
|
|
17874
|
-
frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
|
|
17875
|
-
frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
|
|
17876
|
-
frame("secrets/request", FRAME_SCHEMAS["secrets/request"]),
|
|
17877
|
-
frame("secrets/grant", FRAME_SCHEMAS["secrets/grant"]),
|
|
17878
|
-
frame("secrets/renew", FRAME_SCHEMAS["secrets/renew"]),
|
|
17879
|
-
frame("secrets/revoke", FRAME_SCHEMAS["secrets/revoke"]),
|
|
17880
|
-
frame("scope-attestation", FRAME_SCHEMAS["scope-attestation"]),
|
|
17881
|
-
frame("event/session", FRAME_SCHEMAS["event/session"]),
|
|
17882
|
-
frame("usage/report", FRAME_SCHEMAS["usage/report"]),
|
|
17883
|
-
frame("facts/runtime-profile", FRAME_SCHEMAS["facts/runtime-profile"]),
|
|
17884
|
-
frame("session/list", FRAME_SCHEMAS["session/list"]),
|
|
17885
|
-
frame("session/list/page", FRAME_SCHEMAS["session/list/page"]),
|
|
17886
|
-
frame("session/history", FRAME_SCHEMAS["session/history"]),
|
|
17887
|
-
frame("session/history/page", FRAME_SCHEMAS["session/history/page"]),
|
|
17888
|
-
frame("config/push", FRAME_SCHEMAS["config/push"]),
|
|
17889
|
-
frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
|
|
17890
|
-
frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
|
|
17891
|
-
frame("daemon/control/ack", FRAME_SCHEMAS["daemon/control/ack"]),
|
|
17892
|
-
frame("ack", FRAME_SCHEMAS["ack"]),
|
|
17893
|
-
frame("error", FRAME_SCHEMAS["error"])
|
|
17894
|
-
]);
|
|
17895
|
-
//#endregion
|
|
17896
|
-
//#region ../protocol/dist/codec.js
|
|
17897
|
-
/** Soft cap per frame — 256 KiB (protocol §1). Over this → FRAME_TOO_LARGE. */
|
|
17898
|
-
const MAX_FRAME_BYTES = 256 * 1024;
|
|
17899
|
-
const textEncoder = new TextEncoder();
|
|
17900
|
-
function byteLength(text) {
|
|
17901
|
-
return textEncoder.encode(text).length;
|
|
17902
|
-
}
|
|
17903
|
-
function extractControlExt(json) {
|
|
17904
|
-
if (typeof json !== "object" || json === null) return void 0;
|
|
17905
|
-
const o = json;
|
|
17906
|
-
const ext = {};
|
|
17907
|
-
if (typeof o.epoch === "number") ext.epoch = o.epoch;
|
|
17908
|
-
if (typeof o.seq === "number") ext.seq = o.seq;
|
|
17909
|
-
if (typeof o.agentId === "string") ext.agentId = o.agentId;
|
|
17910
|
-
if (typeof o.launchId === "string") ext.launchId = o.launchId;
|
|
17911
|
-
return Object.keys(ext).length > 0 ? ext : void 0;
|
|
17912
|
-
}
|
|
17913
|
-
function decodeEnvelope(text) {
|
|
17914
|
-
if (byteLength(text) > 262144) return {
|
|
17915
|
-
ok: false,
|
|
17916
|
-
id: NIL_UUID,
|
|
17917
|
-
msg: "FRAME_TOO_LARGE"
|
|
17918
|
-
};
|
|
17919
|
-
let json;
|
|
17920
|
-
try {
|
|
17921
|
-
json = JSON.parse(text);
|
|
17922
|
-
} catch {
|
|
17923
|
-
return {
|
|
17924
|
-
ok: false,
|
|
17925
|
-
id: NIL_UUID,
|
|
17926
|
-
msg: "invalid json"
|
|
17927
|
-
};
|
|
17928
|
-
}
|
|
17929
|
-
const env = Envelope.safeParse(json);
|
|
17930
|
-
if (!env.success) return {
|
|
17931
|
-
ok: false,
|
|
17932
|
-
id: typeof json === "object" && json !== null && typeof json.id === "string" ? json.id : NIL_UUID,
|
|
17933
|
-
msg: env.error.message
|
|
17934
|
-
};
|
|
17935
|
-
const schema = FRAME_SCHEMAS[env.data.type];
|
|
17936
|
-
if (!schema) return {
|
|
17937
|
-
ok: false,
|
|
17938
|
-
id: env.data.id,
|
|
17939
|
-
msg: "UNKNOWN_FRAME"
|
|
17940
|
-
};
|
|
17941
|
-
const payload = schema.safeParse(env.data.payload);
|
|
17942
|
-
if (!payload.success) return {
|
|
17943
|
-
ok: false,
|
|
17944
|
-
id: env.data.id,
|
|
17945
|
-
msg: payload.error.message
|
|
17946
|
-
};
|
|
17947
|
-
const ext = extractControlExt(json);
|
|
17948
|
-
return {
|
|
17949
|
-
ok: true,
|
|
17950
|
-
frame: {
|
|
17951
|
-
...env.data,
|
|
17952
|
-
payload: payload.data
|
|
17953
|
-
},
|
|
17954
|
-
...ext ? { ext } : {}
|
|
17955
|
-
};
|
|
17956
|
-
}
|
|
17957
|
-
function buildEnvelope(type, payload, opts = {}) {
|
|
17958
|
-
return {
|
|
17959
|
-
v: 1,
|
|
17960
|
-
id: opts.id ?? randomUUID(),
|
|
17961
|
-
ts: opts.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
17962
|
-
type,
|
|
17963
|
-
payload,
|
|
17964
|
-
...opts.corr ? { corr: opts.corr } : {},
|
|
17965
|
-
...opts.ext ?? {}
|
|
17966
|
-
};
|
|
17967
|
-
}
|
|
17968
|
-
function encode(frame) {
|
|
17969
|
-
return JSON.stringify(frame);
|
|
17970
|
-
}
|
|
17971
|
-
//#endregion
|
|
17972
|
-
//#region ../protocol/dist/index.js
|
|
17973
|
-
/**
|
|
17974
|
-
* Narrowing guard factory: `isFrame("auth")(frame)` narrows a decoded
|
|
17975
|
-
* `AnyFrame` to the member whose `type` matches.
|
|
17976
|
-
*/
|
|
17977
|
-
function isFrame(type) {
|
|
17978
|
-
return (frame) => frame.type === type;
|
|
17979
|
-
}
|
|
17980
|
-
//#endregion
|
|
17981
18013
|
//#region ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js
|
|
17982
18014
|
var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
17983
18015
|
const BINARY_TYPES = [
|
|
@@ -82373,7 +82405,7 @@ function applySpecFields(raw, spec, opts) {
|
|
|
82373
82405
|
const existing = typeof raw.workspace === "object" && raw.workspace !== null ? raw.workspace : {};
|
|
82374
82406
|
existing.mode = mapWorkspaceMode(ws.mode);
|
|
82375
82407
|
if (ws.mode === "github") {
|
|
82376
|
-
existing.gitRepo = ws.gitRepo;
|
|
82408
|
+
existing.gitRepo = normalizeGitUrl(ws.gitRepo);
|
|
82377
82409
|
existing.gitBranch = ws.branch;
|
|
82378
82410
|
}
|
|
82379
82411
|
if (opts.creating && existing.path === void 0) existing.path = join(opts.agentDir, "workspace");
|