@openparachute/agent 0.2.2 → 0.2.3-rc.3

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.
Files changed (58) hide show
  1. package/.parachute/module.json +3 -3
  2. package/package.json +4 -1
  3. package/src/transports/vault.ts +40 -22
  4. package/web/ui/dist/assets/index-5KEwEhfi.js +60 -0
  5. package/web/ui/dist/index.html +1 -1
  6. package/src/_parked/interactive-spawn.test.ts +0 -324
  7. package/src/_parked/interactive-spawn.ts +0 -701
  8. package/src/agent-defs.test.ts +0 -1504
  9. package/src/agent-mcp-config.test.ts +0 -115
  10. package/src/agents.test.ts +0 -360
  11. package/src/auth.test.ts +0 -46
  12. package/src/backends/attached-queue.test.ts +0 -376
  13. package/src/backends/programmatic.test.ts +0 -1715
  14. package/src/backends/registry.test.ts +0 -1494
  15. package/src/backends/stream-json.test.ts +0 -570
  16. package/src/channel-backend-wiring.test.ts +0 -237
  17. package/src/credentials.test.ts +0 -274
  18. package/src/cron.test.ts +0 -342
  19. package/src/daemon-agent-def-api.test.ts +0 -166
  20. package/src/daemon-agent-defs-api.test.ts +0 -953
  21. package/src/daemon-agent-env-api.test.ts +0 -338
  22. package/src/daemon-attached-queue-store.test.ts +0 -65
  23. package/src/daemon-config-api.test.ts +0 -962
  24. package/src/daemon-jobs-api.test.ts +0 -271
  25. package/src/daemon-vault-chat.test.ts +0 -250
  26. package/src/daemon.test.ts +0 -746
  27. package/src/def-vaults.test.ts +0 -136
  28. package/src/delivery-state.test.ts +0 -110
  29. package/src/effective-env.test.ts +0 -114
  30. package/src/grants.test.ts +0 -638
  31. package/src/hub-jwt.test.ts +0 -161
  32. package/src/jobs.test.ts +0 -245
  33. package/src/mcp-http.test.ts +0 -265
  34. package/src/mint-token.test.ts +0 -152
  35. package/src/module-manifest.test.ts +0 -158
  36. package/src/programmatic-wiring.test.ts +0 -838
  37. package/src/registry.test.ts +0 -227
  38. package/src/resolve-port.test.ts +0 -64
  39. package/src/routing.test.ts +0 -184
  40. package/src/runner.test.ts +0 -506
  41. package/src/sandbox/config.test.ts +0 -150
  42. package/src/sandbox/egress.test.ts +0 -113
  43. package/src/sandbox/live-seatbelt.test.ts +0 -277
  44. package/src/sandbox/mounts.test.ts +0 -154
  45. package/src/sandbox/sandbox.test.ts +0 -168
  46. package/src/services-manifest.test.ts +0 -106
  47. package/src/spa-serve.test.ts +0 -116
  48. package/src/spawn-agent-cli.test.ts +0 -172
  49. package/src/spawn-agent.test.ts +0 -1218
  50. package/src/spawn-deps.test.ts +0 -54
  51. package/src/terminal-assets.test.ts +0 -50
  52. package/src/terminal.test.ts +0 -530
  53. package/src/transports/http-ui.test.ts +0 -455
  54. package/src/transports/telegram.test.ts +0 -174
  55. package/src/transports/vault.test.ts +0 -2011
  56. package/src/ui-kit.test.ts +0 -178
  57. package/web/ui/dist/assets/index-VFETBk0a.js +0 -60
  58. package/web/ui/tsconfig.json +0 -21
@@ -52,7 +52,7 @@
52
52
  "module": "vault",
53
53
  "event": "note.created",
54
54
  "filter": {
55
- "tags": ["#agent/message/inbound"],
55
+ "tags": ["agent/message/inbound"],
56
56
  "has_metadata": ["channel"],
57
57
  "missing_metadata": ["channel_inbound_rendered_at"]
58
58
  }
@@ -85,7 +85,7 @@
85
85
  "module": "vault",
86
86
  "event": "note.created",
87
87
  "filter": {
88
- "tags": ["#agent/definition"]
88
+ "tags": ["agent/definition"]
89
89
  }
90
90
  },
91
91
  "sink": {
@@ -110,7 +110,7 @@
110
110
  "module": "vault",
111
111
  "event": "note.updated",
112
112
  "filter": {
113
- "tags": ["#agent/definition"]
113
+ "tags": ["agent/definition"]
114
114
  }
115
115
  },
116
116
  "sink": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/agent",
3
- "version": "0.2.2",
3
+ "version": "0.2.3-rc.3",
4
4
  "description": "Vault-native agents for Claude Code — a #agent/definition note + an inbound message becomes a sandboxed claude turn; the reply is written back as a note. Messaging gateway on :1941.",
5
5
  "license": "AGPL-3.0",
6
6
  "type": "module",
@@ -24,6 +24,8 @@
24
24
  "test:spa": "cd web/ui && bun run test",
25
25
  "test:all": "bun run test && bun run test:spa",
26
26
  "test:e2e": "bun e2e/llm/run.ts",
27
+ "lint": "biome check .",
28
+ "lint:fix": "biome check --write .",
27
29
  "typecheck": "tsc --noEmit",
28
30
  "build:spa": "cd web/ui && bun install --frozen-lockfile && bun run build",
29
31
  "prepack": "bun run build:spa"
@@ -39,6 +41,7 @@
39
41
  "typescript": "^5"
40
42
  },
41
43
  "devDependencies": {
44
+ "@biomejs/biome": "^1.9.4",
42
45
  "@types/bun": "latest"
43
46
  },
44
47
  "repository": {
@@ -96,6 +96,17 @@ export interface VaultTransportConfig {
96
96
  webhookSecret?: string;
97
97
  /** Optional path prefix for written notes. Default `channel`. */
98
98
  notePathPrefix?: string;
99
+ /**
100
+ * Whether `start()` fires the best-effort `ensureSchema()` tag-schema upsert
101
+ * against the connected vault. Default `true` (back-compat — the daemon always
102
+ * declares the module's tag inheritance on connect). Tests that construct a
103
+ * transport with a fake token set this `false` so `start()` does NOT hit the
104
+ * live vault on 127.0.0.1:1940 (which 401s the fake token → ~one `console.warn`
105
+ * per schema entry of benign noise). The "tag both parent + child" write floor
106
+ * means a channel works regardless, so skipping the declaration is safe; it's
107
+ * only a setup optimization, not a runtime contract. See #32.
108
+ */
109
+ declareSchemaOnStart?: boolean;
99
110
  }
100
111
 
101
112
  /** The note shape the daemon hands `ingestInbound` (a subset of the trigger payload). */
@@ -261,11 +272,11 @@ export class InboundClaimConflictError extends Error {
261
272
  /** Parent tag (NEW, namespaced) — carried LITERALLY on every note WE write; query
262
273
  * this + metadata.channel to see BOTH directions of a channel (the slash children
263
274
  * are namespace, not inheritance). */
264
- const AGENT_MESSAGE_TAG = "#agent/message";
275
+ const AGENT_MESSAGE_TAG = "agent/message";
265
276
  /** Inbound child (NEW) — the vault trigger fires on this exact tag (never matches outbound → no loop). */
266
- const AGENT_MESSAGE_INBOUND_TAG = "#agent/message/inbound";
277
+ const AGENT_MESSAGE_INBOUND_TAG = "agent/message/inbound";
267
278
  /** Outbound child (NEW) — replies carry this; the trigger's exact-match predicate excludes it. */
268
- const AGENT_MESSAGE_OUTBOUND_TAG = "#agent/message/outbound";
279
+ const AGENT_MESSAGE_OUTBOUND_TAG = "agent/message/outbound";
269
280
 
270
281
  /** Metadata key carrying the channel-queue claim status (design 2026-06-18). */
271
282
  const STATUS_META_KEY = "status";
@@ -369,7 +380,7 @@ function buildThreadSummaryBody(t: {
369
380
  * (it always queries the exact leaf tag); it exists for the nice human rollup, per
370
381
  * the design's namespacing decision.
371
382
  */
372
- export const AGENT_ROOT_TAG = "#agent";
383
+ export const AGENT_ROOT_TAG = "agent";
373
384
 
374
385
  /**
375
386
  * Agent-definition tag — a vault-native agent IS a `#agent/definition` note (design
@@ -377,7 +388,7 @@ export const AGENT_ROOT_TAG = "#agent";
377
388
  * METADATA is the config (name, backend, workspace, isolation, the def-vault binding).
378
389
  * The module reads these notes from a def-vault and instantiates each as a live agent.
379
390
  */
380
- export const AGENT_DEFINITION_TAG = "#agent/definition";
391
+ export const AGENT_DEFINITION_TAG = "agent/definition";
381
392
 
382
393
  /**
383
394
  * Scheduled-job tag — the runner's vault-native job store (design
@@ -386,7 +397,7 @@ export const AGENT_DEFINITION_TAG = "#agent/definition";
386
397
  * `#agent/message`. Introduced in Phase 2 as the flat `#agent-job`; moved into the
387
398
  * `#agent/*` namespace (`#agent/job`) by the vault-native-agents work (Phase 4a).
388
399
  */
389
- export const AGENT_JOB_TAG = "#agent/job";
400
+ export const AGENT_JOB_TAG = "agent/job";
390
401
  /** Default path prefix under which job notes are written: `Channels/<ch>/jobs/<id>`. */
391
402
  const JOB_PATH_PREFIX = "Channels";
392
403
 
@@ -413,7 +424,7 @@ const JOB_PATH_PREFIX = "Channels";
413
424
  * The note carries `['#agent/thread']` EXACTLY — NOT a message tag, NOT the inbound
414
425
  * child — so it can never wake a session (no loop).
415
426
  */
416
- export const AGENT_THREAD_TAG = "#agent/thread";
427
+ export const AGENT_THREAD_TAG = "agent/thread";
417
428
  /** Default path prefix under which thread notes are written: `Threads/<ch>/<leaf>`. */
418
429
  const THREAD_PATH_PREFIX = "Threads";
419
430
 
@@ -557,7 +568,7 @@ export const AGENT_VAULT_TRIGGER_TEMPLATE = {
557
568
  name: "channel_inbound_<channel>", // hub substitutes the channel name
558
569
  events: ["created"],
559
570
  when: {
560
- tags: ["#agent/message/inbound"],
571
+ tags: ["agent/message/inbound"],
561
572
  has_metadata: ["agent"],
562
573
  missing_metadata: ["channel_inbound_rendered_at"],
563
574
  },
@@ -581,7 +592,7 @@ export const AGENT_DEF_VAULT_TRIGGER_TEMPLATE = {
581
592
  name: "agent_def_reload",
582
593
  events: ["created", "updated", "deleted"],
583
594
  when: {
584
- tags: ["#agent/definition"],
595
+ tags: ["agent/definition"],
585
596
  },
586
597
  action: {
587
598
  webhook: "<hub-origin>/agent/api/vault/agent-def", // hub fills origin + the auth.bearer
@@ -605,6 +616,8 @@ export class VaultTransport implements Transport {
605
616
  */
606
617
  readonly webhookSecret?: string;
607
618
  private readonly pathPrefix: string;
619
+ /** See `VaultTransportConfig.declareSchemaOnStart`. Default `true`. */
620
+ private readonly declareSchemaOnStart: boolean;
608
621
 
609
622
  constructor(config: VaultTransportConfig) {
610
623
  if (!config.vault) {
@@ -621,6 +634,7 @@ export class VaultTransport implements Transport {
621
634
  this.token = config.token;
622
635
  this.webhookSecret = config.webhookSecret;
623
636
  this.pathPrefix = (config.notePathPrefix ?? DEFAULT_PATH_PREFIX).replace(/\/$/, "");
637
+ this.declareSchemaOnStart = config.declareSchemaOnStart ?? true;
624
638
  }
625
639
 
626
640
  /**
@@ -642,7 +656,11 @@ export class VaultTransport implements Transport {
642
656
  // "tag both parent + child" floor in the note writes is the fail-safe, so the
643
657
  // channel works even if this declaration never lands. Fire-and-forget — no
644
658
  // reason to delay the channel coming up on a schema upsert.
645
- void this.ensureSchema();
659
+ //
660
+ // Suppressible via `declareSchemaOnStart: false` — tests with a fake token
661
+ // set this so `start()` doesn't 401 against the live vault (benign warn noise,
662
+ // #32). The write floor makes the declaration optional anyway.
663
+ if (this.declareSchemaOnStart) void this.ensureSchema();
646
664
  }
647
665
 
648
666
  // -------------------------------------------------------------------------
@@ -657,11 +675,11 @@ export class VaultTransport implements Transport {
657
675
  * `decodeURIComponent`'d (parachute-vault `src/routes.ts` handleTags, the
658
676
  * "Routes with tag name" block + `routing.ts` `apiPath.startsWith("/tags")`).
659
677
  * Because the route matches a SINGLE path segment (`[^/]+`, no literal slash)
660
- * and decodes it, the tag name — which contains BOTH `#` and `/`
661
- * (`#agent/message/inbound`) — must be `encodeURIComponent`'d so the `#`
662
- * becomes `%23` and the `/` becomes `%2F`; the route then decodes that back to
663
- * the literal name. A bare `/` in the URL would fail the `[^/]+` match → 404,
664
- * silently dropping the declaration. The PUT body is `{ description?, parent_names? }`.
678
+ * and decodes it, the tag name — which contains a `/`
679
+ * (`agent/message/inbound`) — must be `encodeURIComponent`'d so the `/` becomes
680
+ * `%2F`; the route then decodes that back to the literal name. A bare `/` in the
681
+ * URL would fail the `[^/]+` match → 404, silently dropping the declaration. The
682
+ * PUT body is `{ description?, parent_names? }`.
665
683
  *
666
684
  * Best-effort + non-fatal by contract: every failure is caught and `console.warn`'d,
667
685
  * never thrown — the tag-both write floor is the fallback.
@@ -669,8 +687,8 @@ export class VaultTransport implements Transport {
669
687
  async ensureSchema(): Promise<void> {
670
688
  for (const entry of AGENT_VAULT_TAG_SCHEMA) {
671
689
  try {
672
- // Single-segment, percent-encoded name: `#agent/message/inbound` →
673
- // `%23agent%2Fmessage%2Finbound`. The vault decodes it back to the literal.
690
+ // Single-segment, percent-encoded name: `agent/message/inbound` →
691
+ // `agent%2Fmessage%2Finbound`. The vault decodes it back to the literal.
674
692
  const url = `${this.vaultUrl}/vault/${this.vault}/api/tags/${encodeURIComponent(entry.name)}`;
675
693
  const body: {
676
694
  description?: string;
@@ -1099,7 +1117,7 @@ export class VaultTransport implements Transport {
1099
1117
  * namespace, not query inheritance, so we never key off them here.
1100
1118
  *
1101
1119
  * GET <vaultUrl>/vault/<vault>/api/notes
1102
- * ?tag=%23agent%2Fmessage (the `#` + `/` MUST be percent-encoded)
1120
+ * ?tag=agent%2Fmessage (the `/` MUST be percent-encoded)
1103
1121
  * &include_content=true (we need the bodies)
1104
1122
  * &limit=<n> (default 200)
1105
1123
  *
@@ -1138,7 +1156,7 @@ export class VaultTransport implements Transport {
1138
1156
  // empty transcript).
1139
1157
  const fetchByTag = async (tag: string): Promise<RawNote[]> => {
1140
1158
  const params = new URLSearchParams();
1141
- params.set("tag", tag); // URLSearchParams encodes `#` → `%23`
1159
+ params.set("tag", tag); // URLSearchParams encodes `/` → `%2F`
1142
1160
  params.set("include_content", "true");
1143
1161
  params.set("limit", String(fetchLimit));
1144
1162
  const url = `${this.vaultUrl}/vault/${this.vault}/api/notes?${params.toString()}`;
@@ -1380,7 +1398,7 @@ export class VaultTransport implements Transport {
1380
1398
  // Overfetch (the tag query spans all channels) then keep this channel's items.
1381
1399
  const fetchLimit = Math.min(Math.max(limit * 4, 500), 2000);
1382
1400
  const params = new URLSearchParams();
1383
- params.set("tag", AGENT_MESSAGE_INBOUND_TAG); // → %23agent%2Fmessage%2Finbound
1401
+ params.set("tag", AGENT_MESSAGE_INBOUND_TAG); // → agent%2Fmessage%2Finbound
1384
1402
  params.set("include_content", "true");
1385
1403
  params.set("limit", String(fetchLimit));
1386
1404
  // NEWEST-first at the vault (default order_by is `updated_at`) so a hard cap drops
@@ -1508,7 +1526,7 @@ export class VaultTransport implements Transport {
1508
1526
 
1509
1527
  /**
1510
1528
  * List the scheduled-job notes in THIS channel's vault. Queries by the parent
1511
- * `#agent/job` tag (URLSearchParams encodes `#`→`%23`, `/`→`%2F`) and returns ALL job
1529
+ * `#agent/job` tag (URLSearchParams encodes `/`→`%2F`) and returns ALL job
1512
1530
  * notes in the vault — the CALLER filters by `metadata.channel` (same index-free
1513
1531
  * pattern as loadTranscript; we don't assume a `channel` index exists). Throws
1514
1532
  * on a non-ok vault response so the API surfaces a clear error rather than a
@@ -1517,7 +1535,7 @@ export class VaultTransport implements Transport {
1517
1535
  async listJobNotes(opts?: { limit?: number }): Promise<JobNote[]> {
1518
1536
  const limit = opts?.limit ?? 500;
1519
1537
  const params = new URLSearchParams();
1520
- params.set("tag", AGENT_JOB_TAG); // → %23agent%2Fjob
1538
+ params.set("tag", AGENT_JOB_TAG); // → agent%2Fjob
1521
1539
  params.set("include_content", "true");
1522
1540
  params.set("limit", String(limit));
1523
1541
  const url = `${this.vaultUrl}/vault/${this.vault}/api/notes?${params.toString()}`;