@hachej/boring-agent 0.1.20 → 0.1.23

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
@@ -28,6 +28,7 @@ npx @hachej/boring-agent
28
28
  | **Three execution modes** | `direct` (no isolation, macOS dev) / `local` (bwrap sandbox) / `vercel-sandbox` (Firecracker microVM) |
29
29
  | **CLI + embeddable** | `npx @hachej/boring-agent` works standalone; `<ChatPanel />` composes into any layout |
30
30
  | **7 standard tools** | `bash`, `read`, `write`, `edit`, `find`, `grep`, `ls` — ported from pi-coding-agent |
31
+ | **Workspace-local runtime provisioning** | Generates `.boring-agent` inside the selected workspace for mirrored skills, SDKs, CLIs, and templates |
31
32
  | **Workspace-agnostic FS** | `Workspace` interface — agent tools and HTTP routes share the same filesystem view |
32
33
  | **Session management** | List, create, switch, delete sessions with streamed history hydration |
33
34
  | **UI bridge** | Agent opens files, panels, and surfaces in the workbench via typed commands |
@@ -58,6 +59,21 @@ write a test for src/utils.ts
58
59
 
59
60
  ---
60
61
 
62
+ ## Workspace-local runtime provisioning
63
+
64
+ Boring UI keeps generated runtime state in the selected workspace at
65
+ `$BORING_AGENT_WORKSPACE_ROOT/.boring-agent`. Plugin skills are mirrored to
66
+ `.boring-agent/skills`, runtime CLIs live under `.boring-agent/node` or
67
+ `.boring-agent/venv`, and templates seed only missing workspace files. The
68
+ folder is generated/disposable and should not be hand-edited or committed.
69
+
70
+ See [docs/runtime-provisioning.md](docs/runtime-provisioning.md) for the full
71
+ user and plugin-author contract, including package metadata shape,
72
+ `provisionWorkspace: false`, `/api/v1/agent/reload`, and direct/local/Vercel
73
+ mode behavior.
74
+
75
+ ---
76
+
61
77
  ## Architecture
62
78
 
63
79
  ```
@@ -1,3 +1,16 @@
1
+ // src/shared/telemetry.ts
2
+ var noopTelemetry = {
3
+ capture() {
4
+ }
5
+ };
6
+ function safeCapture(telemetry, event) {
7
+ try {
8
+ void Promise.resolve(telemetry.capture(event)).catch(() => {
9
+ });
10
+ } catch {
11
+ }
12
+ }
13
+
1
14
  // src/shared/error-codes.ts
2
15
  import { z } from "zod";
3
16
  var ErrorCode = z.enum([
@@ -37,6 +50,15 @@ var ErrorCode = z.enum([
37
50
  // Plugin
38
51
  "PLUGIN_LOAD_FAILED",
39
52
  "PLUGIN_NAME_COLLISION",
53
+ // Runtime provisioning
54
+ "PROVISIONING_LAYOUT_FAILED",
55
+ "PROVISIONING_SKILLS_FAILED",
56
+ "PROVISIONING_TEMPLATES_FAILED",
57
+ "PROVISIONING_NODE_PREFLIGHT_FAILED",
58
+ "PROVISIONING_NPM_INSTALL_FAILED",
59
+ "PROVISIONING_UV_BOOTSTRAP_FAILED",
60
+ "PROVISIONING_UV_INSTALL_FAILED",
61
+ "PROVISIONING_ARTIFACT_FAILED",
40
62
  // Internal
41
63
  "INTERNAL_ERROR"
42
64
  ]);
@@ -68,6 +90,8 @@ function validateTool(tool) {
68
90
  }
69
91
 
70
92
  export {
93
+ noopTelemetry,
94
+ safeCapture,
71
95
  ErrorCode,
72
96
  ERROR_CODES,
73
97
  ApiErrorPayloadSchema,
@@ -5,7 +5,7 @@ import { T as ToolUiMetadata } from '../tool-ui-DIFNGwYd.js';
5
5
  import * as ai from 'ai';
6
6
  import { UIMessage, FileUIPart, ChatStatus } from 'ai';
7
7
  import * as _ai_sdk_react from '@ai-sdk/react';
8
- import { S as SendMessageInput, e as SessionSummary } from '../harness-DRrTn_5T.js';
8
+ import { S as SendMessageInput, e as SessionSummary } from '../harness-BCit36Ha.js';
9
9
  import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, InputGroupAddon, InputGroupButton, InputGroupTextarea } from '@hachej/boring-ui-kit';
10
10
  import { Streamdown } from 'streamdown';
11
11
  import { StickToBottom } from 'use-stick-to-bottom';
@@ -4551,7 +4551,7 @@ function useAttachmentNotice(timeoutMs = 4e3) {
4551
4551
 
4552
4552
  // src/front/chatPanelComposerControls.tsx
4553
4553
  import { useState as useState19 } from "react";
4554
- import { BotIcon, BrainIcon as BrainIcon2, CheckIcon as CheckIcon2, ChevronDownIcon as ChevronDownIcon5, EyeIcon, EyeOffIcon } from "lucide-react";
4554
+ import { CheckIcon as CheckIcon2, ChevronDownIcon as ChevronDownIcon5, EyeIcon, EyeOffIcon } from "lucide-react";
4555
4555
  import {
4556
4556
  Command as Command2,
4557
4557
  CommandEmpty as CommandEmpty2,
@@ -4603,6 +4603,33 @@ var THINKING_LEVEL_LABELS = {
4603
4603
  medium: "Med",
4604
4604
  high: "High"
4605
4605
  };
4606
+ function ThinkingLevelGlyph({ level }) {
4607
+ const lit = level === "off" ? 0 : level === "low" ? 1 : level === "medium" ? 2 : 3;
4608
+ return /* @__PURE__ */ jsx23(
4609
+ "svg",
4610
+ {
4611
+ "aria-hidden": "true",
4612
+ width: "14",
4613
+ height: "14",
4614
+ viewBox: "0 0 14 14",
4615
+ fill: "none",
4616
+ className: "shrink-0",
4617
+ children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx23(
4618
+ "rect",
4619
+ {
4620
+ x: 2 + i * 4,
4621
+ y: 10 - i * 2,
4622
+ width: "2",
4623
+ height: 3 + i * 2,
4624
+ rx: "0.5",
4625
+ fill: "currentColor",
4626
+ opacity: i < lit ? 1 : 0.25
4627
+ },
4628
+ i
4629
+ ))
4630
+ }
4631
+ );
4632
+ }
4606
4633
  function ModelSelect({
4607
4634
  value,
4608
4635
  onChange,
@@ -4641,13 +4668,16 @@ function ModelSelect({
4641
4668
  "aria-label": "Model",
4642
4669
  className: cn(
4643
4670
  composerActionClass,
4644
- "w-auto max-w-[min(52vw,200px)] px-2 text-xs font-medium",
4645
- open && "bg-muted/60 text-foreground"
4671
+ // Model is the only piece of state the composer carries between
4672
+ // turns — give it a status-pill shape so it reads as data, not
4673
+ // another tertiary control. The label is the signal; no icon
4674
+ // (the bot/AI glyph was decoration, not information).
4675
+ "w-auto max-w-[min(52vw,260px)] gap-1 rounded-full bg-muted/40 px-2.5 text-[12px] font-medium text-foreground/85 hover:bg-muted/70",
4676
+ open && "bg-muted/70 text-foreground"
4646
4677
  ),
4647
4678
  children: [
4648
- /* @__PURE__ */ jsx23(BotIcon, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
4649
4679
  /* @__PURE__ */ jsx23("span", { className: "min-w-0 truncate", children: triggerLabel }),
4650
- /* @__PURE__ */ jsx23(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/60", "aria-hidden": "true" })
4680
+ /* @__PURE__ */ jsx23(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
4651
4681
  ]
4652
4682
  }
4653
4683
  ) }),
@@ -4657,7 +4687,7 @@ function ModelSelect({
4657
4687
  align: "start",
4658
4688
  sideOffset: 6,
4659
4689
  "data-boring-agent": "",
4660
- className: "w-[min(90vw,260px)] rounded-xl border-border/60 bg-popover p-1 shadow-xl",
4690
+ className: "w-[min(92vw,340px)] rounded-xl border-border/60 bg-popover p-1 shadow-xl",
4661
4691
  children: /* @__PURE__ */ jsxs21(Command2, { children: [
4662
4692
  menuOptions.length > 8 && /* @__PURE__ */ jsx23(
4663
4693
  CommandInput2,
@@ -4686,7 +4716,13 @@ function ModelSelect({
4686
4716
  },
4687
4717
  className: cn(
4688
4718
  "flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px]",
4689
- key === currentKey && "bg-accent text-accent-foreground"
4719
+ // Soften cmdk's full-saturation `data-[selected=true]:bg-accent`
4720
+ // (keyboard-cursor focus) to a subtle warm tint so the
4721
+ // active row doesn't read as a saturated tile in dark mode.
4722
+ "data-[selected=true]:bg-[color:oklch(from_var(--accent)_l_c_h/0.15)] data-[selected=true]:text-foreground",
4723
+ // The user's CURRENTLY-SELECTED model gets a neutral wash +
4724
+ // the CheckIcon (accent is reserved for primary CTA / focus).
4725
+ key === currentKey && "bg-foreground/[0.06] text-foreground"
4690
4726
  ),
4691
4727
  children: [
4692
4728
  /* @__PURE__ */ jsx23(
@@ -4694,12 +4730,12 @@ function ModelSelect({
4694
4730
  {
4695
4731
  className: cn(
4696
4732
  "h-3.5 w-3.5 shrink-0",
4697
- key === currentKey ? "opacity-100" : "opacity-0"
4733
+ key === currentKey ? "text-[color:var(--accent)] opacity-100" : "opacity-0"
4698
4734
  )
4699
4735
  }
4700
4736
  ),
4701
4737
  /* @__PURE__ */ jsx23("span", { className: "truncate", children: label }),
4702
- /* @__PURE__ */ jsx23("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/70", children: m.id })
4738
+ /* @__PURE__ */ jsx23("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/60", children: m.id })
4703
4739
  ]
4704
4740
  },
4705
4741
  key
@@ -4733,12 +4769,17 @@ function ThinkingSelect({
4733
4769
  {
4734
4770
  "data-boring-agent-part": "thinking-select",
4735
4771
  "data-boring-state": disabled ? "disabled" : void 0,
4736
- className: cn(composerActionClass, "w-8 px-0"),
4737
- "aria-label": "Thinking level",
4772
+ className: cn(
4773
+ composerActionClass,
4774
+ "w-8 px-0 [&>span[aria-hidden]]:hidden",
4775
+ value !== "off" && "text-foreground"
4776
+ ),
4777
+ "aria-label": `Thinking level: ${THINKING_LEVEL_LABELS[value]}`,
4778
+ title: `Thinking: ${THINKING_LEVEL_LABELS[value]}`,
4738
4779
  "data-testid": "thinking-select",
4739
4780
  children: [
4740
4781
  THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx23("span", { "data-value": level, hidden: true }, level)),
4741
- /* @__PURE__ */ jsx23(BrainIcon2, { className: "h-3.5 w-3.5" })
4782
+ /* @__PURE__ */ jsx23(ThinkingLevelGlyph, { level: value })
4742
4783
  ]
4743
4784
  }
4744
4785
  ),
@@ -4772,25 +4813,26 @@ function ThoughtVisibilityButton({
4772
4813
  variant: "ghost",
4773
4814
  size: "icon-sm",
4774
4815
  onClick: onToggle,
4775
- className: cn(composerActionClass, "w-8"),
4816
+ className: cn(composerActionClass, "w-8", visible && "text-foreground"),
4776
4817
  "aria-pressed": visible,
4777
4818
  "aria-label": visible ? "Hide thoughts" : "Show thoughts",
4778
4819
  title: visible ? "Hide thoughts" : "Show thoughts",
4779
- children: /* @__PURE__ */ jsx23(Icon, { className: "h-3.5 w-3.5" })
4820
+ children: /* @__PURE__ */ jsx23(Icon, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
4780
4821
  }
4781
4822
  );
4782
4823
  }
4783
4824
 
4784
4825
  // src/front/chatPanelKbdHints.tsx
4826
+ import { Kbd } from "@hachej/boring-ui-kit";
4785
4827
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
4786
4828
  function KbdHints() {
4787
4829
  return /* @__PURE__ */ jsxs22(
4788
- "kbd",
4830
+ Kbd,
4789
4831
  {
4790
4832
  "aria-hidden": "true",
4833
+ title: "Shift + Enter for newline",
4791
4834
  className: cn(
4792
- "hidden h-5 items-center gap-0.5 rounded-[var(--radius-sm)] border border-border/50",
4793
- "bg-background/50 px-1.5 font-mono text-[10px] text-muted-foreground/40",
4835
+ "hidden gap-0.5 border-border/60 bg-muted/40 leading-none shadow-none",
4794
4836
  "sm:inline-flex"
4795
4837
  ),
4796
4838
  children: [
@@ -5750,8 +5792,8 @@ ${reasoningPart.text}`;
5750
5792
  onChange: handleComposerChange,
5751
5793
  onKeyDown: handleComposerKeyDown,
5752
5794
  className: cn(
5753
- "min-h-[48px] resize-none border-0 bg-transparent shadow-none",
5754
- "px-4 py-3 text-[13px] leading-[1.55] placeholder:text-muted-foreground/50",
5795
+ "min-h-11 resize-none border-0 bg-transparent shadow-none",
5796
+ "px-4 py-2.5 text-[13px] leading-[1.55] placeholder:text-muted-foreground/45",
5755
5797
  "focus-visible:ring-0 focus-visible:ring-offset-0"
5756
5798
  )
5757
5799
  }
@@ -5809,7 +5851,7 @@ ${reasoningPart.text}`;
5809
5851
  // to earn the real estate. Becomes a Stop affordance
5810
5852
  // (square icon + aria-label="Stop") while the turn
5811
5853
  // streams.
5812
- "h-8 w-8 shrink-0 rounded-[var(--radius-lg)]",
5854
+ "h-8 w-8 shrink-0 rounded-full",
5813
5855
  "bg-[color:var(--accent)] text-[color:var(--accent-foreground)]",
5814
5856
  "transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
5815
5857
  "hover:shadow-[0_0_0_3px_oklch(from_var(--accent)_l_c_h/0.30)] hover:brightness-110 hover:scale-[1.04]",
@@ -5863,7 +5905,7 @@ function AttachmentButton({ disabled }) {
5863
5905
  className: cn(composerActionClass, "w-8"),
5864
5906
  "aria-label": "Attach files",
5865
5907
  title: disabled ? "Attachments are available after the current response finishes." : "Attach files",
5866
- children: /* @__PURE__ */ jsx25(PaperclipIcon2, { className: "h-4 w-4" })
5908
+ children: /* @__PURE__ */ jsx25(PaperclipIcon2, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
5867
5909
  }
5868
5910
  );
5869
5911
  }
@@ -322,9 +322,6 @@
322
322
  .h-4 {
323
323
  height: calc(var(--spacing) * 4);
324
324
  }
325
- .h-5 {
326
- height: calc(var(--spacing) * 5);
327
- }
328
325
  .h-6 {
329
326
  height: calc(var(--spacing) * 6);
330
327
  }
@@ -364,12 +361,12 @@
364
361
  .min-h-6 {
365
362
  min-height: calc(var(--spacing) * 6);
366
363
  }
364
+ .min-h-11 {
365
+ min-height: calc(var(--spacing) * 11);
366
+ }
367
367
  .min-h-16 {
368
368
  min-height: calc(var(--spacing) * 16);
369
369
  }
370
- .min-h-\[48px\] {
371
- min-height: 48px;
372
- }
373
370
  .\!w-full {
374
371
  width: 100% !important;
375
372
  }
@@ -409,8 +406,8 @@
409
406
  .w-\[1\.5ch\] {
410
407
  width: 1.5ch;
411
408
  }
412
- .w-\[min\(90vw\,260px\)\] {
413
- width: min(90vw, 260px);
409
+ .w-\[min\(92vw\,340px\)\] {
410
+ width: min(92vw, 340px);
414
411
  }
415
412
  .w-auto {
416
413
  width: auto;
@@ -448,8 +445,8 @@
448
445
  .max-w-\[680px\] {
449
446
  max-width: 680px;
450
447
  }
451
- .max-w-\[min\(52vw\,200px\)\] {
452
- max-width: min(52vw, 200px);
448
+ .max-w-\[min\(52vw\,260px\)\] {
449
+ max-width: min(52vw, 260px);
453
450
  }
454
451
  .max-w-full {
455
452
  max-width: 100%;
@@ -951,12 +948,6 @@
951
948
  .bg-background {
952
949
  background-color: var(--background);
953
950
  }
954
- .bg-background\/50 {
955
- background-color: var(--background);
956
- @supports (color: color-mix(in lab, red, red)) {
957
- background-color: color-mix(in oklab, var(--background) 50%, transparent);
958
- }
959
- }
960
951
  .bg-background\/60 {
961
952
  background-color: var(--background);
962
953
  @supports (color: color-mix(in lab, red, red)) {
@@ -1032,6 +1023,12 @@
1032
1023
  background-color: color-mix(in oklab, var(--color-emerald-500) 70%, transparent);
1033
1024
  }
1034
1025
  }
1026
+ .bg-foreground\/\[0\.06\] {
1027
+ background-color: var(--foreground);
1028
+ @supports (color: color-mix(in lab, red, red)) {
1029
+ background-color: color-mix(in oklab, var(--foreground) 6%, transparent);
1030
+ }
1031
+ }
1035
1032
  .bg-muted {
1036
1033
  background-color: var(--muted);
1037
1034
  }
@@ -1053,6 +1050,12 @@
1053
1050
  background-color: color-mix(in oklab, var(--muted) 30%, transparent);
1054
1051
  }
1055
1052
  }
1053
+ .bg-muted\/40 {
1054
+ background-color: var(--muted);
1055
+ @supports (color: color-mix(in lab, red, red)) {
1056
+ background-color: color-mix(in oklab, var(--muted) 40%, transparent);
1057
+ }
1058
+ }
1056
1059
  .bg-muted\/50 {
1057
1060
  background-color: var(--muted);
1058
1061
  @supports (color: color-mix(in lab, red, red)) {
@@ -1065,6 +1068,12 @@
1065
1068
  background-color: color-mix(in oklab, var(--muted) 60%, transparent);
1066
1069
  }
1067
1070
  }
1071
+ .bg-muted\/70 {
1072
+ background-color: var(--muted);
1073
+ @supports (color: color-mix(in lab, red, red)) {
1074
+ background-color: color-mix(in oklab, var(--muted) 70%, transparent);
1075
+ }
1076
+ }
1068
1077
  .bg-muted\/80 {
1069
1078
  background-color: var(--muted);
1070
1079
  @supports (color: color-mix(in lab, red, red)) {
@@ -1330,6 +1339,10 @@
1330
1339
  --tw-leading: 1.55;
1331
1340
  line-height: 1.55;
1332
1341
  }
1342
+ .leading-none {
1343
+ --tw-leading: 1;
1344
+ line-height: 1;
1345
+ }
1333
1346
  .leading-normal {
1334
1347
  --tw-leading: var(--leading-normal);
1335
1348
  line-height: var(--leading-normal);
@@ -1436,9 +1449,6 @@
1436
1449
  .text-accent {
1437
1450
  color: var(--accent);
1438
1451
  }
1439
- .text-accent-foreground {
1440
- color: var(--accent-foreground);
1441
- }
1442
1452
  .text-accent\/80 {
1443
1453
  color: var(--accent);
1444
1454
  @supports (color: color-mix(in lab, red, red)) {
@@ -1824,11 +1834,11 @@
1824
1834
  color: var(--foreground);
1825
1835
  }
1826
1836
  }
1827
- .placeholder\:text-muted-foreground\/50 {
1837
+ .placeholder\:text-muted-foreground\/45 {
1828
1838
  &::placeholder {
1829
1839
  color: var(--muted-foreground);
1830
1840
  @supports (color: color-mix(in lab, red, red)) {
1831
- color: color-mix(in oklab, var(--muted-foreground) 50%, transparent);
1841
+ color: color-mix(in oklab, var(--muted-foreground) 45%, transparent);
1832
1842
  }
1833
1843
  }
1834
1844
  }
@@ -2053,6 +2063,16 @@
2053
2063
  }
2054
2064
  }
2055
2065
  }
2066
+ .hover\:bg-muted\/70 {
2067
+ &:hover {
2068
+ @media (hover: hover) {
2069
+ background-color: var(--muted);
2070
+ @supports (color: color-mix(in lab, red, red)) {
2071
+ background-color: color-mix(in oklab, var(--muted) 70%, transparent);
2072
+ }
2073
+ }
2074
+ }
2075
+ }
2056
2076
  .hover\:bg-primary\/10 {
2057
2077
  &:hover {
2058
2078
  @media (hover: hover) {
@@ -2255,6 +2275,21 @@
2255
2275
  opacity: 50%;
2256
2276
  }
2257
2277
  }
2278
+ .data-\[selected\=true\]\:bg-\[color\:oklch\(from_var\(--accent\)_l_c_h\/0\.15\)\] {
2279
+ &[data-selected="true"] {
2280
+ background-color: oklch(from var(--accent) l c h/0.15);
2281
+ }
2282
+ }
2283
+ .data-\[selected\=true\]\:bg-accent {
2284
+ &[data-selected="true"] {
2285
+ background-color: var(--accent);
2286
+ }
2287
+ }
2288
+ .data-\[selected\=true\]\:text-foreground {
2289
+ &[data-selected="true"] {
2290
+ color: var(--foreground);
2291
+ }
2292
+ }
2258
2293
  .data-\[state\=active\]\:after\:bg-\[color\:var\(--accent\)\] {
2259
2294
  &[data-state="active"] {
2260
2295
  &::after {
@@ -2493,6 +2528,11 @@
2493
2528
  background-color: transparent;
2494
2529
  }
2495
2530
  }
2531
+ .\[\&\>span\[aria-hidden\]\]\:hidden {
2532
+ &>span[aria-hidden] {
2533
+ display: none;
2534
+ }
2535
+ }
2496
2536
  .\[\&\>svg\]\:size-2\.5 {
2497
2537
  &>svg {
2498
2538
  width: calc(var(--spacing) * 2.5);
@@ -29,6 +29,18 @@ interface SessionDetail extends SessionSummary {
29
29
  messages: UIMessage[];
30
30
  }
31
31
 
32
+ interface TelemetrySink {
33
+ capture(event: TelemetryEvent): void | Promise<void>;
34
+ flush?(): void | Promise<void>;
35
+ }
36
+ interface TelemetryEvent {
37
+ name: string;
38
+ distinctId?: string;
39
+ properties?: Record<string, unknown>;
40
+ }
41
+ declare const noopTelemetry: TelemetrySink;
42
+ declare function safeCapture(telemetry: TelemetrySink, event: TelemetryEvent): void;
43
+
32
44
  type JSONSchema = Record<string, unknown>;
33
45
  interface AgentTool {
34
46
  name: string;
@@ -67,6 +79,8 @@ interface AgentHarnessFactoryInput {
67
79
  * prompt context without a workspace-injected harness extension.
68
80
  */
69
81
  systemPromptDynamic?: () => string | undefined | Promise<string | undefined>;
82
+ /** Host-provided telemetry sink. Optional and best-effort; harnesses may ignore it. */
83
+ telemetry?: TelemetrySink;
70
84
  }
71
85
  type AgentHarnessFactory = (input: AgentHarnessFactoryInput) => AgentHarness | Promise<AgentHarness>;
72
86
  interface AgentHarness {
@@ -127,4 +141,4 @@ interface RunContext {
127
141
  userId?: string;
128
142
  }
129
143
 
130
- export type { AgentTool as A, JSONSchema as J, RunContext as R, SendMessageInput as S, ToolExecContext as T, AgentHarness as a, SessionCtx as b, SessionDetail as c, SessionStore as d, SessionSummary as e, ToolResult as f, AgentHarnessFactory as g, AgentHarnessFactoryInput as h };
144
+ export { type AgentTool as A, type JSONSchema as J, type RunContext as R, type SendMessageInput as S, type TelemetryEvent as T, type AgentHarness as a, type SessionCtx as b, type SessionDetail as c, type SessionStore as d, type SessionSummary as e, type TelemetrySink as f, type ToolExecContext as g, type ToolResult as h, type AgentHarnessFactory as i, type AgentHarnessFactoryInput as j, noopTelemetry as n, safeCapture as s };