@agentconnect.md/daemon 1.0.0-rc.246 → 1.0.0-rc.247
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/{acp-host-Bhc0M4Pn.js → acp-host-CUct-2CR.js} +4 -1559
- package/dist/acp-host-CUct-2CR.js.map +1 -0
- package/dist/{agent-list-aVWm-4ru.js → agent-list-CpvraCfQ.js} +2 -2
- package/dist/{agent-list-aVWm-4ru.js.map → agent-list-CpvraCfQ.js.map} +1 -1
- package/dist/{chat-DLRdxrEz.js → chat-B8Z_78R2.js} +3 -3
- package/dist/{chat-DLRdxrEz.js.map → chat-B8Z_78R2.js.map} +1 -1
- package/dist/index.js +4 -4
- package/dist/load-agents-NvLpcg5H.js +1953 -0
- package/dist/load-agents-NvLpcg5H.js.map +1 -0
- package/dist/{login-Da2UThAL.js → login-CodLtz77.js} +3 -4
- package/dist/{login-Da2UThAL.js.map → login-CodLtz77.js.map} +1 -1
- package/dist/run-foreground-BlMl7cqk.js +2 -0
- package/dist/{run-foreground-CCttICnJ.js → run-foreground-K9FNooBp.js} +22 -6
- package/dist/{run-foreground-CCttICnJ.js.map → run-foreground-K9FNooBp.js.map} +1 -1
- package/package.json +1 -1
- package/dist/acp-host-Bhc0M4Pn.js.map +0 -1
- package/dist/load-agents-Dt8ZYMsH.js +0 -380
- package/dist/load-agents-Dt8ZYMsH.js.map +0 -1
- package/dist/run-foreground-DhTkIkj4.js +0 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "./rolldown-runtime-CE-6LUnI.js";
|
|
2
2
|
import { t as require_src$1 } from "./src-DDtlVutj.js";
|
|
3
|
-
import { T as NEVER, _ as record, a as array, b as unknown,
|
|
3
|
+
import { T as NEVER, _ as record, a as array, b as unknown, d as literal, l as int, m as object, o as boolean, p as number, u as intersection, v as string, x as url, y as union } from "./schemas-CQkO9hC5.js";
|
|
4
|
+
import { b as MEMORY_INDEX, d as normalizeGitUrl } from "./load-agents-NvLpcg5H.js";
|
|
4
5
|
import { c as registryPath, s as registryCachePath } from "./paths-DEcUO8lC.js";
|
|
5
6
|
import { delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { accessSync, constants, existsSync, mkdirSync, promises, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -8,7 +9,6 @@ import { spawn } from "child_process";
|
|
|
8
9
|
import { EventEmitter } from "node:events";
|
|
9
10
|
import { execFile, spawn as spawn$1 } from "node:child_process";
|
|
10
11
|
import { homedir } from "node:os";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { Readable, Writable } from "node:stream";
|
|
13
13
|
//#region src/runtimes/registry.ts
|
|
14
14
|
const PackageDistSchema = object({
|
|
@@ -371,1561 +371,6 @@ function toolsForIntegrations(integrations) {
|
|
|
371
371
|
return tools;
|
|
372
372
|
}
|
|
373
373
|
//#endregion
|
|
374
|
-
//#region ../protocol/dist/envelope.js
|
|
375
|
-
/**
|
|
376
|
-
* The protocol envelope — every frame, both directions, is wrapped in this.
|
|
377
|
-
* Mirrors daemon-cp-ws-protocol.md §1.1.
|
|
378
|
-
*
|
|
379
|
-
* `payload` is left as `unknown` here and validated by the per-type schema in
|
|
380
|
-
* `frames/*` via `FRAME_SCHEMAS[type]` (see `frame.ts`). This two-step parse is
|
|
381
|
-
* what lets the codec answer an unknown `type` with `UNKNOWN_FRAME` (a REP)
|
|
382
|
-
* instead of a hard close — forward-compat, protocol §1.
|
|
383
|
-
*/
|
|
384
|
-
const Envelope = object({
|
|
385
|
-
v: literal(1),
|
|
386
|
-
id: string().uuid(),
|
|
387
|
-
ts: string().datetime(),
|
|
388
|
-
type: string(),
|
|
389
|
-
corr: string().uuid().optional(),
|
|
390
|
-
payload: unknown()
|
|
391
|
-
});
|
|
392
|
-
object({
|
|
393
|
-
epoch: number().int(),
|
|
394
|
-
agentId: string().uuid().optional(),
|
|
395
|
-
launchId: string().uuid().optional()
|
|
396
|
-
});
|
|
397
|
-
/** The all-zero UUID used when a frame is malformed past the point of reading `id`. */
|
|
398
|
-
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
399
|
-
/**
|
|
400
|
-
* Builds the envelope schema for one frame `type` — the `type` literal plus the
|
|
401
|
-
* typed payload — so a wire's discriminated union infers `payload` precisely.
|
|
402
|
-
* Shared by the daemon↔CP union (`frame.ts`) and the relay wire unions
|
|
403
|
-
* (`frames/relay-cp.ts` / `frames/relay-daemon.ts`), which are deliberately
|
|
404
|
-
* SEPARATE unions over the same envelope shape (shared-bot-relay.md §8).
|
|
405
|
-
*/
|
|
406
|
-
function frameSchema(type, payload) {
|
|
407
|
-
return object({
|
|
408
|
-
v: literal(1),
|
|
409
|
-
id: string().uuid(),
|
|
410
|
-
ts: string().datetime(),
|
|
411
|
-
type: literal(type),
|
|
412
|
-
corr: string().uuid().optional(),
|
|
413
|
-
payload
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
//#endregion
|
|
417
|
-
//#region ../protocol/dist/frames/auth.js
|
|
418
|
-
/**
|
|
419
|
-
* Auth & identity — protocol §3.1 / §3.2.
|
|
420
|
-
*
|
|
421
|
-
* `auth` is the first frame after the socket opens. `auth/ok` carries the
|
|
422
|
-
* minted `sessionEpoch` (the global fencing token), heartbeat cadence, and the
|
|
423
|
-
* resume verdict.
|
|
424
|
-
*/
|
|
425
|
-
const AuthReq = object({
|
|
426
|
-
apiKey: string(),
|
|
427
|
-
daemonId: string().uuid().optional(),
|
|
428
|
-
machineId: string().uuid().optional(),
|
|
429
|
-
attestation: string().optional(),
|
|
430
|
-
agentVersion: string(),
|
|
431
|
-
resume: object({ lastEpoch: number().int() }).optional()
|
|
432
|
-
});
|
|
433
|
-
const AuthOk = object({
|
|
434
|
-
daemonId: string().uuid(),
|
|
435
|
-
sessionEpoch: number().int(),
|
|
436
|
-
heartbeatSec: number().int(),
|
|
437
|
-
serverTime: string().datetime(),
|
|
438
|
-
webAppUrl: string().optional(),
|
|
439
|
-
orgSlug: string().optional(),
|
|
440
|
-
resume: object({ accepted: boolean() }).optional()
|
|
441
|
-
});
|
|
442
|
-
//#endregion
|
|
443
|
-
//#region ../protocol/dist/frames/route.js
|
|
444
|
-
/**
|
|
445
|
-
* Routing & orchestration (C→D control) — protocol §5.
|
|
446
|
-
*
|
|
447
|
-
* `SessionKey` is the canonical session primitive shared across route/*,
|
|
448
|
-
* agent/*, and event/session. Its canonical string form is
|
|
449
|
-
* `${platform}:${channel}:${thread ?? "-"}`.
|
|
450
|
-
*/
|
|
451
|
-
const Platform = _enum([
|
|
452
|
-
"slack",
|
|
453
|
-
"telegram",
|
|
454
|
-
"webchat",
|
|
455
|
-
"discord"
|
|
456
|
-
]);
|
|
457
|
-
const SessionKey = object({
|
|
458
|
-
platform: Platform,
|
|
459
|
-
channel: string(),
|
|
460
|
-
thread: string().optional()
|
|
461
|
-
});
|
|
462
|
-
/** Trigger-matching rule for a binding (protocol §5.1). */
|
|
463
|
-
const BindRule = object({ match: discriminatedUnion("kind", [
|
|
464
|
-
object({ kind: literal("mention") }),
|
|
465
|
-
object({ kind: literal("dm") }),
|
|
466
|
-
object({
|
|
467
|
-
kind: literal("keyword"),
|
|
468
|
-
value: string()
|
|
469
|
-
}),
|
|
470
|
-
object({ kind: literal("auto") })
|
|
471
|
-
]) });
|
|
472
|
-
const RouteAssign = object({
|
|
473
|
-
sessionKey: SessionKey,
|
|
474
|
-
agentId: string().uuid(),
|
|
475
|
-
workspaceId: string().uuid(),
|
|
476
|
-
bindRules: array(BindRule).default([])
|
|
477
|
-
});
|
|
478
|
-
const RouteAssignAck = object({
|
|
479
|
-
ok: boolean(),
|
|
480
|
-
sessionKey: SessionKey,
|
|
481
|
-
reason: string().optional()
|
|
482
|
-
});
|
|
483
|
-
const RouteUpdate = object({
|
|
484
|
-
routingEpoch: number().int(),
|
|
485
|
-
rules: array(object({
|
|
486
|
-
match: unknown(),
|
|
487
|
-
agentId: string().uuid()
|
|
488
|
-
}))
|
|
489
|
-
});
|
|
490
|
-
/** Graceful scale-down / rebalance — protocol §5.3. */
|
|
491
|
-
const Drain = object({
|
|
492
|
-
scope: union([
|
|
493
|
-
object({
|
|
494
|
-
kind: literal("agent"),
|
|
495
|
-
agentId: string().uuid()
|
|
496
|
-
}),
|
|
497
|
-
object({ kind: literal("daemon") }),
|
|
498
|
-
object({
|
|
499
|
-
kind: literal("session"),
|
|
500
|
-
sessionKey: SessionKey
|
|
501
|
-
})
|
|
502
|
-
]),
|
|
503
|
-
deadline: string().datetime()
|
|
504
|
-
});
|
|
505
|
-
const DrainProgress = object({
|
|
506
|
-
remaining: number().int(),
|
|
507
|
-
drained: array(SessionKey)
|
|
508
|
-
});
|
|
509
|
-
const DrainDone = object({ released: array(SessionKey) });
|
|
510
|
-
//#endregion
|
|
511
|
-
//#region ../protocol/dist/frames/cron.js
|
|
512
|
-
/**
|
|
513
|
-
* Cron sinks to the daemon (D5) — protocol §5.4.
|
|
514
|
-
*
|
|
515
|
-
* A cron periodically triggers ONE AGENT with a synthetic prompt (`trigger`) to
|
|
516
|
-
* carry out some work. The CP owns the definition; the daemon owns firing +
|
|
517
|
-
* last-run persistence, so crons fire even when the CP is down. On receipt the
|
|
518
|
-
* daemon persists the def into the owning agent's `agent.json` `crons[]` (the
|
|
519
|
-
* single source of truth, same model as integrations) — surviving a restart
|
|
520
|
-
* with the CP down.
|
|
521
|
-
*
|
|
522
|
-
* `target` is OPTIONAL output routing: when present, the daemon posts the
|
|
523
|
-
* trigger as a real message in that channel and the agent's session replies in
|
|
524
|
-
* its thread; when absent the fire is headless (the agent works with no
|
|
525
|
-
* platform output).
|
|
526
|
-
*/
|
|
527
|
-
const CronTarget = object({
|
|
528
|
-
platform: _enum([
|
|
529
|
-
"slack",
|
|
530
|
-
"telegram",
|
|
531
|
-
"discord"
|
|
532
|
-
]).default("slack"),
|
|
533
|
-
channel: string(),
|
|
534
|
-
integrationId: string().uuid().optional()
|
|
535
|
-
});
|
|
536
|
-
const CronUpsert = object({
|
|
537
|
-
cronId: string().uuid(),
|
|
538
|
-
agentId: string().uuid(),
|
|
539
|
-
schedule: string(),
|
|
540
|
-
target: CronTarget.optional(),
|
|
541
|
-
trigger: string(),
|
|
542
|
-
enabled: boolean().default(true)
|
|
543
|
-
});
|
|
544
|
-
const CronRemove = object({ cronId: string().uuid() });
|
|
545
|
-
/**
|
|
546
|
-
* `cron/report` (D→C EVT, fire-and-forget) — one CP-owned cron fired. The
|
|
547
|
-
* daemon stamps the fire into its local store first (it stays authoritative,
|
|
548
|
-
* §5.4) and reports it here so the console's `lastRunAt` converges; the CP
|
|
549
|
-
* upsert is latest-wins, so the daemon re-asserting its stored stamps on
|
|
550
|
-
* reconnect (fires while the CP was unreachable) is idempotent and can never
|
|
551
|
-
* regress the value. Hand-authored (no-origin) crons are never reported.
|
|
552
|
-
*
|
|
553
|
-
* Two reports per fire, keyed by `(cronId, firedAt)`: the FIRE report (no
|
|
554
|
-
* `status`) opens the run, and the COMPLETION report (with `status` + outcome
|
|
555
|
-
* fields) closes it once the dispatched turn ends — feeding the console's
|
|
556
|
-
* per-schedule run history. A completion without a prior fire report (CP was
|
|
557
|
-
* down at fire time) still creates the run row.
|
|
558
|
-
*/
|
|
559
|
-
const CronRunStatus = _enum(["success", "failed"]);
|
|
560
|
-
const CronReport = object({
|
|
561
|
-
cronId: string().uuid(),
|
|
562
|
-
agentId: string().uuid(),
|
|
563
|
-
firedAt: string().datetime(),
|
|
564
|
-
status: CronRunStatus.optional(),
|
|
565
|
-
durationMs: number().int().nonnegative().optional(),
|
|
566
|
-
sessionId: string().optional(),
|
|
567
|
-
reason: string().optional()
|
|
568
|
-
});
|
|
569
|
-
/**
|
|
570
|
-
* `cron/run` (C→D REQ → ack) — fire one CP-owned cron NOW (console "Run now").
|
|
571
|
-
* The daemon accepts (`ok:true`) and runs the fire asynchronously — outcome
|
|
572
|
-
* arrives as normal `cron/report`s; `ok:false` when it holds no such cron.
|
|
573
|
-
*/
|
|
574
|
-
const CronRunNow = object({ cronId: string().uuid() });
|
|
575
|
-
//#endregion
|
|
576
|
-
//#region ../protocol/dist/frames/secrets.js
|
|
577
|
-
/**
|
|
578
|
-
* Secrets (C5 ↔ D10) — protocol §6.
|
|
579
|
-
*
|
|
580
|
-
* Lease-based, no plaintext on the wire or in PG. Every frame carries a
|
|
581
|
-
* REFERENCE to a Vault/KMS path, never the secret material itself.
|
|
582
|
-
*/
|
|
583
|
-
const SecretsRequest = object({ scope: object({
|
|
584
|
-
platform: Platform,
|
|
585
|
-
workspaceId: string().uuid()
|
|
586
|
-
}) });
|
|
587
|
-
const SecretsGrant = object({
|
|
588
|
-
leaseId: string().uuid(),
|
|
589
|
-
scope: object({
|
|
590
|
-
platform: string(),
|
|
591
|
-
workspaceId: string().uuid()
|
|
592
|
-
}),
|
|
593
|
-
ref: string(),
|
|
594
|
-
ttl: number().int(),
|
|
595
|
-
renewBeforeSec: number().int()
|
|
596
|
-
});
|
|
597
|
-
const SecretsRenew = object({ leaseId: string().uuid() });
|
|
598
|
-
const SecretsRevoke = object({
|
|
599
|
-
leaseId: string().uuid(),
|
|
600
|
-
reason: string()
|
|
601
|
-
});
|
|
602
|
-
/** 🅼 Direct-to-store upload/download grant — protocol §3.2 / frame #25. */
|
|
603
|
-
const ScopeAttestation = object({
|
|
604
|
-
machineId: string().uuid(),
|
|
605
|
-
scope: _enum([
|
|
606
|
-
"attachment.put",
|
|
607
|
-
"attachment.get",
|
|
608
|
-
"facts.put"
|
|
609
|
-
]),
|
|
610
|
-
resourceRef: string(),
|
|
611
|
-
jws: string(),
|
|
612
|
-
exp: string().datetime()
|
|
613
|
-
});
|
|
614
|
-
//#endregion
|
|
615
|
-
//#region ../protocol/dist/frames/agent.js
|
|
616
|
-
/**
|
|
617
|
-
* Agent lifecycle (protocol §4.4, §7.4, §8).
|
|
618
|
-
*
|
|
619
|
-
* There is no CP→daemon prompt-delivery frame: the daemon prompts an agent from
|
|
620
|
-
* its own ingress (platform adapters, and webchat over its own `webchat/*`
|
|
621
|
-
* frames), never the CP. The old `agent/prompt` + per-agent `seq` machinery was
|
|
622
|
-
* reserved infrastructure with no live caller and has been removed.
|
|
623
|
-
*/
|
|
624
|
-
/**
|
|
625
|
-
* Where the agent runs. Two modes; the **path is always daemon-generated** —
|
|
626
|
-
* never specified by the caller (UX picks the mode, the machine owns the dir).
|
|
627
|
-
*
|
|
628
|
-
* - `scratch`: a fresh empty working dir on the machine, no repo.
|
|
629
|
-
* - `github`: the daemon clones `gitRepo` @ `branch` and runs the agent in
|
|
630
|
-
* `agentDir` (a subdir of the repo, repo-root if omitted). **Multiple agents
|
|
631
|
-
* may share one repo** — they differ by `agentDir`, so the repo is not an
|
|
632
|
-
* owned entity, just shared config on each agent.
|
|
633
|
-
*/
|
|
634
|
-
const AgentWorkspace = discriminatedUnion("mode", [object({ mode: literal("scratch") }), object({
|
|
635
|
-
mode: literal("github"),
|
|
636
|
-
gitRepo: string(),
|
|
637
|
-
branch: string().default("main"),
|
|
638
|
-
agentDir: string().optional(),
|
|
639
|
-
gitCredential: _enum(["github-app"]).optional()
|
|
640
|
-
})]);
|
|
641
|
-
/**
|
|
642
|
-
* MCP-server name reserved for the daemon's own injected stdio bridge (its
|
|
643
|
-
* platform tools). A config-defined or agent-enabled server under this name
|
|
644
|
-
* would collide with the bridge entry at ACP `session/new`, so the daemon
|
|
645
|
-
* strips it and the CP rejects it in `AgentSpec.mcpServers` at the API edge.
|
|
646
|
-
*/
|
|
647
|
-
const RESERVED_MCP_SERVER_NAME = "agentconnect";
|
|
648
|
-
/**
|
|
649
|
-
* The editable agent definition the CP owns and the daemon needs to run it:
|
|
650
|
-
* prompt + runtime selection. Raft ships this in the launch config and the
|
|
651
|
-
* daemon synthesizes the system prompt locally; `description` IS the prompt.
|
|
652
|
-
*/
|
|
653
|
-
const AgentSpec = object({
|
|
654
|
-
name: string(),
|
|
655
|
-
description: string().optional(),
|
|
656
|
-
runtime: string().optional(),
|
|
657
|
-
model: string().nullable().optional(),
|
|
658
|
-
reasoningEffort: string().nullable().optional(),
|
|
659
|
-
executionMode: string().optional(),
|
|
660
|
-
outputMode: _enum([
|
|
661
|
-
"low",
|
|
662
|
-
"medium",
|
|
663
|
-
"high"
|
|
664
|
-
]).optional(),
|
|
665
|
-
fastMode: boolean().optional(),
|
|
666
|
-
permissionMode: string().nullable().optional(),
|
|
667
|
-
pause: boolean().optional(),
|
|
668
|
-
workspace: AgentWorkspace.optional(),
|
|
669
|
-
env: record(string(), string()).optional(),
|
|
670
|
-
memory: object({ provider: _enum([
|
|
671
|
-
"native",
|
|
672
|
-
"managed",
|
|
673
|
-
"external"
|
|
674
|
-
]) }).optional(),
|
|
675
|
-
mcpServers: array(string()).default([])
|
|
676
|
-
});
|
|
677
|
-
const AgentLaunch = object({
|
|
678
|
-
agentId: string().uuid(),
|
|
679
|
-
runtime: string(),
|
|
680
|
-
workspaceId: string().uuid(),
|
|
681
|
-
capabilities: array(string()),
|
|
682
|
-
spec: AgentSpec,
|
|
683
|
-
mode: _enum(["long_lived", "per_turn"]).default("long_lived")
|
|
684
|
-
});
|
|
685
|
-
/**
|
|
686
|
-
* Live agent CRUD (C→D): the console edited an agent's spec; push it so a
|
|
687
|
-
* running daemon reloads without waiting for the next launch. `agent/remove`
|
|
688
|
-
* tears the agent down. Deleting an agent never relaunches it.
|
|
689
|
-
*/
|
|
690
|
-
const AgentUpsert = object({
|
|
691
|
-
agentId: string().uuid(),
|
|
692
|
-
spec: AgentSpec
|
|
693
|
-
});
|
|
694
|
-
const AgentRemove = object({ agentId: string().uuid() });
|
|
695
|
-
const AgentLaunched = object({
|
|
696
|
-
agentId: string().uuid(),
|
|
697
|
-
launchId: string().uuid(),
|
|
698
|
-
acpSessionId: string().optional(),
|
|
699
|
-
startedAt: string().datetime(),
|
|
700
|
-
runtime: string()
|
|
701
|
-
});
|
|
702
|
-
const AgentStop = object({
|
|
703
|
-
agentId: string().uuid(),
|
|
704
|
-
launchId: string().uuid(),
|
|
705
|
-
reason: string()
|
|
706
|
-
});
|
|
707
|
-
const AgentActivity = object({
|
|
708
|
-
agentId: string().uuid(),
|
|
709
|
-
launchId: string().uuid(),
|
|
710
|
-
state: _enum([
|
|
711
|
-
"thinking",
|
|
712
|
-
"tool_call",
|
|
713
|
-
"awaiting_permission",
|
|
714
|
-
"idle"
|
|
715
|
-
]),
|
|
716
|
-
ts: string().datetime()
|
|
717
|
-
});
|
|
718
|
-
const AgentScopeDenied = object({
|
|
719
|
-
agentId: string().uuid(),
|
|
720
|
-
launchId: string().uuid(),
|
|
721
|
-
capability: string()
|
|
722
|
-
});
|
|
723
|
-
//#endregion
|
|
724
|
-
//#region ../protocol/dist/frames/integration.js
|
|
725
|
-
/**
|
|
726
|
-
* Platform integration distribution (C→D) — the Slack "install" flow.
|
|
727
|
-
*
|
|
728
|
-
* The Control Plane is the source of truth for platform integrations and pushes
|
|
729
|
-
* them to the daemon that owns the integration's agent (`integration/upsert`, and
|
|
730
|
-
* the reconcile snapshot `RegisterOk.integrations[]`). The daemon opens the Socket
|
|
731
|
-
* Mode connection from the delivered config (see slack/connection.ts).
|
|
732
|
-
*
|
|
733
|
-
* SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
|
|
734
|
-
* platform tokens (botToken/appToken). These payloads MUST NEVER be logged — no
|
|
735
|
-
* body dump on decode error, no register/ok snapshot debug dump. The daemon
|
|
736
|
-
* persists them into the owning agent's local `agent.json` (same trust boundary
|
|
737
|
-
* as hand-authored agents, which already keep tokens there) so integrations
|
|
738
|
-
* survive a restart with the CP down.
|
|
739
|
-
*
|
|
740
|
-
* `signingSecret` is intentionally absent: Socket Mode authenticates with the
|
|
741
|
-
* app-level token, so the daemon never needs a signing secret.
|
|
742
|
-
*/
|
|
743
|
-
/** Trigger match — mirrors the daemon BindRuleConfig.match (agents/agent-schema.ts). */
|
|
744
|
-
const BindMatch = discriminatedUnion("kind", [
|
|
745
|
-
object({ kind: literal("mention") }),
|
|
746
|
-
object({ kind: literal("dm") }),
|
|
747
|
-
object({
|
|
748
|
-
kind: literal("keyword"),
|
|
749
|
-
value: string()
|
|
750
|
-
}),
|
|
751
|
-
object({ kind: literal("auto") })
|
|
752
|
-
]);
|
|
753
|
-
/** One channel/thread trigger binding — mirrors the daemon BindRuleConfig. */
|
|
754
|
-
const IntegrationBindRule = object({
|
|
755
|
-
channel: string().optional(),
|
|
756
|
-
thread: string().optional(),
|
|
757
|
-
match: BindMatch
|
|
758
|
-
});
|
|
759
|
-
/** The Slack config the daemon needs to open + route a socket (no signingSecret). */
|
|
760
|
-
const IntegrationSlackConfig = object({
|
|
761
|
-
botToken: string(),
|
|
762
|
-
appToken: string(),
|
|
763
|
-
allowedUserIds: array(string()).default([]),
|
|
764
|
-
bindRules: array(IntegrationBindRule).default([])
|
|
765
|
-
});
|
|
766
|
-
/**
|
|
767
|
-
* The Telegram config the daemon needs to open long-polling + route (grammY).
|
|
768
|
-
* Telegram has a SINGLE BotFather HTTP token — no app-level token and no signing
|
|
769
|
-
* secret (long-polling authenticates every getUpdates call with the bot token).
|
|
770
|
-
*/
|
|
771
|
-
const IntegrationTelegramConfig = object({
|
|
772
|
-
botToken: string(),
|
|
773
|
-
allowedUserIds: array(string()).default([]),
|
|
774
|
-
bindRules: array(IntegrationBindRule).default([])
|
|
775
|
-
});
|
|
776
|
-
/**
|
|
777
|
-
* The Discord config the daemon needs to open the Gateway + route (discord.js).
|
|
778
|
-
* Discord authenticates the Gateway with a SINGLE bot token — no Slack-style
|
|
779
|
-
* app-level token and no signing secret. `applicationId` is public metadata (the
|
|
780
|
-
* client id for the OAuth2 bot-invite URL); it is not secret material.
|
|
781
|
-
*/
|
|
782
|
-
const IntegrationDiscordConfig = object({
|
|
783
|
-
botToken: string(),
|
|
784
|
-
applicationId: string().optional(),
|
|
785
|
-
allowedUserIds: array(string()).default([]),
|
|
786
|
-
bindRules: array(IntegrationBindRule).default([])
|
|
787
|
-
});
|
|
788
|
-
/**
|
|
789
|
-
* One platform integration, owned by exactly one agent. Also the element type of
|
|
790
|
-
* `RegisterOk.integrations[]` (the per-daemon reconcile set). Discriminated on
|
|
791
|
-
* `platform`: the daemon opens a Slack Socket Mode connection, a Telegram
|
|
792
|
-
* long-poll, or a Discord Gateway from whichever variant is delivered.
|
|
793
|
-
*/
|
|
794
|
-
const IntegrationSpec = discriminatedUnion("platform", [
|
|
795
|
-
object({
|
|
796
|
-
integrationId: string().uuid(),
|
|
797
|
-
agentId: string().uuid(),
|
|
798
|
-
platform: literal("slack"),
|
|
799
|
-
slack: IntegrationSlackConfig
|
|
800
|
-
}),
|
|
801
|
-
object({
|
|
802
|
-
integrationId: string().uuid(),
|
|
803
|
-
agentId: string().uuid(),
|
|
804
|
-
platform: literal("telegram"),
|
|
805
|
-
telegram: IntegrationTelegramConfig
|
|
806
|
-
}),
|
|
807
|
-
object({
|
|
808
|
-
integrationId: string().uuid(),
|
|
809
|
-
agentId: string().uuid(),
|
|
810
|
-
platform: literal("discord"),
|
|
811
|
-
discord: IntegrationDiscordConfig
|
|
812
|
-
})
|
|
813
|
-
]);
|
|
814
|
-
/** C→D EVT — install/update an integration on the owning agent's daemon. */
|
|
815
|
-
const IntegrationUpsert = IntegrationSpec;
|
|
816
|
-
/** C→D EVT — remove an integration from the daemon. */
|
|
817
|
-
const IntegrationRemove = object({ integrationId: string().uuid() });
|
|
818
|
-
/** One channel the bot is currently a member of (metadata only — no messages). */
|
|
819
|
-
const IntegrationChannel = object({
|
|
820
|
-
id: string(),
|
|
821
|
-
name: string().optional(),
|
|
822
|
-
isPrivate: boolean().optional()
|
|
823
|
-
});
|
|
824
|
-
/**
|
|
825
|
-
* D→C EVT — the FULL set of channels the integration's bot is a member of
|
|
826
|
-
* (fire-and-forget, latest-wins). Emitted on socket start and whenever the bot
|
|
827
|
-
* is invited to / removed from a channel, so the console can offer per-channel
|
|
828
|
-
* trigger config. Channel names are control metadata, never message content.
|
|
829
|
-
*/
|
|
830
|
-
const IntegrationChannels = object({
|
|
831
|
-
integrationId: string().uuid(),
|
|
832
|
-
channels: array(IntegrationChannel)
|
|
833
|
-
});
|
|
834
|
-
//#endregion
|
|
835
|
-
//#region ../protocol/dist/frames/register.js
|
|
836
|
-
/**
|
|
837
|
-
* Capability upload + the reconcile snapshot — protocol §3.3.
|
|
838
|
-
*
|
|
839
|
-
* `register/ok` is the authoritative source of truth: the daemon converges its
|
|
840
|
-
* local cache to it. CP wins all conflicts, so re-issuing the same snapshot is
|
|
841
|
-
* idempotent.
|
|
842
|
-
*/
|
|
843
|
-
const RegisterReq = object({
|
|
844
|
-
host: string(),
|
|
845
|
-
capabilities: object({
|
|
846
|
-
platforms: array(Platform),
|
|
847
|
-
runtimes: array(string()),
|
|
848
|
-
acp: boolean(),
|
|
849
|
-
features: array(string()).default([])
|
|
850
|
-
}),
|
|
851
|
-
maxAgents: number().int(),
|
|
852
|
-
localState: object({
|
|
853
|
-
assignments: array(string()),
|
|
854
|
-
crons: array(string()),
|
|
855
|
-
leases: array(string())
|
|
856
|
-
})
|
|
857
|
-
});
|
|
858
|
-
/**
|
|
859
|
-
* One relay the daemon SHOULD hold an outbound WS to (shared-bot-relay.md §5).
|
|
860
|
-
* The roster is all-to-all by design: a webchat/webhook landing on ANY relay
|
|
861
|
-
* instance must find this daemon's connection without cross-instance forwarding.
|
|
862
|
-
* That only holds if `url` (the relay's registered `daemonUrl`) routes to that
|
|
863
|
-
* SPECIFIC instance — the daemon confirms the landing spot against
|
|
864
|
-
* `rd/hello/ok.relayId` and treats a mismatch as a deployment misroute.
|
|
865
|
-
*/
|
|
866
|
-
const RelayRosterEntry = object({
|
|
867
|
-
relayId: string().uuid(),
|
|
868
|
-
url: string()
|
|
869
|
-
});
|
|
870
|
-
/**
|
|
871
|
-
* C→D EVT (`relay/roster`) — hot roster update (relay registered / swept).
|
|
872
|
-
* Carries the WHOLE desired set, same converge-don't-diff semantics as the
|
|
873
|
-
* `register/ok.relays` snapshot it refreshes.
|
|
874
|
-
*/
|
|
875
|
-
const RelayRosterUpdate = object({ relays: array(RelayRosterEntry) });
|
|
876
|
-
const RegisterOk = object({
|
|
877
|
-
routingEpoch: number().int(),
|
|
878
|
-
assignments: array(RouteAssign),
|
|
879
|
-
agents: array(AgentSpec.extend({ agentId: string().uuid() })).default([]),
|
|
880
|
-
crons: array(CronUpsert),
|
|
881
|
-
integrations: array(IntegrationSpec).default([]),
|
|
882
|
-
leases: array(SecretsGrant),
|
|
883
|
-
relays: array(RelayRosterEntry).default([]),
|
|
884
|
-
drop: object({
|
|
885
|
-
assignments: array(string()),
|
|
886
|
-
crons: array(string())
|
|
887
|
-
})
|
|
888
|
-
});
|
|
889
|
-
//#endregion
|
|
890
|
-
//#region ../protocol/dist/frames/gitcred.js
|
|
891
|
-
/**
|
|
892
|
-
* Git credentials (github-app workspaces) — daemon-pulled, CP-minted.
|
|
893
|
-
*
|
|
894
|
-
* The CP holds the GitHub App private key and mints short-lived (1h,
|
|
895
|
-
* non-renewable) installation access tokens scoped to a single repository;
|
|
896
|
-
* the daemon pulls one on demand right before a remote git operation and
|
|
897
|
-
* holds it in memory only. Unlike `secrets/*` (lease + reference semantics,
|
|
898
|
-
* still unwired), the grant here carries the TOKEN MATERIAL itself — same
|
|
899
|
-
* plaintext-over-TLS-WS posture as `integration/upsert`, and the same
|
|
900
|
-
* discipline: **never log the payload**.
|
|
901
|
-
*
|
|
902
|
-
* The daemon may only name an agentId — the CP resolves agent → workspace →
|
|
903
|
-
* repo → installation itself, so a daemon can never pick a repo it wasn't
|
|
904
|
-
* assigned. Failures come back as correlated `error` REPs: `SCOPE_DENIED`
|
|
905
|
-
* (not a github-app workspace, or agent not placed on this daemon — stop
|
|
906
|
-
* asking), `LEASE_DENIED` (installation uninstalled/suspended or the repo
|
|
907
|
-
* left its grant set — recoverable only by an operator), `RATE_LIMITED`,
|
|
908
|
-
* `INTERNAL`.
|
|
909
|
-
*/
|
|
910
|
-
const GitCredRequest = object({
|
|
911
|
-
agentId: string().uuid(),
|
|
912
|
-
reason: _enum([
|
|
913
|
-
"clone",
|
|
914
|
-
"fetch",
|
|
915
|
-
"pull",
|
|
916
|
-
"push",
|
|
917
|
-
"helper"
|
|
918
|
-
]).optional()
|
|
919
|
-
});
|
|
920
|
-
const GitCredGrant = object({
|
|
921
|
-
username: literal("x-access-token"),
|
|
922
|
-
token: string(),
|
|
923
|
-
ttlSec: number().int(),
|
|
924
|
-
expiresAt: string().datetime(),
|
|
925
|
-
repoFullName: string(),
|
|
926
|
-
access: _enum(["read", "write"])
|
|
927
|
-
});
|
|
928
|
-
//#endregion
|
|
929
|
-
//#region ../protocol/dist/frames/session.js
|
|
930
|
-
/**
|
|
931
|
-
* Session read-back (C→D REQ → REP) — the console's on-demand pulls.
|
|
932
|
-
*
|
|
933
|
-
* Session bodies are daemon-local (body-locality, §1/§12). The CP may persist a
|
|
934
|
-
* metadata-only session-list snapshot from daemon `event/session` events, while
|
|
935
|
-
* transcript/tool bodies are pulled live from the owning daemon and proxied to
|
|
936
|
-
* the console only for display.
|
|
937
|
-
*
|
|
938
|
-
* - `session/list`: the daemon's local metadata projection, used for snapshots
|
|
939
|
-
* and direct read-back/debugging.
|
|
940
|
-
* - `session/history`: one cursor-paginated page of a session's transcript.
|
|
941
|
-
*/
|
|
942
|
-
/**
|
|
943
|
-
* Per-session token accounting, metered by the daemon from the agent's ACP usage
|
|
944
|
-
* stream. Token counts are CUMULATIVE across the session's turns (summed from each
|
|
945
|
-
* turn's `PromptResponse.usage`); context/cost are the LATEST snapshot from the
|
|
946
|
-
* agent's `usage_update` stream. Every field is optional — a runtime that reports
|
|
947
|
-
* no usage yields an absent field, not a zero.
|
|
948
|
-
*/
|
|
949
|
-
const SessionUsage = object({
|
|
950
|
-
totalTokens: number().int().optional(),
|
|
951
|
-
inputTokens: number().int().optional(),
|
|
952
|
-
outputTokens: number().int().optional(),
|
|
953
|
-
thoughtTokens: number().int().optional(),
|
|
954
|
-
cachedReadTokens: number().int().optional(),
|
|
955
|
-
cachedWriteTokens: number().int().optional(),
|
|
956
|
-
contextUsed: number().int().optional(),
|
|
957
|
-
contextSize: number().int().optional(),
|
|
958
|
-
costAmount: number().optional(),
|
|
959
|
-
costCurrency: string().optional()
|
|
960
|
-
});
|
|
961
|
-
/** One row in the session list (metadata + console metrics; NOT the transcript). */
|
|
962
|
-
const SessionListItem = object({
|
|
963
|
-
sessionId: string(),
|
|
964
|
-
sessionKey: SessionKey,
|
|
965
|
-
agentId: string().uuid(),
|
|
966
|
-
title: string().optional(),
|
|
967
|
-
status: string().optional(),
|
|
968
|
-
lastActivityAt: string().optional(),
|
|
969
|
-
usage: SessionUsage.optional(),
|
|
970
|
-
triggeredBy: string().optional(),
|
|
971
|
-
channelName: string().optional(),
|
|
972
|
-
triggeredByName: string().optional(),
|
|
973
|
-
threadUrl: string().optional()
|
|
974
|
-
});
|
|
975
|
-
/** C→D REQ: list the daemon's sessions (optionally just one agent's). */
|
|
976
|
-
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
977
|
-
/** D→C REP (corr = req id): the daemon's current sessions. */
|
|
978
|
-
const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
979
|
-
object({
|
|
980
|
-
toolCallId: string(),
|
|
981
|
-
kind: string().optional(),
|
|
982
|
-
status: string().optional(),
|
|
983
|
-
rawInput: unknown().optional(),
|
|
984
|
-
rawOutput: unknown().optional(),
|
|
985
|
-
content: array(unknown()).optional(),
|
|
986
|
-
locations: array(object({
|
|
987
|
-
path: string(),
|
|
988
|
-
line: number().int().optional()
|
|
989
|
-
})).optional(),
|
|
990
|
-
truncated: boolean().optional()
|
|
991
|
-
});
|
|
992
|
-
/** One message in a session transcript page (a body — returned only for display). */
|
|
993
|
-
const SessionMessage = object({
|
|
994
|
-
seq: number().int(),
|
|
995
|
-
sender: string(),
|
|
996
|
-
senderName: string().optional(),
|
|
997
|
-
ts: string(),
|
|
998
|
-
kind: string(),
|
|
999
|
-
text: string(),
|
|
1000
|
-
toolCallId: string().optional(),
|
|
1001
|
-
toolStatus: string().optional(),
|
|
1002
|
-
toolKind: string().optional(),
|
|
1003
|
-
body: string().optional(),
|
|
1004
|
-
bodyTruncated: boolean().optional(),
|
|
1005
|
-
bodyBytes: number().int().optional()
|
|
1006
|
-
});
|
|
1007
|
-
/** C→D REQ: fetch one page of a session's history from the owning daemon. */
|
|
1008
|
-
const SessionHistoryReq = object({
|
|
1009
|
-
sessionId: string(),
|
|
1010
|
-
cursor: string().optional(),
|
|
1011
|
-
limit: number().int().positive().max(200).default(50)
|
|
1012
|
-
});
|
|
1013
|
-
/** D→C REP (corr = the req id): a page of messages + the cursor for the next page. */
|
|
1014
|
-
const SessionHistoryPage = object({
|
|
1015
|
-
sessionId: string(),
|
|
1016
|
-
messages: array(SessionMessage),
|
|
1017
|
-
nextCursor: string().optional()
|
|
1018
|
-
});
|
|
1019
|
-
/**
|
|
1020
|
-
* C→D REQ: fetch the FULL (untruncated) ToolBody JSON for one tool call, chunked.
|
|
1021
|
-
* The inline `SessionMessage.body` preview is capped at 32 KiB; when a body is
|
|
1022
|
-
* bigger the console pages the whole thing back through this frame by offset.
|
|
1023
|
-
*/
|
|
1024
|
-
const SessionToolBodyReq = object({
|
|
1025
|
-
sessionId: string(),
|
|
1026
|
-
toolCallId: string(),
|
|
1027
|
-
offset: number().int().nonnegative().default(0)
|
|
1028
|
-
});
|
|
1029
|
-
/** D→C REP (corr = the req id): one frame-budgeted byte slice of the full ToolBody JSON. */
|
|
1030
|
-
const SessionToolBodyChunk = object({
|
|
1031
|
-
sessionId: string(),
|
|
1032
|
-
toolCallId: string(),
|
|
1033
|
-
data: string(),
|
|
1034
|
-
totalBytes: number().int(),
|
|
1035
|
-
nextOffset: number().int().optional()
|
|
1036
|
-
});
|
|
1037
|
-
//#endregion
|
|
1038
|
-
//#region ../protocol/dist/frames/channel.js
|
|
1039
|
-
/**
|
|
1040
|
-
* Channel agent directory (D→C REQ → REP) — agent collaboration lookup.
|
|
1041
|
-
*
|
|
1042
|
-
* An agent asks (via a daemon-side tool) "who else is in this channel?". The
|
|
1043
|
-
* daemon forwards it to the CP — the ONLY authority for the FULL channel roster,
|
|
1044
|
-
* because agents in one channel may run on different daemons (each daemon knows
|
|
1045
|
-
* only its own agents). The CP joins the channel's integrations to their agents
|
|
1046
|
-
* and returns their public metadata (name / displayName / description / status).
|
|
1047
|
-
* Metadata only — never message content (§1/§12).
|
|
1048
|
-
*
|
|
1049
|
-
* Direction is daemon→CP (like `auth` / `register`): the daemon sends the REQ and
|
|
1050
|
-
* the CP replies with `channel/agents/ok` (corr = the req id).
|
|
1051
|
-
*/
|
|
1052
|
-
/** One agent visible in a channel — the collaboration directory entry. */
|
|
1053
|
-
const ChannelAgent = object({
|
|
1054
|
-
agentId: string().uuid(),
|
|
1055
|
-
name: string(),
|
|
1056
|
-
displayName: string().optional(),
|
|
1057
|
-
description: string().optional(),
|
|
1058
|
-
status: _enum([
|
|
1059
|
-
"active",
|
|
1060
|
-
"inactive",
|
|
1061
|
-
"paused"
|
|
1062
|
-
])
|
|
1063
|
-
});
|
|
1064
|
-
/** D→C REQ: list the agents in a channel (for the asking agent's collaboration tool). */
|
|
1065
|
-
const ChannelAgentsReq = object({
|
|
1066
|
-
platform: Platform,
|
|
1067
|
-
channel: string()
|
|
1068
|
-
});
|
|
1069
|
-
/** C→D REP (corr = req id): every agent in the channel across all daemons. */
|
|
1070
|
-
const ChannelAgentsOk = object({
|
|
1071
|
-
platform: Platform,
|
|
1072
|
-
channel: string(),
|
|
1073
|
-
agents: array(ChannelAgent)
|
|
1074
|
-
});
|
|
1075
|
-
//#endregion
|
|
1076
|
-
//#region ../protocol/dist/frames/workspace.js
|
|
1077
|
-
/**
|
|
1078
|
-
* Agent workspace file browsing (C→D REQ → REP) — the console's on-demand pulls.
|
|
1079
|
-
*
|
|
1080
|
-
* The CP stores NO workspace data — directory listings and file bytes are pulled
|
|
1081
|
-
* live from the owning daemon and proxied to the console, never persisted
|
|
1082
|
-
* (body-locality). Missing paths are DATA (`exists:false`), not errors; only
|
|
1083
|
-
* path-containment violations (`BAD_PAYLOAD`) and unexpected fs failures
|
|
1084
|
-
* (`INTERNAL`) come back as `error` frames.
|
|
1085
|
-
*
|
|
1086
|
-
* - `workspace/list`: one cursor-paginated page of a directory's entries.
|
|
1087
|
-
* - `workspace/read`: one byte slice of a file. `limit` is only a ceiling — the
|
|
1088
|
-
* daemon returns FEWER bytes when needed to keep the JSON-escaped REP under the
|
|
1089
|
-
* 256 KiB frame cap (control bytes escape to `\uXXXX`, a 6× blowup), and always
|
|
1090
|
-
* ends the slice on a UTF-8 character boundary. `nextOffset` is the authoritative
|
|
1091
|
-
* byte offset to request next — callers must NOT recompute it from the decoded
|
|
1092
|
-
* `content` (a split multi-byte char would make the byte count drift).
|
|
1093
|
-
*/
|
|
1094
|
-
/** One directory entry in a workspace listing (name-only; not a full path). */
|
|
1095
|
-
const WorkspaceEntry = object({
|
|
1096
|
-
name: string(),
|
|
1097
|
-
type: _enum([
|
|
1098
|
-
"dir",
|
|
1099
|
-
"file",
|
|
1100
|
-
"symlink",
|
|
1101
|
-
"other"
|
|
1102
|
-
]),
|
|
1103
|
-
size: number().int().nonnegative().optional(),
|
|
1104
|
-
mtime: string().optional()
|
|
1105
|
-
});
|
|
1106
|
-
/** C→D REQ: list one page of a directory in the agent's workspace. */
|
|
1107
|
-
const WorkspaceListReq = object({
|
|
1108
|
-
agentId: string().min(1),
|
|
1109
|
-
path: string().default(""),
|
|
1110
|
-
cursor: string().optional(),
|
|
1111
|
-
limit: number().int().positive().max(500).default(200)
|
|
1112
|
-
});
|
|
1113
|
-
/** D→C REP (corr = the req id): a page of entries + the cursor for the next page. */
|
|
1114
|
-
const WorkspaceListPage = object({
|
|
1115
|
-
agentId: string(),
|
|
1116
|
-
path: string(),
|
|
1117
|
-
exists: boolean(),
|
|
1118
|
-
entries: array(WorkspaceEntry),
|
|
1119
|
-
nextCursor: string().optional()
|
|
1120
|
-
});
|
|
1121
|
-
/** C→D REQ: read one byte slice of a file in the agent's workspace. */
|
|
1122
|
-
const WorkspaceReadReq = object({
|
|
1123
|
-
agentId: string().min(1),
|
|
1124
|
-
path: string().min(1),
|
|
1125
|
-
offset: number().int().nonnegative().default(0),
|
|
1126
|
-
limit: number().int().positive().max(65536).default(65536)
|
|
1127
|
-
});
|
|
1128
|
-
/** D→C REP (corr = the req id): the file slice (or `exists:false` / binary-detected). */
|
|
1129
|
-
const WorkspaceReadContent = object({
|
|
1130
|
-
agentId: string(),
|
|
1131
|
-
path: string(),
|
|
1132
|
-
exists: boolean(),
|
|
1133
|
-
size: number().int().nonnegative().optional(),
|
|
1134
|
-
mtime: string().optional(),
|
|
1135
|
-
encoding: _enum(["utf8", "none"]).optional(),
|
|
1136
|
-
content: string().optional(),
|
|
1137
|
-
offset: number().int().nonnegative().optional(),
|
|
1138
|
-
nextOffset: number().int().nonnegative().optional(),
|
|
1139
|
-
truncated: boolean().optional()
|
|
1140
|
-
});
|
|
1141
|
-
/**
|
|
1142
|
-
* Agent workspace git ops (C→D REQ → REP) — the console's on-demand controls for
|
|
1143
|
-
* a git-repo workspace. Like list/read these run daemon-local; the CP proxies the
|
|
1144
|
-
* outcome and stores nothing.
|
|
1145
|
-
*
|
|
1146
|
-
* These are DATA-oriented: a non-repo workspace (`isRepo:false`), a dirty tree,
|
|
1147
|
-
* or a pull that can't fast-forward (offline, diverged, local edits) all come
|
|
1148
|
-
* back as a normal REP — NOT an `error` frame. Only an unknown agent
|
|
1149
|
-
* (`BAD_PAYLOAD`) or an unexpected git/fs failure (`INTERNAL`) is an error.
|
|
1150
|
-
*/
|
|
1151
|
-
/** C→D REQ: report `git status` of the agent's workspace (is it clean?). */
|
|
1152
|
-
const WorkspaceGitStatusReq = object({ agentId: string().min(1) });
|
|
1153
|
-
/** One changed path in a `git status`. `index`/`workingDir` are git's per-file XY
|
|
1154
|
-
* status chars (' ', 'M', 'A', 'D', 'R', 'C', 'U', '?', …). */
|
|
1155
|
-
const WorkspaceGitFile = object({
|
|
1156
|
-
path: string(),
|
|
1157
|
-
index: string(),
|
|
1158
|
-
workingDir: string()
|
|
1159
|
-
});
|
|
1160
|
-
/** The HEAD commit of the checkout. */
|
|
1161
|
-
const WorkspaceGitCommit = object({
|
|
1162
|
-
sha: string(),
|
|
1163
|
-
shortSha: string(),
|
|
1164
|
-
subject: string(),
|
|
1165
|
-
committedAt: string()
|
|
1166
|
-
});
|
|
1167
|
-
/** D→C REP (corr = the req id): the working-tree status. */
|
|
1168
|
-
const WorkspaceGitStatus = object({
|
|
1169
|
-
agentId: string(),
|
|
1170
|
-
isRepo: boolean(),
|
|
1171
|
-
clean: boolean(),
|
|
1172
|
-
branch: string().optional(),
|
|
1173
|
-
tracking: string().optional(),
|
|
1174
|
-
ahead: number().int().nonnegative().optional(),
|
|
1175
|
-
behind: number().int().nonnegative().optional(),
|
|
1176
|
-
files: array(WorkspaceGitFile).optional(),
|
|
1177
|
-
truncated: boolean().optional(),
|
|
1178
|
-
lastCommit: WorkspaceGitCommit.optional(),
|
|
1179
|
-
lastFetchAt: string().optional()
|
|
1180
|
-
});
|
|
1181
|
-
/** C→D REQ: force a fast-forward-only `git pull` in the agent's workspace now. */
|
|
1182
|
-
const WorkspaceGitPullReq = object({ agentId: string().min(1) });
|
|
1183
|
-
/** D→C REP (corr = the req id): the pull outcome. A failed pull is DATA
|
|
1184
|
-
* (`ok:false` + `detail`), not an error frame (see the section docblock). */
|
|
1185
|
-
const WorkspaceGitPullResult = object({
|
|
1186
|
-
agentId: string(),
|
|
1187
|
-
isRepo: boolean(),
|
|
1188
|
-
ok: boolean(),
|
|
1189
|
-
detail: string().optional(),
|
|
1190
|
-
changed: number().int().nonnegative().optional(),
|
|
1191
|
-
insertions: number().int().nonnegative().optional(),
|
|
1192
|
-
deletions: number().int().nonnegative().optional()
|
|
1193
|
-
});
|
|
1194
|
-
//#endregion
|
|
1195
|
-
//#region ../protocol/dist/frames/memory.js
|
|
1196
|
-
/**
|
|
1197
|
-
* Agent memory directory (C→D REQ → REP) — the console's read/write of an agent's
|
|
1198
|
-
* long-term memory, and the surface the daemon serves it from.
|
|
1199
|
-
*
|
|
1200
|
-
* Memory is a DIRECTORY at the agent's ROOT (`<agent-root>/memory/`), OUTSIDE the
|
|
1201
|
-
* workspace — so it survives a workspace reset / re-clone and is never committed
|
|
1202
|
-
* into a github-workspace repo. It follows the index-plus-topics shape: a lean
|
|
1203
|
-
* `MEMORY.md` index (injected into the prompt every session, bounded) plus any
|
|
1204
|
-
* number of `<topic>.md` files the agent reads on demand. The agent maintains it
|
|
1205
|
-
* via MCP tools; these frames are the third channel — the CP reading/writing it
|
|
1206
|
-
* for the console — all pointing at the same directory.
|
|
1207
|
-
*
|
|
1208
|
-
* The CP stores NO memory content — bytes are pulled live from the owning daemon
|
|
1209
|
-
* and proxied to the console, never persisted (body-locality). A not-yet-created
|
|
1210
|
-
* file/dir is DATA (`exists:false`), not an error; only an unknown agent or a
|
|
1211
|
-
* path-containment violation (`BAD_PAYLOAD`) or an unexpected fs failure
|
|
1212
|
-
* (`INTERNAL`) comes back as an `error` frame.
|
|
1213
|
-
*
|
|
1214
|
-
* - `memory/list`: the topic files in the memory dir (flat; the index + topics).
|
|
1215
|
-
* - `memory/read`: one byte slice of a memory file (`path` relative to the memory
|
|
1216
|
-
* dir; default `MEMORY.md`). `limit` is a ceiling — the daemon returns fewer
|
|
1217
|
-
* bytes to keep the JSON-escaped REP under the 256 KiB frame cap and always ends
|
|
1218
|
-
* on a UTF-8 boundary. `nextOffset` is authoritative — do NOT recompute it.
|
|
1219
|
-
* - `memory/write`: replace the whole named memory file with `content`.
|
|
1220
|
-
*/
|
|
1221
|
-
/** The default file name of the memory index (loaded into the prompt each session). */
|
|
1222
|
-
const MEMORY_INDEX = "MEMORY.md";
|
|
1223
|
-
/** One entry in a memory-dir listing (name-only; not a path). */
|
|
1224
|
-
const MemoryEntry = object({
|
|
1225
|
-
name: string(),
|
|
1226
|
-
size: number().int().nonnegative(),
|
|
1227
|
-
mtime: string()
|
|
1228
|
-
});
|
|
1229
|
-
/** C→D REQ: list the files in the agent's memory dir. */
|
|
1230
|
-
const MemoryListReq = object({ agentId: string().min(1) });
|
|
1231
|
-
/** D→C REP (corr = the req id): the memory dir's files (or `exists:false`). */
|
|
1232
|
-
const MemoryListPage = object({
|
|
1233
|
-
agentId: string(),
|
|
1234
|
-
exists: boolean(),
|
|
1235
|
-
entries: array(MemoryEntry)
|
|
1236
|
-
});
|
|
1237
|
-
/** C→D REQ: read one byte slice of a memory file (path relative to the memory dir). */
|
|
1238
|
-
const MemoryReadReq = object({
|
|
1239
|
-
agentId: string().min(1),
|
|
1240
|
-
path: string().default(MEMORY_INDEX),
|
|
1241
|
-
offset: number().int().nonnegative().default(0),
|
|
1242
|
-
limit: number().int().positive().max(65536).default(65536)
|
|
1243
|
-
});
|
|
1244
|
-
/** D→C REP (corr = the req id): the memory file slice (or `exists:false`). Always utf8 text. */
|
|
1245
|
-
const MemoryReadContent = object({
|
|
1246
|
-
agentId: string(),
|
|
1247
|
-
path: string(),
|
|
1248
|
-
exists: boolean(),
|
|
1249
|
-
size: number().int().nonnegative().optional(),
|
|
1250
|
-
mtime: string().optional(),
|
|
1251
|
-
content: string().optional(),
|
|
1252
|
-
offset: number().int().nonnegative().optional(),
|
|
1253
|
-
nextOffset: number().int().nonnegative().optional(),
|
|
1254
|
-
truncated: boolean().optional()
|
|
1255
|
-
});
|
|
1256
|
-
/** C→D REQ: replace the whole named memory file with `content` (console edit). */
|
|
1257
|
-
const MemoryWriteReq = object({
|
|
1258
|
-
agentId: string().min(1),
|
|
1259
|
-
path: string().default(MEMORY_INDEX),
|
|
1260
|
-
content: string(),
|
|
1261
|
-
ifMatchMtime: string().optional()
|
|
1262
|
-
});
|
|
1263
|
-
/** D→C REP (corr = the req id): the written state, so the console can refresh. */
|
|
1264
|
-
const MemoryWriteOk = object({
|
|
1265
|
-
agentId: string(),
|
|
1266
|
-
path: string(),
|
|
1267
|
-
size: number().int().nonnegative(),
|
|
1268
|
-
mtime: string()
|
|
1269
|
-
});
|
|
1270
|
-
//#endregion
|
|
1271
|
-
//#region ../protocol/dist/frames/telemetry.js
|
|
1272
|
-
/**
|
|
1273
|
-
* Telemetry & facts (D→C) — protocol §7.
|
|
1274
|
-
*
|
|
1275
|
-
* The dashboard's real-time source. `event/session` is the converged milestone
|
|
1276
|
-
* feed (NOT the message stream); `facts/daemon-runtimes` is the observed runtime
|
|
1277
|
-
* capability feed; `heartbeat` drives the watchdog.
|
|
1278
|
-
*/
|
|
1279
|
-
const Heartbeat = object({
|
|
1280
|
-
load: object({
|
|
1281
|
-
cpu: number(),
|
|
1282
|
-
mem: number(),
|
|
1283
|
-
agents: number().int()
|
|
1284
|
-
}),
|
|
1285
|
-
health: _enum(["ok", "degraded"]),
|
|
1286
|
-
activeSessions: number().int(),
|
|
1287
|
-
degradedScopes: array(string()).default([])
|
|
1288
|
-
});
|
|
1289
|
-
/** Converged session lifecycle milestone — protocol §7.2. NOT the message stream. */
|
|
1290
|
-
const EventSession = object({
|
|
1291
|
-
sessionId: string(),
|
|
1292
|
-
agentId: string().uuid(),
|
|
1293
|
-
launchId: string().uuid().optional(),
|
|
1294
|
-
phase: _enum([
|
|
1295
|
-
"start",
|
|
1296
|
-
"plan",
|
|
1297
|
-
"problem",
|
|
1298
|
-
"end"
|
|
1299
|
-
]),
|
|
1300
|
-
platform: Platform.optional(),
|
|
1301
|
-
channel: string().optional(),
|
|
1302
|
-
thread: string().optional(),
|
|
1303
|
-
link: string().optional(),
|
|
1304
|
-
summary: string().optional(),
|
|
1305
|
-
title: string().optional(),
|
|
1306
|
-
status: string().optional(),
|
|
1307
|
-
lastActivityAt: string().datetime().optional(),
|
|
1308
|
-
triggeredBy: string().optional(),
|
|
1309
|
-
channelName: string().optional(),
|
|
1310
|
-
triggeredByName: string().optional(),
|
|
1311
|
-
threadUrl: string().optional(),
|
|
1312
|
-
ts: string().datetime()
|
|
1313
|
-
});
|
|
1314
|
-
/**
|
|
1315
|
-
* Per-session token-usage report — D→C EVT. The daemon meters usage from the
|
|
1316
|
-
* agent's ACP stream and reports the session's CUMULATIVE snapshot (latest-wins)
|
|
1317
|
-
* so the CP can persist it for the console's historical usage aggregates. This is
|
|
1318
|
-
* dashboard metadata (token counts + cost), never the message stream. Idempotent
|
|
1319
|
-
* by `sessionId`: re-sending the same snapshot is a no-op upsert on the CP.
|
|
1320
|
-
*/
|
|
1321
|
-
const UsageReport = object({
|
|
1322
|
-
sessionId: string(),
|
|
1323
|
-
agentId: string().uuid(),
|
|
1324
|
-
platform: string().optional(),
|
|
1325
|
-
channel: string().optional(),
|
|
1326
|
-
lastActivityAt: string(),
|
|
1327
|
-
usage: SessionUsage
|
|
1328
|
-
});
|
|
1329
|
-
/**
|
|
1330
|
-
* Observed capabilities of an installed runtime — protocol §7.3. The element
|
|
1331
|
-
* type of the `facts/daemon-runtimes` snapshot. As a standalone per-runtime
|
|
1332
|
-
* frame (`facts/runtime-profile`, upsert-only) it is DEPRECATED: current
|
|
1333
|
-
* daemons report the full snapshot instead; the CP keeps accepting the old
|
|
1334
|
-
* frame from older daemons.
|
|
1335
|
-
*/
|
|
1336
|
-
/**
|
|
1337
|
-
* MCP transports a runtime accepts on `session/new` `mcpServers` (from the ACP
|
|
1338
|
-
* `initialize` response's `agentCapabilities.mcpCapabilities`). Stdio is the ACP
|
|
1339
|
-
* baseline every agent must accept, so only the optional transports are listed.
|
|
1340
|
-
*/
|
|
1341
|
-
const McpTransportCapabilities = object({
|
|
1342
|
-
http: boolean(),
|
|
1343
|
-
sse: boolean()
|
|
1344
|
-
});
|
|
1345
|
-
const FactsRuntimeProfile = object({
|
|
1346
|
-
runtime: string(),
|
|
1347
|
-
version: string(),
|
|
1348
|
-
models: array(string()),
|
|
1349
|
-
contextWindow: number().int().optional(),
|
|
1350
|
-
acpSupport: _enum([
|
|
1351
|
-
"full",
|
|
1352
|
-
"partial",
|
|
1353
|
-
"none"
|
|
1354
|
-
]),
|
|
1355
|
-
acpProtocolVersion: number().int().optional(),
|
|
1356
|
-
toolCalling: boolean(),
|
|
1357
|
-
mcpCapabilities: McpTransportCapabilities.optional()
|
|
1358
|
-
});
|
|
1359
|
-
/**
|
|
1360
|
-
* Probe result for one daemon-configured MCP server (daemon config `mcpServers`).
|
|
1361
|
-
* The daemon connects to the server (MCP initialize + `tools/list`) so the
|
|
1362
|
-
* console can show what each server offers before the user enables it on an
|
|
1363
|
-
* agent. Metadata only — definitions (commands, URLs, headers) stay daemon-local.
|
|
1364
|
-
*/
|
|
1365
|
-
const FactsMcpServer = object({
|
|
1366
|
-
name: string(),
|
|
1367
|
-
transport: _enum([
|
|
1368
|
-
"stdio",
|
|
1369
|
-
"http",
|
|
1370
|
-
"sse"
|
|
1371
|
-
]),
|
|
1372
|
-
/** True iff the MCP handshake succeeded. */
|
|
1373
|
-
ok: boolean(),
|
|
1374
|
-
tools: array(string()).default([]),
|
|
1375
|
-
version: string().optional(),
|
|
1376
|
-
error: string().optional()
|
|
1377
|
-
});
|
|
1378
|
-
/**
|
|
1379
|
-
* Full snapshot of the daemon's installed runtimes — D→C EVT (protocol §7.3a),
|
|
1380
|
-
* emitted on each register and again once the background probe sweep completes
|
|
1381
|
-
* (with the learned `models[]`). REPLACE semantics: the CP reconciles its
|
|
1382
|
-
* stored runtime list for the daemon to exactly `runtimes[]`, pruning runtimes
|
|
1383
|
-
* that are no longer installed. Idempotent (latest-wins). Supersedes the
|
|
1384
|
-
* per-runtime `facts/runtime-profile` upsert.
|
|
1385
|
-
*/
|
|
1386
|
-
const DaemonRuntimes = object({
|
|
1387
|
-
runtimes: array(FactsRuntimeProfile),
|
|
1388
|
-
mcpServers: array(FactsMcpServer).default([])
|
|
1389
|
-
});
|
|
1390
|
-
/** Non-secret config update — C→D EVT, protocol §8.3 / frame #26. */
|
|
1391
|
-
const ConfigPush = object({ keys: record(string(), unknown()) });
|
|
1392
|
-
/** Fleet: drain+exit, supervisor restarts — C→D REQ, protocol §8.3 / frame #27. */
|
|
1393
|
-
const DaemonRestart = object({
|
|
1394
|
-
reason: string(),
|
|
1395
|
-
drainFirst: boolean().default(true)
|
|
1396
|
-
});
|
|
1397
|
-
/** Fleet: drain+exit for version bump — C→D REQ, protocol §8.3 / frame #28. */
|
|
1398
|
-
const DaemonUpgrade = object({
|
|
1399
|
-
targetVersion: string(),
|
|
1400
|
-
drainFirst: boolean().default(true)
|
|
1401
|
-
});
|
|
1402
|
-
/** Reply to a fleet restart/upgrade REQ (protocol §8.3). */
|
|
1403
|
-
const DaemonControlAck = object({
|
|
1404
|
-
accepted: boolean(),
|
|
1405
|
-
willDrainUntil: string().datetime().optional()
|
|
1406
|
-
});
|
|
1407
|
-
/**
|
|
1408
|
-
* Generic acknowledgement for REQs whose reply is just `ack` in the frame index
|
|
1409
|
-
* (cron/upsert, cron/remove, agent/stop). Kept permissive but typed.
|
|
1410
|
-
*/
|
|
1411
|
-
const Ack = object({
|
|
1412
|
-
ok: boolean(),
|
|
1413
|
-
reason: string().optional()
|
|
1414
|
-
});
|
|
1415
|
-
const ErrorFrame = object({
|
|
1416
|
-
code: _enum([
|
|
1417
|
-
"UNKNOWN_FRAME",
|
|
1418
|
-
"FRAME_TOO_LARGE",
|
|
1419
|
-
"PROTOCOL_STATE",
|
|
1420
|
-
"BAD_PAYLOAD",
|
|
1421
|
-
"AUTH_FAILED",
|
|
1422
|
-
"ATTESTATION_INVALID",
|
|
1423
|
-
"STALE_EPOCH",
|
|
1424
|
-
"STALE_LAUNCH",
|
|
1425
|
-
"NO_SESSION",
|
|
1426
|
-
"SCOPE_DENIED",
|
|
1427
|
-
"LEASE_EXPIRED",
|
|
1428
|
-
"LEASE_DENIED",
|
|
1429
|
-
"RATE_LIMITED",
|
|
1430
|
-
"CONFLICT",
|
|
1431
|
-
"INTERNAL"
|
|
1432
|
-
]),
|
|
1433
|
-
message: string(),
|
|
1434
|
-
retryable: boolean(),
|
|
1435
|
-
details: record(string(), unknown()).optional()
|
|
1436
|
-
});
|
|
1437
|
-
object({
|
|
1438
|
-
accepted: boolean(),
|
|
1439
|
-
turnId: string().uuid(),
|
|
1440
|
-
reason: _enum([
|
|
1441
|
-
"queued",
|
|
1442
|
-
"no_agent",
|
|
1443
|
-
"busy",
|
|
1444
|
-
"paused",
|
|
1445
|
-
"draining"
|
|
1446
|
-
]).optional()
|
|
1447
|
-
});
|
|
1448
|
-
const WebchatEvent = discriminatedUnion("kind", [
|
|
1449
|
-
object({
|
|
1450
|
-
kind: literal("message"),
|
|
1451
|
-
text: string()
|
|
1452
|
-
}),
|
|
1453
|
-
object({
|
|
1454
|
-
kind: literal("thinking"),
|
|
1455
|
-
text: string()
|
|
1456
|
-
}),
|
|
1457
|
-
object({
|
|
1458
|
-
kind: literal("tool_call"),
|
|
1459
|
-
toolCallId: string(),
|
|
1460
|
-
title: string(),
|
|
1461
|
-
status: string()
|
|
1462
|
-
}),
|
|
1463
|
-
object({
|
|
1464
|
-
kind: literal("tool_update"),
|
|
1465
|
-
toolCallId: string(),
|
|
1466
|
-
status: string()
|
|
1467
|
-
}),
|
|
1468
|
-
object({
|
|
1469
|
-
kind: literal("session_info"),
|
|
1470
|
-
title: string()
|
|
1471
|
-
})
|
|
1472
|
-
]);
|
|
1473
|
-
const WebchatStatus = object({
|
|
1474
|
-
model: string().optional(),
|
|
1475
|
-
effort: string().optional(),
|
|
1476
|
-
permissionMode: string().optional(),
|
|
1477
|
-
fastMode: boolean().optional(),
|
|
1478
|
-
contextUsed: number().int().optional(),
|
|
1479
|
-
contextSize: number().int().optional(),
|
|
1480
|
-
totalTokens: number().int().optional(),
|
|
1481
|
-
costAmount: number().optional(),
|
|
1482
|
-
costCurrency: string().optional(),
|
|
1483
|
-
models: array(string()).optional(),
|
|
1484
|
-
efforts: array(string()).optional(),
|
|
1485
|
-
permissionModes: array(string()).optional(),
|
|
1486
|
-
fastModeAvailable: boolean().optional(),
|
|
1487
|
-
sessionId: string().optional()
|
|
1488
|
-
});
|
|
1489
|
-
const WebchatOutput = object({
|
|
1490
|
-
conversationId: string().uuid(),
|
|
1491
|
-
turnId: string().uuid(),
|
|
1492
|
-
index: number().int(),
|
|
1493
|
-
event: WebchatEvent.optional(),
|
|
1494
|
-
status: WebchatStatus.optional()
|
|
1495
|
-
}).refine((o) => o.event !== void 0 || o.status !== void 0, { message: "WebchatOutput must carry event and/or status" });
|
|
1496
|
-
const WebchatDone = object({
|
|
1497
|
-
conversationId: string().uuid(),
|
|
1498
|
-
turnId: string().uuid(),
|
|
1499
|
-
stopReason: string().optional(),
|
|
1500
|
-
usage: object({
|
|
1501
|
-
used: number().int().optional(),
|
|
1502
|
-
cost: number().optional()
|
|
1503
|
-
}).optional(),
|
|
1504
|
-
error: string().optional()
|
|
1505
|
-
});
|
|
1506
|
-
//#endregion
|
|
1507
|
-
//#region ../protocol/dist/frame.js
|
|
1508
|
-
/**
|
|
1509
|
-
* The single source of truth for the wire: `type` string → payload zod schema.
|
|
1510
|
-
*
|
|
1511
|
-
* Mirrors the frame index in daemon-cp-ws-protocol.md §10 (the 29 numbered
|
|
1512
|
-
* frames) plus the correlated REP types named in the "Reply" column
|
|
1513
|
-
* (`route/assign/ack`, `drain/done`, and the generic `ack` replies) that also
|
|
1514
|
-
* travel on the wire and must be decodable.
|
|
1515
|
-
*
|
|
1516
|
-
* `ws/codec.ts` validates every inbound `payload` against `FRAME_SCHEMAS[type]`;
|
|
1517
|
-
* an unknown `type` → `ErrorFrame{code:"UNKNOWN_FRAME"}` (a REP, not a close).
|
|
1518
|
-
*/
|
|
1519
|
-
const FRAME_SCHEMAS = {
|
|
1520
|
-
auth: AuthReq,
|
|
1521
|
-
"auth/ok": AuthOk,
|
|
1522
|
-
register: RegisterReq,
|
|
1523
|
-
"register/ok": RegisterOk,
|
|
1524
|
-
"relay/roster": RelayRosterUpdate,
|
|
1525
|
-
heartbeat: Heartbeat,
|
|
1526
|
-
"agent/launch": AgentLaunch,
|
|
1527
|
-
"agent/launched": AgentLaunched,
|
|
1528
|
-
"agent/stop": AgentStop,
|
|
1529
|
-
"agent/upsert": AgentUpsert,
|
|
1530
|
-
"agent/remove": AgentRemove,
|
|
1531
|
-
"agent/activity": AgentActivity,
|
|
1532
|
-
"agent/scope-denied": AgentScopeDenied,
|
|
1533
|
-
"route/assign": RouteAssign,
|
|
1534
|
-
"route/assign/ack": RouteAssignAck,
|
|
1535
|
-
"route/update": RouteUpdate,
|
|
1536
|
-
"daemon/drain": Drain,
|
|
1537
|
-
"drain/progress": DrainProgress,
|
|
1538
|
-
"drain/done": DrainDone,
|
|
1539
|
-
"cron/upsert": CronUpsert,
|
|
1540
|
-
"cron/remove": CronRemove,
|
|
1541
|
-
"cron/report": CronReport,
|
|
1542
|
-
"cron/run": CronRunNow,
|
|
1543
|
-
"integration/upsert": IntegrationUpsert,
|
|
1544
|
-
"integration/remove": IntegrationRemove,
|
|
1545
|
-
"integration/channels": IntegrationChannels,
|
|
1546
|
-
"gitcred/request": GitCredRequest,
|
|
1547
|
-
"gitcred/grant": GitCredGrant,
|
|
1548
|
-
"secrets/request": SecretsRequest,
|
|
1549
|
-
"secrets/grant": SecretsGrant,
|
|
1550
|
-
"secrets/renew": SecretsRenew,
|
|
1551
|
-
"secrets/revoke": SecretsRevoke,
|
|
1552
|
-
"scope-attestation": ScopeAttestation,
|
|
1553
|
-
"event/session": EventSession,
|
|
1554
|
-
"usage/report": UsageReport,
|
|
1555
|
-
"facts/runtime-profile": FactsRuntimeProfile,
|
|
1556
|
-
"facts/daemon-runtimes": DaemonRuntimes,
|
|
1557
|
-
"session/list": SessionListReq,
|
|
1558
|
-
"session/list/page": SessionListPage,
|
|
1559
|
-
"session/history": SessionHistoryReq,
|
|
1560
|
-
"session/history/page": SessionHistoryPage,
|
|
1561
|
-
"session/tool-body": SessionToolBodyReq,
|
|
1562
|
-
"session/tool-body/chunk": SessionToolBodyChunk,
|
|
1563
|
-
"channel/agents": ChannelAgentsReq,
|
|
1564
|
-
"channel/agents/ok": ChannelAgentsOk,
|
|
1565
|
-
"workspace/list": WorkspaceListReq,
|
|
1566
|
-
"workspace/list/page": WorkspaceListPage,
|
|
1567
|
-
"workspace/read": WorkspaceReadReq,
|
|
1568
|
-
"workspace/read/content": WorkspaceReadContent,
|
|
1569
|
-
"workspace/gitstatus": WorkspaceGitStatusReq,
|
|
1570
|
-
"workspace/gitstatus/result": WorkspaceGitStatus,
|
|
1571
|
-
"workspace/gitpull": WorkspaceGitPullReq,
|
|
1572
|
-
"workspace/gitpull/result": WorkspaceGitPullResult,
|
|
1573
|
-
"memory/list": MemoryListReq,
|
|
1574
|
-
"memory/list/page": MemoryListPage,
|
|
1575
|
-
"memory/read": MemoryReadReq,
|
|
1576
|
-
"memory/read/content": MemoryReadContent,
|
|
1577
|
-
"memory/write": MemoryWriteReq,
|
|
1578
|
-
"memory/write/ok": MemoryWriteOk,
|
|
1579
|
-
"config/push": ConfigPush,
|
|
1580
|
-
"daemon/restart": DaemonRestart,
|
|
1581
|
-
"daemon/upgrade": DaemonUpgrade,
|
|
1582
|
-
"daemon/control/ack": DaemonControlAck,
|
|
1583
|
-
ack: Ack,
|
|
1584
|
-
error: ErrorFrame
|
|
1585
|
-
};
|
|
1586
|
-
Object.keys(FRAME_SCHEMAS);
|
|
1587
|
-
/**
|
|
1588
|
-
* Builds the envelope schema for one frame `type` with a `type` literal and the
|
|
1589
|
-
* typed payload, so the discriminated union infers `payload` precisely.
|
|
1590
|
-
*
|
|
1591
|
-
* Kept as a local generic (payload pinned to `FRAME_SCHEMAS[T]`) so a
|
|
1592
|
-
* mispaired member — `frame('auth', FRAME_SCHEMAS['register'])` — is a compile
|
|
1593
|
-
* error; a bare `frameSchema` alias would accept it. The body is deliberately
|
|
1594
|
-
* inlined rather than delegated: `return frameSchema(type, payload)` under
|
|
1595
|
-
* THIS generic signature collapses every union member's `payload` to `unknown`
|
|
1596
|
-
* (113 × TS18046 under TS 6.0.3 / zod 4.4.3 — reproducible by making exactly
|
|
1597
|
-
* that substitution), while the small relay unions call `frameSchema` with
|
|
1598
|
-
* concrete schemas and infer fine.
|
|
1599
|
-
*/
|
|
1600
|
-
function frame(type, payload) {
|
|
1601
|
-
return object({
|
|
1602
|
-
v: literal(1),
|
|
1603
|
-
id: string().uuid(),
|
|
1604
|
-
ts: string().datetime(),
|
|
1605
|
-
type: literal(type),
|
|
1606
|
-
corr: string().uuid().optional(),
|
|
1607
|
-
payload
|
|
1608
|
-
});
|
|
1609
|
-
}
|
|
1610
|
-
discriminatedUnion("type", [
|
|
1611
|
-
frame("auth", FRAME_SCHEMAS["auth"]),
|
|
1612
|
-
frame("auth/ok", FRAME_SCHEMAS["auth/ok"]),
|
|
1613
|
-
frame("register", FRAME_SCHEMAS["register"]),
|
|
1614
|
-
frame("register/ok", FRAME_SCHEMAS["register/ok"]),
|
|
1615
|
-
frame("relay/roster", FRAME_SCHEMAS["relay/roster"]),
|
|
1616
|
-
frame("heartbeat", FRAME_SCHEMAS["heartbeat"]),
|
|
1617
|
-
frame("agent/launch", FRAME_SCHEMAS["agent/launch"]),
|
|
1618
|
-
frame("agent/launched", FRAME_SCHEMAS["agent/launched"]),
|
|
1619
|
-
frame("agent/stop", FRAME_SCHEMAS["agent/stop"]),
|
|
1620
|
-
frame("agent/upsert", FRAME_SCHEMAS["agent/upsert"]),
|
|
1621
|
-
frame("agent/remove", FRAME_SCHEMAS["agent/remove"]),
|
|
1622
|
-
frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
|
|
1623
|
-
frame("agent/scope-denied", FRAME_SCHEMAS["agent/scope-denied"]),
|
|
1624
|
-
frame("route/assign", FRAME_SCHEMAS["route/assign"]),
|
|
1625
|
-
frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
|
|
1626
|
-
frame("route/update", FRAME_SCHEMAS["route/update"]),
|
|
1627
|
-
frame("daemon/drain", FRAME_SCHEMAS["daemon/drain"]),
|
|
1628
|
-
frame("drain/progress", FRAME_SCHEMAS["drain/progress"]),
|
|
1629
|
-
frame("drain/done", FRAME_SCHEMAS["drain/done"]),
|
|
1630
|
-
frame("cron/upsert", FRAME_SCHEMAS["cron/upsert"]),
|
|
1631
|
-
frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
|
|
1632
|
-
frame("cron/report", FRAME_SCHEMAS["cron/report"]),
|
|
1633
|
-
frame("cron/run", FRAME_SCHEMAS["cron/run"]),
|
|
1634
|
-
frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
|
|
1635
|
-
frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
|
|
1636
|
-
frame("integration/channels", FRAME_SCHEMAS["integration/channels"]),
|
|
1637
|
-
frame("gitcred/request", FRAME_SCHEMAS["gitcred/request"]),
|
|
1638
|
-
frame("gitcred/grant", FRAME_SCHEMAS["gitcred/grant"]),
|
|
1639
|
-
frame("secrets/request", FRAME_SCHEMAS["secrets/request"]),
|
|
1640
|
-
frame("secrets/grant", FRAME_SCHEMAS["secrets/grant"]),
|
|
1641
|
-
frame("secrets/renew", FRAME_SCHEMAS["secrets/renew"]),
|
|
1642
|
-
frame("secrets/revoke", FRAME_SCHEMAS["secrets/revoke"]),
|
|
1643
|
-
frame("scope-attestation", FRAME_SCHEMAS["scope-attestation"]),
|
|
1644
|
-
frame("event/session", FRAME_SCHEMAS["event/session"]),
|
|
1645
|
-
frame("usage/report", FRAME_SCHEMAS["usage/report"]),
|
|
1646
|
-
frame("facts/runtime-profile", FRAME_SCHEMAS["facts/runtime-profile"]),
|
|
1647
|
-
frame("facts/daemon-runtimes", FRAME_SCHEMAS["facts/daemon-runtimes"]),
|
|
1648
|
-
frame("session/list", FRAME_SCHEMAS["session/list"]),
|
|
1649
|
-
frame("session/list/page", FRAME_SCHEMAS["session/list/page"]),
|
|
1650
|
-
frame("session/history", FRAME_SCHEMAS["session/history"]),
|
|
1651
|
-
frame("session/history/page", FRAME_SCHEMAS["session/history/page"]),
|
|
1652
|
-
frame("session/tool-body", FRAME_SCHEMAS["session/tool-body"]),
|
|
1653
|
-
frame("session/tool-body/chunk", FRAME_SCHEMAS["session/tool-body/chunk"]),
|
|
1654
|
-
frame("channel/agents", FRAME_SCHEMAS["channel/agents"]),
|
|
1655
|
-
frame("channel/agents/ok", FRAME_SCHEMAS["channel/agents/ok"]),
|
|
1656
|
-
frame("workspace/list", FRAME_SCHEMAS["workspace/list"]),
|
|
1657
|
-
frame("workspace/list/page", FRAME_SCHEMAS["workspace/list/page"]),
|
|
1658
|
-
frame("workspace/read", FRAME_SCHEMAS["workspace/read"]),
|
|
1659
|
-
frame("workspace/read/content", FRAME_SCHEMAS["workspace/read/content"]),
|
|
1660
|
-
frame("workspace/gitstatus", FRAME_SCHEMAS["workspace/gitstatus"]),
|
|
1661
|
-
frame("workspace/gitstatus/result", FRAME_SCHEMAS["workspace/gitstatus/result"]),
|
|
1662
|
-
frame("workspace/gitpull", FRAME_SCHEMAS["workspace/gitpull"]),
|
|
1663
|
-
frame("workspace/gitpull/result", FRAME_SCHEMAS["workspace/gitpull/result"]),
|
|
1664
|
-
frame("memory/list", FRAME_SCHEMAS["memory/list"]),
|
|
1665
|
-
frame("memory/list/page", FRAME_SCHEMAS["memory/list/page"]),
|
|
1666
|
-
frame("memory/read", FRAME_SCHEMAS["memory/read"]),
|
|
1667
|
-
frame("memory/read/content", FRAME_SCHEMAS["memory/read/content"]),
|
|
1668
|
-
frame("memory/write", FRAME_SCHEMAS["memory/write"]),
|
|
1669
|
-
frame("memory/write/ok", FRAME_SCHEMAS["memory/write/ok"]),
|
|
1670
|
-
frame("config/push", FRAME_SCHEMAS["config/push"]),
|
|
1671
|
-
frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
|
|
1672
|
-
frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
|
|
1673
|
-
frame("daemon/control/ack", FRAME_SCHEMAS["daemon/control/ack"]),
|
|
1674
|
-
frame("ack", FRAME_SCHEMAS["ack"]),
|
|
1675
|
-
frame("error", FRAME_SCHEMAS["error"])
|
|
1676
|
-
]);
|
|
1677
|
-
//#endregion
|
|
1678
|
-
//#region ../protocol/dist/wire.js
|
|
1679
|
-
/**
|
|
1680
|
-
* Generic envelope codec — the schema-map-agnostic core shared by every wire.
|
|
1681
|
-
*
|
|
1682
|
-
* The daemon↔CP wire (`codec.ts` over `FRAME_SCHEMAS`) and the two relay wires
|
|
1683
|
-
* (`frames/relay-cp.ts` / `frames/relay-daemon.ts` over their OWN maps) all
|
|
1684
|
-
* decode the same envelope shape but accept disjoint frame families — a relay
|
|
1685
|
-
* socket must answer a daemon↔CP frame with `UNKNOWN_FRAME`, and vice versa
|
|
1686
|
-
* (shared-bot-relay.md §8 "独立帧 union,不混入 daemon 协议").
|
|
1687
|
-
*/
|
|
1688
|
-
/** Soft cap per frame — 256 KiB (protocol §1). Over this → FRAME_TOO_LARGE. */
|
|
1689
|
-
const MAX_FRAME_BYTES = 256 * 1024;
|
|
1690
|
-
const textEncoder = new TextEncoder();
|
|
1691
|
-
function byteLength(text) {
|
|
1692
|
-
return textEncoder.encode(text).length;
|
|
1693
|
-
}
|
|
1694
|
-
function extractControlExt(json) {
|
|
1695
|
-
if (typeof json !== "object" || json === null) return void 0;
|
|
1696
|
-
const o = json;
|
|
1697
|
-
const ext = {};
|
|
1698
|
-
if (typeof o.epoch === "number") ext.epoch = o.epoch;
|
|
1699
|
-
if (typeof o.agentId === "string") ext.agentId = o.agentId;
|
|
1700
|
-
if (typeof o.launchId === "string") ext.launchId = o.launchId;
|
|
1701
|
-
return Object.keys(ext).length > 0 ? ext : void 0;
|
|
1702
|
-
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Decode one wire frame against `schemas` (the wire's `type` → payload map).
|
|
1705
|
-
* Two-step parse: envelope first, then the per-type payload schema — an unknown
|
|
1706
|
-
* `type` is a typed `UNKNOWN_FRAME` failure (a REP, never a hard close).
|
|
1707
|
-
*/
|
|
1708
|
-
function decodeEnvelopeWith(schemas, text) {
|
|
1709
|
-
if (byteLength(text) > 262144) return {
|
|
1710
|
-
ok: false,
|
|
1711
|
-
id: NIL_UUID,
|
|
1712
|
-
msg: "FRAME_TOO_LARGE"
|
|
1713
|
-
};
|
|
1714
|
-
let json;
|
|
1715
|
-
try {
|
|
1716
|
-
json = JSON.parse(text);
|
|
1717
|
-
} catch {
|
|
1718
|
-
return {
|
|
1719
|
-
ok: false,
|
|
1720
|
-
id: NIL_UUID,
|
|
1721
|
-
msg: "invalid json"
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
const env = Envelope.safeParse(json);
|
|
1725
|
-
if (!env.success) return {
|
|
1726
|
-
ok: false,
|
|
1727
|
-
id: typeof json === "object" && json !== null && typeof json.id === "string" ? json.id : NIL_UUID,
|
|
1728
|
-
msg: env.error.message
|
|
1729
|
-
};
|
|
1730
|
-
const schema = schemas[env.data.type];
|
|
1731
|
-
if (!schema) return {
|
|
1732
|
-
ok: false,
|
|
1733
|
-
id: env.data.id,
|
|
1734
|
-
msg: "UNKNOWN_FRAME"
|
|
1735
|
-
};
|
|
1736
|
-
const payload = schema.safeParse(env.data.payload);
|
|
1737
|
-
if (!payload.success) return {
|
|
1738
|
-
ok: false,
|
|
1739
|
-
id: env.data.id,
|
|
1740
|
-
msg: payload.error.message
|
|
1741
|
-
};
|
|
1742
|
-
const ext = extractControlExt(json);
|
|
1743
|
-
return {
|
|
1744
|
-
ok: true,
|
|
1745
|
-
frame: {
|
|
1746
|
-
...env.data,
|
|
1747
|
-
payload: payload.data
|
|
1748
|
-
},
|
|
1749
|
-
...ext ? { ext } : {}
|
|
1750
|
-
};
|
|
1751
|
-
}
|
|
1752
|
-
/**
|
|
1753
|
-
* Wrap a payload in the wire envelope. Untyped core — each wire exports a typed
|
|
1754
|
-
* wrapper (`buildEnvelope` / `buildRelayCpFrame` / `buildRelayDaemonFrame`) that
|
|
1755
|
-
* pins `type` to its own union and casts the result.
|
|
1756
|
-
*/
|
|
1757
|
-
function buildEnvelopeRaw(type, payload, opts = {}) {
|
|
1758
|
-
return {
|
|
1759
|
-
v: 1,
|
|
1760
|
-
id: opts.id ?? randomUUID(),
|
|
1761
|
-
ts: opts.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1762
|
-
type,
|
|
1763
|
-
payload,
|
|
1764
|
-
...opts.corr ? { corr: opts.corr } : {},
|
|
1765
|
-
...opts.ext ?? {}
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
//#endregion
|
|
1769
|
-
//#region ../protocol/dist/codec.js
|
|
1770
|
-
function decodeEnvelope(text) {
|
|
1771
|
-
return decodeEnvelopeWith(FRAME_SCHEMAS, text);
|
|
1772
|
-
}
|
|
1773
|
-
function buildEnvelope(type, payload, opts = {}) {
|
|
1774
|
-
return buildEnvelopeRaw(type, payload, opts);
|
|
1775
|
-
}
|
|
1776
|
-
function encode(frame) {
|
|
1777
|
-
return JSON.stringify(frame);
|
|
1778
|
-
}
|
|
1779
|
-
//#endregion
|
|
1780
|
-
//#region ../protocol/dist/frames/relay-daemon.js
|
|
1781
|
-
/**
|
|
1782
|
-
* relay↔daemon frames (`rd/*`) — shared-bot-relay.md §7.2.
|
|
1783
|
-
*
|
|
1784
|
-
* The daemon dials OUT to every relay in its roster (one WS per (daemon, relay)
|
|
1785
|
-
* pair, multiplexing every source). This wire CARRIES MESSAGE CONTENT — it is
|
|
1786
|
-
* the ingress data plane — so nothing on it is ever logged with its payload.
|
|
1787
|
-
*
|
|
1788
|
-
* Direction per frame:
|
|
1789
|
-
* - `rd/hello` (+ `rd/hello/ok`) D→R REQ / R→D REP — authenticate the dial-in
|
|
1790
|
-
* - `rd/msg` R→D REQ — one inbound item, already routed
|
|
1791
|
-
* - `rd/ack` D→R REP — receipt (+ webchat turn verdict)
|
|
1792
|
-
* - `rd/chat` D→R EVT — webchat output stream back to the
|
|
1793
|
-
* browser (webchat is the one BIDIRECTIONAL
|
|
1794
|
-
* source: its far end hangs off the relay)
|
|
1795
|
-
*
|
|
1796
|
-
* Milestone A carries the webchat slice only; `im` (shared bot) and `hook`
|
|
1797
|
-
* (webhook) members join the `rd/msg` union with milestone B.
|
|
1798
|
-
*/
|
|
1799
|
-
/** Subprotocol negotiated on the relay↔daemon socket. */
|
|
1800
|
-
const RELAY_DAEMON_SUBPROTOCOL = "agentconnect.rd.v1";
|
|
1801
|
-
/** The canonical path the relay mounts its daemon-facing `rd/*` server at, and
|
|
1802
|
-
* the daemon appends to a roster `daemonUrl` origin when dialing (mirrors the
|
|
1803
|
-
* daemon↔CP `/daemon/ws` convention). A roster url already ending in it is used
|
|
1804
|
-
* verbatim. */
|
|
1805
|
-
const RELAY_DAEMON_WS_PATH = "/rd/ws";
|
|
1806
|
-
const RdHello = object({
|
|
1807
|
-
apiKey: string().min(1),
|
|
1808
|
-
daemonId: string().uuid()
|
|
1809
|
-
});
|
|
1810
|
-
const RdHelloOk = object({ relayId: string().uuid() });
|
|
1811
|
-
const RelayWebchatOp = discriminatedUnion("op", [
|
|
1812
|
-
object({
|
|
1813
|
-
op: literal("turn"),
|
|
1814
|
-
text: string(),
|
|
1815
|
-
user: string().optional()
|
|
1816
|
-
}),
|
|
1817
|
-
object({
|
|
1818
|
-
op: literal("set_model"),
|
|
1819
|
-
model: string()
|
|
1820
|
-
}),
|
|
1821
|
-
object({
|
|
1822
|
-
op: literal("set_effort"),
|
|
1823
|
-
effort: string()
|
|
1824
|
-
}),
|
|
1825
|
-
object({
|
|
1826
|
-
op: literal("set_permission_mode"),
|
|
1827
|
-
permissionMode: string()
|
|
1828
|
-
}),
|
|
1829
|
-
object({
|
|
1830
|
-
op: literal("set_fast"),
|
|
1831
|
-
fastMode: boolean()
|
|
1832
|
-
}),
|
|
1833
|
-
object({ op: literal("cancel") }),
|
|
1834
|
-
object({ op: literal("close") })
|
|
1835
|
-
]);
|
|
1836
|
-
const RdMsg = discriminatedUnion("source", [object({
|
|
1837
|
-
source: literal("webchat"),
|
|
1838
|
-
agentId: string().uuid(),
|
|
1839
|
-
sessionKey: string().min(1),
|
|
1840
|
-
msgId: string().min(1),
|
|
1841
|
-
chatId: string().uuid(),
|
|
1842
|
-
payload: RelayWebchatOp
|
|
1843
|
-
})]);
|
|
1844
|
-
const RdAck = object({
|
|
1845
|
-
msgId: string(),
|
|
1846
|
-
accepted: boolean(),
|
|
1847
|
-
turnId: string().uuid().optional(),
|
|
1848
|
-
reason: string().optional()
|
|
1849
|
-
});
|
|
1850
|
-
const RdChatEvent = discriminatedUnion("kind", [object({
|
|
1851
|
-
kind: literal("output"),
|
|
1852
|
-
output: WebchatOutput
|
|
1853
|
-
}), object({
|
|
1854
|
-
kind: literal("done"),
|
|
1855
|
-
done: WebchatDone
|
|
1856
|
-
})]);
|
|
1857
|
-
/** `type` string → payload schema for the relay↔daemon wire. */
|
|
1858
|
-
const RELAY_DAEMON_SCHEMAS = {
|
|
1859
|
-
"rd/hello": RdHello,
|
|
1860
|
-
"rd/hello/ok": RdHelloOk,
|
|
1861
|
-
"rd/msg": RdMsg,
|
|
1862
|
-
"rd/ack": RdAck,
|
|
1863
|
-
"rd/chat": object({
|
|
1864
|
-
chatId: string().uuid(),
|
|
1865
|
-
seq: number().int(),
|
|
1866
|
-
event: RdChatEvent
|
|
1867
|
-
}),
|
|
1868
|
-
error: ErrorFrame
|
|
1869
|
-
};
|
|
1870
|
-
Object.keys(RELAY_DAEMON_SCHEMAS);
|
|
1871
|
-
discriminatedUnion("type", [
|
|
1872
|
-
frameSchema("rd/hello", RELAY_DAEMON_SCHEMAS["rd/hello"]),
|
|
1873
|
-
frameSchema("rd/hello/ok", RELAY_DAEMON_SCHEMAS["rd/hello/ok"]),
|
|
1874
|
-
frameSchema("rd/msg", RELAY_DAEMON_SCHEMAS["rd/msg"]),
|
|
1875
|
-
frameSchema("rd/ack", RELAY_DAEMON_SCHEMAS["rd/ack"]),
|
|
1876
|
-
frameSchema("rd/chat", RELAY_DAEMON_SCHEMAS["rd/chat"]),
|
|
1877
|
-
frameSchema("error", RELAY_DAEMON_SCHEMAS["error"])
|
|
1878
|
-
]);
|
|
1879
|
-
/** Decode one relay↔daemon wire frame (envelope + typed payload). */
|
|
1880
|
-
function decodeRelayDaemonFrame(text) {
|
|
1881
|
-
return decodeEnvelopeWith(RELAY_DAEMON_SCHEMAS, text);
|
|
1882
|
-
}
|
|
1883
|
-
/** Build a relay↔daemon frame with a compile-time-typed payload. */
|
|
1884
|
-
function buildRelayDaemonFrame(type, payload, opts = {}) {
|
|
1885
|
-
return buildEnvelopeRaw(type, payload, opts);
|
|
1886
|
-
}
|
|
1887
|
-
//#endregion
|
|
1888
|
-
//#region ../protocol/dist/git-url.js
|
|
1889
|
-
/**
|
|
1890
|
-
* Git repo address helpers, shared by the CP (normalize on write) and the
|
|
1891
|
-
* daemon (defensive normalize before clone).
|
|
1892
|
-
*
|
|
1893
|
-
* STORAGE INVARIANT: `workspace.gitRepo` is persisted as a FULL cloneable git
|
|
1894
|
-
* address (e.g. `https://github.com/acme/infra`), never the `acme/infra`
|
|
1895
|
-
* shorthand a user may type. UIs shorten it back to `org/repo` for display
|
|
1896
|
-
* with `gitRepoLabel`.
|
|
1897
|
-
*/
|
|
1898
|
-
/** Scheme-full address: https://, ssh://, git://, file://, … */
|
|
1899
|
-
const SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
1900
|
-
/** scp-like ssh address: git@github.com:acme/infra(.git) */
|
|
1901
|
-
const SCP_RE = /^[\w.-]+@[\w.-]+:(.+)$/;
|
|
1902
|
-
/**
|
|
1903
|
-
* Normalize a user-supplied repo reference into a full cloneable git address.
|
|
1904
|
-
* Idempotent — full addresses pass through unchanged.
|
|
1905
|
-
*
|
|
1906
|
-
* - `https://…` / `ssh://…` / `git://…` / `git@host:…` → as-is
|
|
1907
|
-
* - `github.com/acme/infra` (host-prefixed shorthand) → `https://github.com/acme/infra`
|
|
1908
|
-
* - `acme/infra` (bare org/repo) → `https://github.com/acme/infra`
|
|
1909
|
-
* - anything else (e.g. a local path) → as-is
|
|
1910
|
-
*/
|
|
1911
|
-
function normalizeGitUrl(input) {
|
|
1912
|
-
const s = input.trim().replace(/\/+$/, "");
|
|
1913
|
-
if (!s || SCHEME_RE.test(s) || SCP_RE.test(s)) return s;
|
|
1914
|
-
const segments = s.split("/");
|
|
1915
|
-
if (segments.length >= 3 && segments[0].includes(".")) return `https://${s}`;
|
|
1916
|
-
if (segments.length === 2 && segments.every((p) => p.length > 0)) return `https://github.com/${s}`;
|
|
1917
|
-
return s;
|
|
1918
|
-
}
|
|
1919
|
-
//#endregion
|
|
1920
|
-
//#region ../protocol/dist/index.js
|
|
1921
|
-
/**
|
|
1922
|
-
* Narrowing guard factory: `isFrame("auth")(frame)` narrows a decoded
|
|
1923
|
-
* `AnyFrame` to the member whose `type` matches.
|
|
1924
|
-
*/
|
|
1925
|
-
function isFrame(type) {
|
|
1926
|
-
return (frame) => frame.type === type;
|
|
1927
|
-
}
|
|
1928
|
-
//#endregion
|
|
1929
374
|
//#region src/agents/memory.ts
|
|
1930
375
|
/**
|
|
1931
376
|
* Agent memory — a DIRECTORY at the agent's ROOT (`<agent-root>/memory/`), OUTSIDE
|
|
@@ -11554,6 +9999,6 @@ var AcpHost = class {
|
|
|
11554
9999
|
}
|
|
11555
10000
|
};
|
|
11556
10001
|
//#endregion
|
|
11557
|
-
export {
|
|
10002
|
+
export { resolveRuntimes as S, writeMemoryFile as _, initGitInjection as a, cachedRuntimeNames as b, simpleGit as c, memoryProviderFor as d, MemoryConflictError as f, resolveInMemoryDir as g, listMemory as h, prepareWorkspace as i, createMemoryProvider as l, MemoryTooLargeError as m, installedRuntimes as n, probeGitVersion as o, MemoryPathError as p, prefetchWorkspace as r, sessionGitEnv as s, AcpHost as t, memoryKindOf as u, MEMORY_TOOL_NAMES as v, cachedRuntimeVersions as x, toolsForIntegrations as y };
|
|
11558
10003
|
|
|
11559
|
-
//# sourceMappingURL=acp-host-
|
|
10004
|
+
//# sourceMappingURL=acp-host-CUct-2CR.js.map
|