@agentconnect.md/cli 1.15.0-rc.7 → 1.17.0-rc.5

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 CHANGED
@@ -7885,6 +7885,27 @@ const MemoryRecallPolicy = object({
7885
7885
  timeoutMs: number().int().positive().max(MEMORY_RECALL_HARD_LIMITS.timeoutMs).default(MEMORY_RECALL_DEFAULTS.timeoutMs)
7886
7886
  }).strict();
7887
7887
  const MemoryCapturePolicy = object({ mode: _enum(["turn", "manual"]).default("manual") }).strict();
7888
+ /**
7889
+ * Dreaming — periodic offline consolidation of the MANAGED store
7890
+ * (design: docs/designs/memory-dreaming.md). Valid only with
7891
+ * `provider: 'managed'`; the daemon stages a rebuilt store per dream and the
7892
+ * user (or the gated auto-adopt path) reviews and adopts it. Bounds mirror the
7893
+ * design: sessionWindow ≤ 100 mined transcripts, instructions ≤ 4096 chars.
7894
+ */
7895
+ const MemoryDreamingPolicy = object({
7896
+ enabled: boolean(),
7897
+ /** How many recent sessions to mine (default 20). */
7898
+ sessionWindow: number().int().min(1).max(100).optional(),
7899
+ /** Cron expression for scheduled dreams (same syntax as agent crons). */
7900
+ schedule: string().min(1).max(128).optional(),
7901
+ /** Operator steering text applied through the whole dream pipeline. */
7902
+ instructions: string().max(4096).optional(),
7903
+ /** Also mine reusable procedures into candidate skills (never auto-installed). */
7904
+ mineSkills: boolean().optional(),
7905
+ /** Adopt the staged store automatically on completion. Admissible only on
7906
+ * runtimes with a trusted extraction channel; the daemon enforces that. */
7907
+ autoAdopt: boolean().optional()
7908
+ }).strict();
7888
7909
  /** Agent-facing provider selection. External bindings carry policy, never endpoints or secrets. */
7889
7910
  const AgentMemoryBinding = union([object({
7890
7911
  provider: _enum([
@@ -7892,8 +7913,15 @@ const AgentMemoryBinding = union([object({
7892
7913
  "native",
7893
7914
  "managed"
7894
7915
  ]),
7895
- autoDistill: boolean().optional()
7896
- }).strict(), object({
7916
+ autoDistill: boolean().optional(),
7917
+ dreaming: MemoryDreamingPolicy.optional()
7918
+ }).strict().superRefine((binding, ctx) => {
7919
+ if (binding.dreaming && binding.provider !== "managed") ctx.addIssue({
7920
+ code: "custom",
7921
+ path: ["dreaming"],
7922
+ message: "dreaming is only supported with the managed memory provider"
7923
+ });
7924
+ }), object({
7897
7925
  provider: literal("external"),
7898
7926
  connectionId: string().uuid(),
7899
7927
  recall: MemoryRecallPolicy.default({
@@ -8152,9 +8180,9 @@ const IntegrationChannels = object({
8152
8180
  * Agent lifecycle (protocol §4.4, §7.4, §8).
8153
8181
  *
8154
8182
  * There is no CP→daemon prompt-delivery frame: the daemon prompts an agent from
8155
- * its own ingress (platform adapters, and webchat over its own `webchat/*`
8156
- * frames), never the CP. The old `agent/prompt` + per-agent `seq` machinery was
8157
- * reserved infrastructure with no live caller and has been removed.
8183
+ * its own ingress (platform adapters or relay `rd/*` delivery), never the CP.
8184
+ * The old `agent/prompt` + per-agent `seq` machinery was reserved infrastructure
8185
+ * with no live caller and has been removed.
8158
8186
  */
8159
8187
  /**
8160
8188
  * Where the agent runs. Two modes; the **path is always daemon-generated** —
@@ -8213,6 +8241,22 @@ discriminatedUnion("kind", [
8213
8241
  object({ kind: literal("image") })
8214
8242
  ]);
8215
8243
  /**
8244
+ * A self-contained skill source the daemon installs via `npx skills` after the
8245
+ * workspace is ready and before the ACP host spawns (design: shared-skills.md §4).
8246
+ * The source definition rides INLINE on the AgentSpec (and lands in agent.json,
8247
+ * like mcpServers) — there is no separate skillsource frame or daemon-side def
8248
+ * cache. The CP resolves each agent's enabled org-level `SkillSource` rows into
8249
+ * these entries when it builds the spec.
8250
+ */
8251
+ const SkillArg = string().min(1).refine((s) => !s.startsWith("-"), { message: "must not start with \"-\"" });
8252
+ const AgentSkillEntry = object({
8253
+ name: string(),
8254
+ source: SkillArg,
8255
+ ref: string().optional(),
8256
+ subDir: string().optional(),
8257
+ skills: array(SkillArg).default([])
8258
+ });
8259
+ /**
8216
8260
  * The editable agent definition the CP owns and the daemon needs to run it:
8217
8261
  * prompt + runtime selection. The launch protocol carries this config and the
8218
8262
  * daemon synthesizes the system prompt locally; `description` IS the prompt.
@@ -8233,16 +8277,21 @@ const AgentSpec = object({
8233
8277
  "medium",
8234
8278
  "high"
8235
8279
  ]).optional(),
8280
+ showFooter: boolean().optional(),
8236
8281
  fastMode: boolean().optional(),
8237
8282
  permissionMode: string().nullable().optional(),
8283
+ allowRuntimeChangesInChat: boolean().optional(),
8238
8284
  pause: boolean().optional(),
8239
8285
  workspace: AgentWorkspace.optional(),
8240
8286
  env: record(string(), string()).optional(),
8241
8287
  secrets: record(string(), string()).optional(),
8242
8288
  memory: AgentMemoryBinding.optional(),
8243
8289
  mcpServers: array(string()).default([]),
8290
+ skills: array(AgentSkillEntry).default([]),
8244
8291
  callPolicy: _enum(["all", "selected"]).optional(),
8245
8292
  allowedCallerAgentIds: array(string()).default([]),
8293
+ outboundPolicy: _enum(["all", "selected"]).optional(),
8294
+ allowedTargetAgentIds: array(string()).optional(),
8246
8295
  introduceOnJoin: boolean().optional(),
8247
8296
  restrictFileAccess: boolean().optional()
8248
8297
  });
@@ -8325,6 +8374,36 @@ const AgentScopeDenied = object({
8325
8374
  launchId: string().uuid(),
8326
8375
  capability: string()
8327
8376
  });
8377
+ /** Editor approval queue. The daemon owns the live resolver and durable local
8378
+ * history; the Control Plane only proxies this bounded, secret-masked summary. */
8379
+ const AgentPermissionRequestRecord = object({
8380
+ id: string().uuid(),
8381
+ agentId: string().uuid(),
8382
+ createdAt: string().datetime(),
8383
+ requesterId: string().nullable(),
8384
+ requesterName: string().nullable(),
8385
+ command: string().max(240),
8386
+ status: _enum([
8387
+ "pending",
8388
+ "allowed",
8389
+ "denied",
8390
+ "expired"
8391
+ ]),
8392
+ resolvedAt: string().datetime().nullable()
8393
+ });
8394
+ const AgentPermissionRequestList = object({
8395
+ agentId: string().uuid(),
8396
+ limit: number().int().min(1).max(100).default(50)
8397
+ });
8398
+ const AgentPermissionRequestPage = object({
8399
+ agentId: string().uuid(),
8400
+ requests: array(AgentPermissionRequestRecord)
8401
+ });
8402
+ const AgentPermissionDecision = object({
8403
+ agentId: string().uuid(),
8404
+ requestId: string().uuid(),
8405
+ decision: _enum(["allow", "deny"])
8406
+ });
8328
8407
  //#endregion
8329
8408
  //#region ../protocol/dist/frames/mcpserver.js
8330
8409
  /**
@@ -8422,6 +8501,10 @@ const CollabAgentPlacement = object({
8422
8501
  botAppId: string().optional(),
8423
8502
  callPolicy: _enum(["all", "selected"]).default("all"),
8424
8503
  allowedCallerAgentIds: array(string()).default([]),
8504
+ /** Caller-side authorization. Effective A→B access requires A's outbound
8505
+ * policy to admit B and B's inbound call policy to admit A. */
8506
+ outboundPolicy: _enum(["all", "selected"]).default("all"),
8507
+ allowedTargetAgentIds: array(string()).default([]),
8425
8508
  name: string().optional(),
8426
8509
  displayName: string().optional()
8427
8510
  });
@@ -8474,7 +8557,7 @@ const GitCommitIdentity = object({
8474
8557
  * `INTERNAL`.
8475
8558
  */
8476
8559
  /**
8477
- * Token capability classes (webhook-triggers-and-github-events.md P2.5 回写).
8560
+ * Token capability classes (webhook-triggers-and-github-events.md P2.5 write-back).
8478
8561
  * `contents` is the git data plane (the pre-capabilities behavior); `issues` /
8479
8562
  * `pull_requests` buy the agent `gh` write-back (issue/PR comments), and
8480
8563
  * `actions` buys GitHub Actions inspection/execution. Every
@@ -8653,6 +8736,7 @@ const GithubHookMetadata = object({
8653
8736
  mergeCommitSha: string().min(1).optional(),
8654
8737
  isDraft: boolean().optional(),
8655
8738
  baseChanged: boolean().optional(),
8739
+ explicitReviewRequest: boolean().optional(),
8656
8740
  reviewCommentId: HookBigIntString.optional(),
8657
8741
  reviewThreadRootCommentId: HookBigIntString.optional()
8658
8742
  }).superRefine((value, ctx) => {
@@ -9253,6 +9337,143 @@ const MemoryRecordHistoryPage = object({
9253
9337
  events: array(MemoryPluginHistoryEvent).max(20),
9254
9338
  nextCursor: OptionalRecordCursor
9255
9339
  }).strict();
9340
+ /**
9341
+ * Memory dreaming (C→D REQ → REP) — offline consolidation jobs over the
9342
+ * MANAGED store (design: docs/designs/memory-dreaming.md).
9343
+ *
9344
+ * A dream reads a snapshot of `<agent-root>/memory/` plus recent session
9345
+ * transcripts and stages a rebuilt store under
9346
+ * `<agent-root>/memory-dreams/<dreamId>/`; the live store is never modified by
9347
+ * a running dream. The CP relays these frames and persists at most the
9348
+ * metadata (`DreamInfo`) — staged bodies transit only as correlated
9349
+ * request/reply payloads, exactly like `memory/read` (body-locality).
9350
+ *
9351
+ * - `memory/dream/start|cancel|adopt|discard`: lifecycle commands; each REP is
9352
+ * the updated `DreamInfo` so the console can refresh without a second read.
9353
+ * - `memory/dream/list|get`: job metadata for the history view.
9354
+ * - `memory/dream/files` + `memory/dream/file/read`: browse the STAGED output
9355
+ * tree (byte-sliced like `memory/read`; same UTF-8-boundary semantics).
9356
+ * - `memory/dream/skill/accept|dismiss`: review actions on mined skill
9357
+ * candidates (skills are never auto-adopted; see the design §7).
9358
+ */
9359
+ const DreamStatus = _enum([
9360
+ "pending",
9361
+ "running",
9362
+ "completed",
9363
+ "failed",
9364
+ "canceled",
9365
+ "adopted",
9366
+ "discarded"
9367
+ ]);
9368
+ const DreamTrigger = _enum([
9369
+ "manual",
9370
+ "schedule",
9371
+ "auto"
9372
+ ]);
9373
+ /** Review state of one mined skill candidate (design §7). */
9374
+ const DreamSkillInfo = object({
9375
+ name: string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/, "skill name must be lowercase kebab-case"),
9376
+ description: string().max(1024),
9377
+ state: _enum([
9378
+ "proposed",
9379
+ "accepted",
9380
+ "dismissed"
9381
+ ])
9382
+ }).strict();
9383
+ /** Dream job metadata (never staged bodies). The only dream shape the CP may persist. */
9384
+ const DreamInfo = object({
9385
+ dreamId: string().min(1).max(128),
9386
+ agentId: string().min(1),
9387
+ status: DreamStatus,
9388
+ trigger: DreamTrigger,
9389
+ sessionIds: array(string().min(1)).max(100),
9390
+ snapshotDigest: string().min(1).max(128),
9391
+ instructions: string().max(4096).optional(),
9392
+ skills: array(DreamSkillInfo).max(16).optional(),
9393
+ usage: object({
9394
+ inputBytes: number().int().nonnegative(),
9395
+ outputBytes: number().int().nonnegative()
9396
+ }).strict().optional(),
9397
+ error: object({
9398
+ type: string().min(1).max(128),
9399
+ message: string().max(2048)
9400
+ }).strict().optional(),
9401
+ createdAt: string(),
9402
+ endedAt: string().optional()
9403
+ }).strict();
9404
+ /** C→D REQ: start a dream for one agent (managed provider only). */
9405
+ const DreamStartReq = object({
9406
+ agentId: string().min(1),
9407
+ trigger: DreamTrigger.default("manual"),
9408
+ /** Per-run overrides of the agent's configured dreaming policy. */
9409
+ sessionWindow: number().int().min(1).max(100).optional(),
9410
+ instructions: string().max(4096).optional()
9411
+ }).strict();
9412
+ /** D→C REP (corr = the req id): the created/updated dream job. Shared by every
9413
+ * lifecycle command so the console always renders from one shape. */
9414
+ const DreamState = object({ dream: DreamInfo }).strict();
9415
+ /** C→D REQ: cancel a pending|running dream. */
9416
+ const DreamCancelReq = object({
9417
+ agentId: string().min(1),
9418
+ dreamId: string().min(1)
9419
+ }).strict();
9420
+ /** C→D REQ: list dream jobs for one agent (newest first; bounded). */
9421
+ const DreamListReq = object({
9422
+ agentId: string().min(1),
9423
+ limit: number().int().positive().max(50).default(20)
9424
+ }).strict();
9425
+ /** D→C REP (corr = the req id). */
9426
+ const DreamListPage = object({
9427
+ agentId: string().min(1),
9428
+ dreams: array(DreamInfo).max(50)
9429
+ }).strict();
9430
+ /** C→D REQ: fetch one dream job's metadata. */
9431
+ const DreamGetReq = DreamCancelReq;
9432
+ /** C→D REQ: adopt a completed dream's staged store (atomic swap + backup). */
9433
+ const DreamAdoptReq = object({
9434
+ agentId: string().min(1),
9435
+ dreamId: string().min(1),
9436
+ /** Adopt even when the live store changed since the snapshot (fence override). */
9437
+ force: boolean().default(false)
9438
+ }).strict();
9439
+ /** C→D REQ: discard a terminal dream's staged output. */
9440
+ const DreamDiscardReq = DreamCancelReq;
9441
+ /** C→D REQ: list the STAGED output files of one dream (review surface). */
9442
+ const DreamFilesReq = DreamCancelReq;
9443
+ /** D→C REP (corr = the req id): staged store listing (or `exists:false`). */
9444
+ const DreamFilesPage = object({
9445
+ agentId: string(),
9446
+ dreamId: string(),
9447
+ exists: boolean(),
9448
+ entries: array(MemoryEntry)
9449
+ }).strict();
9450
+ /** C→D REQ: read one byte slice of a STAGED dream file (semantics of `memory/read`). */
9451
+ const DreamFileReadReq = object({
9452
+ agentId: string().min(1),
9453
+ dreamId: string().min(1),
9454
+ path: string().default(MEMORY_INDEX),
9455
+ offset: number().int().nonnegative().default(0),
9456
+ limit: number().int().positive().max(65536).default(65536)
9457
+ }).strict();
9458
+ /** D→C REP (corr = the req id): the staged file slice (fields as `memory/read/content`). */
9459
+ const DreamFileReadContent = object({
9460
+ agentId: string(),
9461
+ dreamId: string(),
9462
+ path: string(),
9463
+ exists: boolean(),
9464
+ size: number().int().nonnegative().optional(),
9465
+ mtime: string().optional(),
9466
+ content: string().optional(),
9467
+ offset: number().int().nonnegative().optional(),
9468
+ nextOffset: number().int().nonnegative().optional(),
9469
+ truncated: boolean().optional()
9470
+ }).strict();
9471
+ /** C→D REQ: accept or dismiss one mined skill candidate (design §7). */
9472
+ const DreamSkillReviewReq = object({
9473
+ agentId: string().min(1),
9474
+ dreamId: string().min(1),
9475
+ name: string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/)
9476
+ }).strict();
9256
9477
  //#endregion
9257
9478
  //#region ../protocol/dist/frames/telemetry.js
9258
9479
  /**
@@ -9345,6 +9566,13 @@ const EffortOption = object({
9345
9566
  name: string().optional(),
9346
9567
  description: string().optional()
9347
9568
  });
9569
+ /** One runtime permission-mode choice. The display metadata is forwarded
9570
+ * verbatim from the runtime's ACP `category: "mode"` select option. */
9571
+ const PermissionModeOption = object({
9572
+ value: string(),
9573
+ name: string().optional(),
9574
+ description: string().optional()
9575
+ });
9348
9576
  /**
9349
9577
  * A runtime's discovered model × config capability matrix. One shape shared by
9350
9578
  * the wire, the CP's `runtime_profile.modelCatalog` JSONB column, and the DTO —
@@ -9359,10 +9587,7 @@ const RuntimeModelCatalog = object({
9359
9587
  fastMode: boolean().optional()
9360
9588
  })).max(128),
9361
9589
  defaultModel: string().optional(),
9362
- permissionModes: array(object({
9363
- value: string(),
9364
- name: string().optional()
9365
- })).optional(),
9590
+ permissionModes: array(PermissionModeOption).optional(),
9366
9591
  defaultPermissionMode: string().optional(),
9367
9592
  source: _enum(["native", "acp"]),
9368
9593
  observedAt: string().datetime()
@@ -9428,6 +9653,9 @@ const FRAME_SCHEMAS = {
9428
9653
  "agent/activate": AgentActivate,
9429
9654
  "agent/activity": AgentActivity,
9430
9655
  "agent/scope-denied": AgentScopeDenied,
9656
+ "agent/permission-requests": AgentPermissionRequestList,
9657
+ "agent/permission-requests/page": AgentPermissionRequestPage,
9658
+ "agent/permission-decision": AgentPermissionDecision,
9431
9659
  "route/assign": RouteAssign,
9432
9660
  "route/assign/ack": RouteAssignAck,
9433
9661
  "route/update": RouteUpdate,
@@ -9506,6 +9734,26 @@ const FRAME_SCHEMAS = {
9506
9734
  "memory/record/delete/result": MemoryRecordDeleteResult,
9507
9735
  "memory/record/history": MemoryRecordHistoryReq,
9508
9736
  "memory/record/history/page": MemoryRecordHistoryPage,
9737
+ "memory/dream/start": DreamStartReq,
9738
+ "memory/dream/start/ok": DreamState,
9739
+ "memory/dream/cancel": DreamCancelReq,
9740
+ "memory/dream/cancel/ok": DreamState,
9741
+ "memory/dream/list": DreamListReq,
9742
+ "memory/dream/list/page": DreamListPage,
9743
+ "memory/dream/get": DreamGetReq,
9744
+ "memory/dream/get/result": DreamState,
9745
+ "memory/dream/adopt": DreamAdoptReq,
9746
+ "memory/dream/adopt/ok": DreamState,
9747
+ "memory/dream/discard": DreamDiscardReq,
9748
+ "memory/dream/discard/ok": DreamState,
9749
+ "memory/dream/files": DreamFilesReq,
9750
+ "memory/dream/files/page": DreamFilesPage,
9751
+ "memory/dream/file/read": DreamFileReadReq,
9752
+ "memory/dream/file/read/content": DreamFileReadContent,
9753
+ "memory/dream/skill/accept": DreamSkillReviewReq,
9754
+ "memory/dream/skill/accept/ok": DreamState,
9755
+ "memory/dream/skill/dismiss": DreamSkillReviewReq,
9756
+ "memory/dream/skill/dismiss/ok": DreamState,
9509
9757
  "config/push": object({ keys: record(string(), unknown()) }),
9510
9758
  "daemon/restart": object({
9511
9759
  reason: string(),
@@ -9588,6 +9836,9 @@ discriminatedUnion("type", [
9588
9836
  frame("agent/activate", FRAME_SCHEMAS["agent/activate"]),
9589
9837
  frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
9590
9838
  frame("agent/scope-denied", FRAME_SCHEMAS["agent/scope-denied"]),
9839
+ frame("agent/permission-requests", FRAME_SCHEMAS["agent/permission-requests"]),
9840
+ frame("agent/permission-requests/page", FRAME_SCHEMAS["agent/permission-requests/page"]),
9841
+ frame("agent/permission-decision", FRAME_SCHEMAS["agent/permission-decision"]),
9591
9842
  frame("route/assign", FRAME_SCHEMAS["route/assign"]),
9592
9843
  frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
9593
9844
  frame("route/update", FRAME_SCHEMAS["route/update"]),
@@ -9662,6 +9913,26 @@ discriminatedUnion("type", [
9662
9913
  frame("memory/record/delete/result", FRAME_SCHEMAS["memory/record/delete/result"]),
9663
9914
  frame("memory/record/history", FRAME_SCHEMAS["memory/record/history"]),
9664
9915
  frame("memory/record/history/page", FRAME_SCHEMAS["memory/record/history/page"]),
9916
+ frame("memory/dream/start", FRAME_SCHEMAS["memory/dream/start"]),
9917
+ frame("memory/dream/start/ok", FRAME_SCHEMAS["memory/dream/start/ok"]),
9918
+ frame("memory/dream/cancel", FRAME_SCHEMAS["memory/dream/cancel"]),
9919
+ frame("memory/dream/cancel/ok", FRAME_SCHEMAS["memory/dream/cancel/ok"]),
9920
+ frame("memory/dream/list", FRAME_SCHEMAS["memory/dream/list"]),
9921
+ frame("memory/dream/list/page", FRAME_SCHEMAS["memory/dream/list/page"]),
9922
+ frame("memory/dream/get", FRAME_SCHEMAS["memory/dream/get"]),
9923
+ frame("memory/dream/get/result", FRAME_SCHEMAS["memory/dream/get/result"]),
9924
+ frame("memory/dream/adopt", FRAME_SCHEMAS["memory/dream/adopt"]),
9925
+ frame("memory/dream/adopt/ok", FRAME_SCHEMAS["memory/dream/adopt/ok"]),
9926
+ frame("memory/dream/discard", FRAME_SCHEMAS["memory/dream/discard"]),
9927
+ frame("memory/dream/discard/ok", FRAME_SCHEMAS["memory/dream/discard/ok"]),
9928
+ frame("memory/dream/files", FRAME_SCHEMAS["memory/dream/files"]),
9929
+ frame("memory/dream/files/page", FRAME_SCHEMAS["memory/dream/files/page"]),
9930
+ frame("memory/dream/file/read", FRAME_SCHEMAS["memory/dream/file/read"]),
9931
+ frame("memory/dream/file/read/content", FRAME_SCHEMAS["memory/dream/file/read/content"]),
9932
+ frame("memory/dream/skill/accept", FRAME_SCHEMAS["memory/dream/skill/accept"]),
9933
+ frame("memory/dream/skill/accept/ok", FRAME_SCHEMAS["memory/dream/skill/accept/ok"]),
9934
+ frame("memory/dream/skill/dismiss", FRAME_SCHEMAS["memory/dream/skill/dismiss"]),
9935
+ frame("memory/dream/skill/dismiss/ok", FRAME_SCHEMAS["memory/dream/skill/dismiss/ok"]),
9665
9936
  frame("config/push", FRAME_SCHEMAS["config/push"]),
9666
9937
  frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
9667
9938
  frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
@@ -9678,7 +9949,7 @@ discriminatedUnion("type", [
9678
9949
  * (`frames/relay-cp.ts` / `frames/relay-daemon.ts` over their OWN maps) all
9679
9950
  * decode the same envelope shape but accept disjoint frame families — a relay
9680
9951
  * socket must answer a daemon↔CP frame with `UNKNOWN_FRAME`, and vice versa
9681
- * (shared-bot-relay.md §8 "独立帧 union,不混入 daemon 协议").
9952
+ * (shared-bot-relay.md §8 "standalone frame union, not mixed into the daemon protocol").
9682
9953
  */
9683
9954
  /** Soft cap per frame — 256 KiB (protocol §1). Over this → FRAME_TOO_LARGE. */
9684
9955
  const MAX_FRAME_BYTES = 256 * 1024;