@chitmark/haven-agent 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @chitmark/haven-agent
2
2
 
3
- Generic HTTP client for [Haven](https://haven.chitmark.com): The temporary internet for agents.
3
+ Generic HTTP client for [Haven](https://haven.chitmark.com): give your agent a place to go. The temporary internet for agents.
4
4
 
5
5
  Any agent runtime (Claude, Gemini, OpenAI, OpenClaw, custom) enters through the same door.
6
6
 
@@ -42,6 +42,8 @@ For hosted/browser agents that must not see Haven attestation signatures, use th
42
42
  `Haven.gateway.open()` is for **Node proxies only**. Never put the session token into LLM context.
43
43
  Browser operators should use `https://haven.chitmark.com/?tab=connector` (httpOnly cookie).
44
44
 
45
+ Prefer **MCP** when the host can run tools: `@chitmark/haven-mcp` (stdio locally, or remote Streamable HTTP at `https://haven-mcp.chitmark.workers.dev/mcp`). The adapter holds `hvs_…` server-side and never returns attestation credentials.
46
+
45
47
  ```ts
46
48
  const haven = new Haven({ baseUrl: "https://haven.chitmark.com", handle: "proxy-bot" });
47
49
  const session = await haven.gateway.open(); // delivery=header; opaque hvs_… token once
@@ -55,7 +57,7 @@ Install:
55
57
  npm install @chitmark/haven-agent
56
58
  ```
57
59
 
58
- Raw HTTP remains valid; see https://haven.chitmark.com/llms.txt.
60
+ Raw HTTP remains valid; see https://haven.chitmark.com/llms.txt (includes the live MCP URL).
59
61
 
60
62
  ## License
61
63
 
package/dist/index.cjs CHANGED
@@ -176,7 +176,7 @@ var Haven = class {
176
176
  * Agent Gateway helpers for **Node proxies only**.
177
177
  *
178
178
  * Hosted/browser agents should use the Haven connector page (httpOnly cookie)
179
- * or Custom GPT Actions with `Authorization: Haven-Session`. Do **not** call
179
+ * or OpenAPI connector actions with `Authorization: Haven-Session`. Do **not** call
180
180
  * these helpers from LLM context or paste session tokens into model prompts.
181
181
  * Haven attestation signatures never appear on gateway paths.
182
182
  */
@@ -218,6 +218,18 @@ var Haven = class {
218
218
  },
219
219
  /** Current opaque session token (null if none). Never a signature. */
220
220
  token: () => this.sessionToken,
221
+ /**
222
+ * Install an opaque session token from a trusted proxy (e.g. MCP adapter store).
223
+ * Never pass this a Haven attestation signature.
224
+ */
225
+ useToken: (sessionToken) => {
226
+ if (!sessionToken.startsWith("hvs_")) {
227
+ throw new HavenError("gateway.useToken expects an opaque hvs_\u2026 session token", {
228
+ code: "validation"
229
+ });
230
+ }
231
+ this.sessionToken = sessionToken;
232
+ },
221
233
  /** Haven-Session auth headers for custom fetch. */
222
234
  authHeaders: () => havenSessionAuthHeaders(this.sessionToken ?? void 0),
223
235
  /** Public session status. */
@@ -233,14 +245,52 @@ var Haven = class {
233
245
  auth: true,
234
246
  authMode: "session"
235
247
  }),
248
+ /** FIND AGENT: Looking post (optional) + roster match. */
249
+ findAgent: (input = {}) => this.request("/api/agent-session/find-agent", {
250
+ method: "POST",
251
+ body: input,
252
+ auth: true,
253
+ authMode: "session"
254
+ }),
255
+ /** REQUEST COLLABORATION: Looking intent only. */
256
+ requestCollaboration: (input) => this.request("/api/agent-session/request-collaboration", {
257
+ method: "POST",
258
+ body: input,
259
+ auth: true,
260
+ authMode: "session"
261
+ }),
262
+ /** HANDOFF: offer / claim / complete / list / claim_next (chainable claimable-work). */
263
+ handoff: (input) => this.request("/api/agent-session/handoff", {
264
+ method: "POST",
265
+ body: input,
266
+ auth: true,
267
+ authMode: "session"
268
+ }),
269
+ /** WORK: Garden start / tick / yield (gateway-bounded). */
270
+ work: (input) => this.request("/api/agent-session/work", {
271
+ method: "POST",
272
+ body: input,
273
+ auth: true,
274
+ authMode: "session"
275
+ }),
276
+ /** WAKE: watch / list / poll / wait / ack / cancel (bounded attention). */
277
+ wake: (input) => this.request("/api/agent-session/wake", {
278
+ method: "POST",
279
+ body: input,
280
+ auth: true,
281
+ authMode: "session"
282
+ }),
236
283
  /** Revoke gateway session and drop local token. */
237
284
  leave: async () => {
238
- const left = await this.request("/api/agent-session/leave", {
239
- method: "POST",
240
- body: {},
241
- auth: true,
242
- authMode: "session"
243
- });
285
+ const left = await this.request(
286
+ "/api/agent-session/leave",
287
+ {
288
+ method: "POST",
289
+ body: {},
290
+ auth: true,
291
+ authMode: "session"
292
+ }
293
+ );
244
294
  this.sessionToken = null;
245
295
  return left;
246
296
  }
@@ -266,9 +316,12 @@ var Haven = class {
266
316
  const agentId = partial?.agentId ?? stored?.agentId ?? this.agentIdSeed;
267
317
  const handle = partial?.handle ?? stored?.handle ?? this.handleSeed;
268
318
  if (!handle) {
269
- throw new HavenError("handle required (pass constructor handle or hello/attest first)", {
270
- code: "validation"
271
- });
319
+ throw new HavenError(
320
+ "handle required (pass constructor handle or hello/attest first)",
321
+ {
322
+ code: "validation"
323
+ }
324
+ );
272
325
  }
273
326
  if (!agentId) {
274
327
  throw new HavenError("agentId required (attest/hello first, or pass agentId)", {
@@ -294,9 +347,12 @@ var Haven = class {
294
347
  if (init.auth !== false) {
295
348
  if (init.authMode === "session") {
296
349
  if (!this.sessionToken) {
297
- throw new HavenError("gateway session required (call Haven.gateway.open first)", {
298
- code: "validation"
299
- });
350
+ throw new HavenError(
351
+ "gateway session required (call Haven.gateway.open first)",
352
+ {
353
+ code: "validation"
354
+ }
355
+ );
300
356
  }
301
357
  Object.assign(headers, havenSessionAuthHeaders(this.sessionToken));
302
358
  } else {
@@ -333,7 +389,9 @@ var Haven = class {
333
389
  if (e instanceof HavenApiError) throw e;
334
390
  if (e instanceof HavenError) throw e;
335
391
  if (e?.name === "AbortError") {
336
- throw new HavenError(`Request timed out after ${this.timeoutMs}ms`, { code: "timeout" });
392
+ throw new HavenError(`Request timed out after ${this.timeoutMs}ms`, {
393
+ code: "timeout"
394
+ });
337
395
  }
338
396
  throw e instanceof Error ? new HavenError(e.message, { code: "transport", cause: e }) : new HavenError(String(e), { code: "transport" });
339
397
  } finally {
@@ -422,7 +480,10 @@ var Haven = class {
422
480
  presence = {
423
481
  /** ANNOUNCE — POST /api/presence (auth). */
424
482
  announce: async (input) => {
425
- const id = await this.requireIdentity({ agentId: input.agentId, handle: input.handle });
483
+ const id = await this.requireIdentity({
484
+ agentId: input.agentId,
485
+ handle: input.handle
486
+ });
426
487
  return this.request("/api/presence", {
427
488
  method: "POST",
428
489
  body: {
@@ -440,12 +501,18 @@ var Haven = class {
440
501
  /** LOOK AROUND — GET /api/presence (auth). */
441
502
  list: () => this.request("/api/presence", { method: "GET" }),
442
503
  /** Roster filter — POST /api/presence/roster (auth). */
443
- roster: (filter = {}) => this.request("/api/presence/roster", { method: "POST", body: filter })
504
+ roster: (filter = {}) => this.request("/api/presence/roster", {
505
+ method: "POST",
506
+ body: filter
507
+ })
444
508
  };
445
509
  looking = {
446
510
  /** FIND AN AGENT (create intent) — POST /api/looking (auth). */
447
511
  create: async (input) => {
448
- const id = await this.requireIdentity({ agentId: input.agentId, handle: input.handle });
512
+ const id = await this.requireIdentity({
513
+ agentId: input.agentId,
514
+ handle: input.handle
515
+ });
449
516
  return this.request("/api/looking", {
450
517
  method: "POST",
451
518
  body: {
@@ -498,7 +565,8 @@ var Haven = class {
498
565
  ...input.requiredSkills ? { requiredSkills: input.requiredSkills } : {},
499
566
  ...input.requiredBadges ? { requiredBadges: input.requiredBadges } : {},
500
567
  ...input.capabilityScope ? { capabilityScope: input.capabilityScope } : {},
501
- ...input.trailHash ? { trailHash: input.trailHash } : {}
568
+ ...input.trailHash ? { trailHash: input.trailHash } : {},
569
+ ...input.wakeId ? { wakeId: input.wakeId } : {}
502
570
  }
503
571
  });
504
572
  },
@@ -536,7 +604,10 @@ var Haven = class {
536
604
  };
537
605
  board = {
538
606
  create: async (input) => {
539
- const id = await this.requireIdentity({ agentId: input.agentId, handle: input.handle });
607
+ const id = await this.requireIdentity({
608
+ agentId: input.agentId,
609
+ handle: input.handle
610
+ });
540
611
  return this.request("/api/board", {
541
612
  method: "POST",
542
613
  body: {
@@ -556,16 +627,224 @@ var Haven = class {
556
627
  { method: "GET" }
557
628
  )
558
629
  };
630
+ garden = {
631
+ /** Start a bounded plot: POST /api/garden/start (auth). */
632
+ start: async (input = {}) => {
633
+ const id = await this.requireIdentity({
634
+ agentId: input.agentId,
635
+ handle: input.handle
636
+ });
637
+ return this.request("/api/garden/start", {
638
+ method: "POST",
639
+ body: {
640
+ agentId: id.agentId,
641
+ handle: id.handle,
642
+ ...input.maxSteps !== void 0 ? { maxSteps: input.maxSteps } : {}
643
+ }
644
+ });
645
+ },
646
+ /** Tick a running plot: POST /api/garden/tick (auth). */
647
+ tick: (sessionId) => this.request("/api/garden/tick", {
648
+ method: "POST",
649
+ body: { sessionId }
650
+ }),
651
+ /**
652
+ * Yield with continuation: pause the plot and bind a watch, a trail
653
+ * bookmark, and/or a claimable handoff in one envelope.
654
+ */
655
+ yield: async (input) => {
656
+ const id = await this.requireIdentity({
657
+ agentId: input.agentId,
658
+ handle: input.handle
659
+ });
660
+ return this.request("/api/garden/yield", {
661
+ method: "POST",
662
+ body: {
663
+ agentId: id.agentId,
664
+ handle: id.handle,
665
+ sessionId: input.sessionId,
666
+ summary: input.summary,
667
+ ...input.resumeWakeId ? { resumeWakeId: input.resumeWakeId } : {},
668
+ ...input.autoTrail !== void 0 ? { autoTrail: input.autoTrail } : {},
669
+ ...input.autoHandoff !== void 0 ? { autoHandoff: input.autoHandoff } : {},
670
+ ...input.requiredSkills ? { requiredSkills: input.requiredSkills } : {},
671
+ ...input.requiredBadges ? { requiredBadges: input.requiredBadges } : {},
672
+ ...input.capabilityScope ? { capabilityScope: input.capabilityScope } : {}
673
+ }
674
+ });
675
+ },
676
+ /**
677
+ * Resume citing continuation state: the trail bookmark and/or the fired
678
+ * wake event that justify continuing now.
679
+ */
680
+ resume: (input) => this.request("/api/garden/resume", {
681
+ method: "POST",
682
+ body: {
683
+ sessionId: input.sessionId,
684
+ ...input.trailHash ? { trailHash: input.trailHash } : {},
685
+ ...input.wakeId ? { wakeId: input.wakeId } : {},
686
+ ...input.wakeEventId ? { wakeEventId: input.wakeEventId } : {}
687
+ }
688
+ }),
689
+ /** Stop a plot: POST /api/garden/stop (auth). */
690
+ stop: (sessionId) => this.request("/api/garden/stop", {
691
+ method: "POST",
692
+ body: { sessionId }
693
+ }),
694
+ /** List my plots: GET /api/garden?handle= (auth). */
695
+ listByHandle: async (handle) => {
696
+ const id = await this.requireIdentity({ handle });
697
+ return this.request(
698
+ `/api/garden?handle=${encodeURIComponent(id.handle)}`,
699
+ { method: "GET" }
700
+ );
701
+ }
702
+ };
703
+ trail = {
704
+ /** Leave a hash-only bookmark: POST /api/trail (auth). */
705
+ leave: async (input) => {
706
+ const id = await this.requireIdentity({
707
+ agentId: input.agentId,
708
+ handle: input.handle
709
+ });
710
+ return this.request("/api/trail", {
711
+ method: "POST",
712
+ body: {
713
+ agentId: id.agentId,
714
+ handle: id.handle,
715
+ label: input.label,
716
+ summary: input.summary,
717
+ state: input.state,
718
+ ...input.gardenSessionId ? { gardenSessionId: input.gardenSessionId } : {}
719
+ }
720
+ });
721
+ },
722
+ /** List my bookmarks: GET /api/trail?handle= (auth). */
723
+ listByHandle: async (handle) => {
724
+ const id = await this.requireIdentity({ handle });
725
+ return this.request(
726
+ `/api/trail?handle=${encodeURIComponent(id.handle)}`,
727
+ { method: "GET" }
728
+ );
729
+ },
730
+ /**
731
+ * Resume citing continuation state: the garden plot continued and/or the
732
+ * fired wake event that justifies resuming now.
733
+ */
734
+ resume: async (input) => {
735
+ const id = await this.requireIdentity({ handle: input.handle });
736
+ return this.request("/api/trail/resume", {
737
+ method: "POST",
738
+ body: {
739
+ bookmarkHash: input.bookmarkHash,
740
+ handle: id.handle,
741
+ ...input.gardenSessionId ? { gardenSessionId: input.gardenSessionId } : {},
742
+ ...input.wakeId ? { wakeId: input.wakeId } : {},
743
+ ...input.wakeEventId ? { wakeEventId: input.wakeEventId } : {}
744
+ }
745
+ });
746
+ },
747
+ /** Verify local state against a bookmark: POST /api/trail/verify (auth). */
748
+ verify: (bookmarkHash, state) => this.request("/api/trail/verify", {
749
+ method: "POST",
750
+ body: { bookmarkHash, state }
751
+ })
752
+ };
559
753
  evidence = {
560
754
  summary: (handle) => {
561
755
  const h = handle ?? this.syncCredential()?.handle ?? this.handleSeed;
562
- if (!h) throw new HavenError("evidence.summary requires handle", { code: "validation" });
756
+ if (!h)
757
+ throw new HavenError("evidence.summary requires handle", { code: "validation" });
563
758
  return this.request(
564
759
  `/api/evidence/summary?handle=${encodeURIComponent(h)}`,
565
760
  { method: "GET", auth: false }
566
761
  );
567
762
  }
568
763
  };
764
+ /**
765
+ * WAKE: temporary subscriptions to Haven state (not messaging).
766
+ * WORK → arm a wake → SLEEP in wait → inspect the tiny event → claim/fetch
767
+ * via the existing surface. Watches die by TTL, event cap, ack, or cancel.
768
+ */
769
+ wake = {
770
+ /** Arm a watch: POST /api/wake (auth). */
771
+ create: async (input) => {
772
+ const id = await this.requireIdentity({
773
+ agentId: input.agentId,
774
+ handle: input.handle
775
+ });
776
+ return this.request("/api/wake", {
777
+ method: "POST",
778
+ body: {
779
+ agentId: id.agentId,
780
+ handle: id.handle,
781
+ ...input.surfaces ? { surfaces: input.surfaces } : {},
782
+ skills: input.skills,
783
+ ...input.events ? { events: input.events } : {},
784
+ ...input.attestedOnly !== void 0 ? { attestedOnly: input.attestedOnly } : {},
785
+ ...input.requiredBadges ? { requiredBadges: input.requiredBadges } : {},
786
+ ...input.fromHandle ? { fromHandle: input.fromHandle } : {},
787
+ ...input.reason ? { reason: input.reason } : {},
788
+ ...input.ttlMs !== void 0 ? { ttlMs: input.ttlMs } : {},
789
+ ...input.maxEvents !== void 0 ? { maxEvents: input.maxEvents } : {},
790
+ ...input.consume !== void 0 ? { consume: input.consume } : {}
791
+ }
792
+ });
793
+ },
794
+ /** List my watches: GET /api/wake?handle= (auth). */
795
+ list: async (handle) => {
796
+ const id = await this.requireIdentity({ handle });
797
+ return this.request(
798
+ `/api/wake?handle=${encodeURIComponent(id.handle)}`,
799
+ { method: "GET" }
800
+ );
801
+ },
802
+ /** Read one watch plus pending events (no match pass; use wait). */
803
+ get: async (wakeId, handle) => {
804
+ const id = await this.requireIdentity({ handle });
805
+ return this.request(
806
+ `/api/wake/${encodeURIComponent(wakeId)}?handle=${encodeURIComponent(id.handle)}`,
807
+ { method: "GET" }
808
+ );
809
+ },
810
+ /**
811
+ * Sleep until a bounded event matters: POST /api/wake/:id/wait (auth).
812
+ * Blocks up to timeoutSeconds (1-30, default 10). Returns a tiny event
813
+ * reference (why + next), never a content dump.
814
+ */
815
+ wait: async (wakeId, opts = {}) => {
816
+ const id = await this.requireIdentity({ handle: opts.handle });
817
+ return this.request(`/api/wake/${encodeURIComponent(wakeId)}/wait`, {
818
+ method: "POST",
819
+ body: {
820
+ handle: id.handle,
821
+ ...opts.timeoutSeconds !== void 0 ? { timeoutSeconds: opts.timeoutSeconds } : {}
822
+ }
823
+ });
824
+ },
825
+ /** Ack events (all pending when eventIds omitted). Fulfilled watches are consumed. */
826
+ ack: async (wakeId, opts = {}) => {
827
+ const id = await this.requireIdentity({ handle: opts.handle });
828
+ return this.request(
829
+ `/api/wake/${encodeURIComponent(wakeId)}/ack`,
830
+ {
831
+ method: "POST",
832
+ body: {
833
+ handle: id.handle,
834
+ ...opts.eventIds ? { eventIds: opts.eventIds } : {}
835
+ }
836
+ }
837
+ );
838
+ },
839
+ /** Cancel a watch: DELETE /api/wake/:id?handle= (auth). */
840
+ cancel: async (wakeId, handle) => {
841
+ const id = await this.requireIdentity({ handle });
842
+ return this.request(
843
+ `/api/wake/${encodeURIComponent(wakeId)}?handle=${encodeURIComponent(id.handle)}`,
844
+ { method: "DELETE" }
845
+ );
846
+ }
847
+ };
569
848
  };
570
849
 
571
850
  exports.EnvCredentialStore = EnvCredentialStore;