kibi-agent-core 0.11.0 → 0.11.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # kibi-agent-core
2
2
 
3
+ ## 0.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - The telemetry status carries the channel in effect and where it comes from (`channel`, `channelSource`), and `telemetryChannel` returns both.
8
+
9
+ - Edit snippets say what the lead requirement must keep true as the claims its facts model, in words with their operator and members (for example "REQ-GREET must keep true: The greet function must not call console.log."), or as the requirement's own statement, instead of listing internal fact IDs and subject keys. A requirement title that an older Kibi cut at a dot inside a name is shown whole.
10
+
11
+ - f9ec786: Usage telemetry events now carry a `channel`: `release` for an installed Kibi package, `source` when Kibi runs from a checkout of its own repository, or the value of `KIBI_TELEMETRY_CHANNEL` when a harness sets one (for example `agent-test`). Development and test runs can then be filtered out of real usage. Nothing else about what is sent changes.
12
+
3
13
  ## 0.11.0
4
14
 
5
15
  ### Minor Changes
@@ -82,6 +82,21 @@ export type EntitySummary = {
82
82
  * with canonical relationship-shard records whose `from` is this entity.
83
83
  */
84
84
  links?: EntityLink[];
85
+ /** Requirement prose: `semantic_text`, else the body's first paragraph. */
86
+ statement?: string;
87
+ /** The claim a fact models, from its typed front matter fields. */
88
+ claim?: FactClaim;
89
+ };
90
+ /** A fact's typed fields, as authored; absent fields are left out. */
91
+ export type FactClaim = {
92
+ factKind?: string;
93
+ subjectKey?: string;
94
+ propertyKey?: string;
95
+ operator?: string;
96
+ /** `value_set` members, or the one scalar value. */
97
+ values?: string[];
98
+ unit?: string;
99
+ claimText?: string;
85
100
  };
86
101
  /**
87
102
  * Read an entity's title, status and outgoing links. Title and status come
@@ -636,6 +636,62 @@ const ENTITY_LANES = [
636
636
  ["EVT-", "events"],
637
637
  ];
638
638
  const SAFE_ENTITY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
639
+ /** Front matter scalars a summary keeps, by field name. */
640
+ const SUMMARY_SCALARS = {
641
+ semantic_text: "statement",
642
+ fact_kind: "factKind",
643
+ subject_key: "subjectKey",
644
+ property_key: "propertyKey",
645
+ operator: "operator",
646
+ unit: "unit",
647
+ claim_text: "claimText",
648
+ };
649
+ const SCALAR_VALUE_FIELDS = new Set([
650
+ "value_string",
651
+ "value_int",
652
+ "value_number",
653
+ "value_bool",
654
+ ]);
655
+ /**
656
+ * A front matter scalar starting at `lines[index]` with `raw` after its key:
657
+ * a plain or quoted value with its indented continuation lines, or a block
658
+ * scalar (`>-`, `|`). Returns the value and the last line it used.
659
+ */
660
+ function readScalar(lines, index, raw) {
661
+ const block = /^[>|][+-]?$/.test(raw.trim());
662
+ const parts = block ? [] : [raw.trim()];
663
+ let last = index;
664
+ while (last + 1 < lines.length) {
665
+ const next = lines[last + 1] ?? "";
666
+ if (next.trim() === "---" || !/^\s+\S/.test(next))
667
+ break;
668
+ if (!block && /^\s+-\s/.test(next))
669
+ break;
670
+ parts.push(next.trim());
671
+ last += 1;
672
+ }
673
+ const joined = parts.join(" ").trim();
674
+ return { value: block ? joined : unquote(joined), last };
675
+ }
676
+ /** The first prose paragraph of a Markdown body, headings skipped. */
677
+ function firstParagraph(lines) {
678
+ const paragraph = [];
679
+ for (const line of lines) {
680
+ const text = line.trim();
681
+ if (text === "") {
682
+ if (paragraph.length > 0)
683
+ break;
684
+ continue;
685
+ }
686
+ if (/^#{1,6}\s/.test(text)) {
687
+ if (paragraph.length > 0)
688
+ break;
689
+ continue;
690
+ }
691
+ paragraph.push(text);
692
+ }
693
+ return paragraph.length > 0 ? paragraph.join(" ") : undefined;
694
+ }
639
695
  const MAX_SUMMARY_LINKS = 24;
640
696
  const RELATIONSHIPS_DIR = ".kb/relationships";
641
697
  /**
@@ -713,16 +769,53 @@ export function readEntitySummary(workspaceRoot, entityId) {
713
769
  return { id: entityId };
714
770
  const summary = { id: entityId };
715
771
  const authored = [];
772
+ const claim = {};
773
+ const values = [];
716
774
  let inLinks = false;
775
+ let inValueSet = false;
717
776
  let inOrigin = false;
718
777
  let originKind;
719
778
  let closed = false;
779
+ let bodyStart = lines.length;
720
780
  let pending;
721
- for (const line of lines.slice(1)) {
781
+ for (let index = 1; index < lines.length; index += 1) {
782
+ const line = lines[index] ?? "";
722
783
  if (line.trim() === "---") {
723
784
  closed = true;
785
+ bodyStart = index + 1;
724
786
  break;
725
787
  }
788
+ const scalar = /^([a-z_]+):\s*(.*)$/.exec(line);
789
+ const scalarField = scalar ? SUMMARY_SCALARS[scalar[1] ?? ""] : undefined;
790
+ if (scalar && scalarField && scalar[2]) {
791
+ const { value, last } = readScalar(lines, index, scalar[2]);
792
+ if (value !== "") {
793
+ if (scalarField === "statement")
794
+ summary.statement = value;
795
+ else if (scalarField !== "values")
796
+ claim[scalarField] = value;
797
+ }
798
+ index = last;
799
+ flushLink(authored, pending);
800
+ pending = undefined;
801
+ inLinks = false;
802
+ inValueSet = false;
803
+ continue;
804
+ }
805
+ if (scalar && SCALAR_VALUE_FIELDS.has(scalar[1] ?? "") && scalar[2]) {
806
+ values.push(unquote(scalar[2]));
807
+ }
808
+ const flowSet = /^value_set:\s*\[(.*)\]\s*$/.exec(line);
809
+ if (flowSet?.[1]?.trim()) {
810
+ values.push(...flowSet[1].split(",").map((member) => unquote(member)));
811
+ }
812
+ if (/^\S/.test(line))
813
+ inValueSet = /^value_set:\s*$/.test(line);
814
+ else if (inValueSet) {
815
+ const member = /^\s*-\s*(.+)$/.exec(line);
816
+ if (member)
817
+ values.push(unquote(member[1] ?? ""));
818
+ }
726
819
  // implements REQ-kb-decision-firmness-from-origin
727
820
  const flowOrigin = /^origin:\s*\{.*\bkind:\s*["']?([a-z]+)/.exec(line);
728
821
  if (flowOrigin)
@@ -791,5 +884,14 @@ export function readEntitySummary(workspaceRoot, entityId) {
791
884
  }
792
885
  if (links.length > 0)
793
886
  summary.links = links;
887
+ if (values.length > 0)
888
+ claim.values = values;
889
+ if (Object.keys(claim).length > 0)
890
+ summary.claim = claim;
891
+ if (summary.statement === undefined && lane[1] === "requirements" && closed) {
892
+ const paragraph = firstParagraph(lines.slice(bodyStart));
893
+ if (paragraph)
894
+ summary.statement = paragraph;
895
+ }
794
896
  return summary;
795
897
  }
@@ -44,10 +44,20 @@ export declare function unreadableManifestNote(relativePath: string): string;
44
44
  /** Link types through which a requirement is grounded in semantic facts. */
45
45
  export declare const GROUNDING_LINK_TYPES: readonly string[];
46
46
  /**
47
- * What a requirement says must stay true (its linked facts) and the decision
48
- * behind it (a linked ADR), read from its frontmatter links and
49
- * relationship-shard records. A retired requirement is not current policy, so
50
- * its grounding is not presented as something to keep true.
47
+ * A property fact as a sentence naming its operator and members ("The greet
48
+ * function must not call console.log"); a predicate or rule fact as the
49
+ * claim text it models. Undefined for a subject fact and for a fact without
50
+ * readable fields.
51
+ */
52
+ export declare function factClaimSentence(summary: EntitySummary): string | undefined;
53
+ /**
54
+ * What a requirement says must stay true and the decision behind it (a
55
+ * linked ADR), read from its frontmatter links and relationship-shard
56
+ * records. Each linked fact is shown as the claim it models, with its
57
+ * operator and members (a fact without typed fields by its title); without
58
+ * one the requirement's own statement is shown. Fact IDs are internal and
59
+ * never shown. A retired requirement is not current policy, so its grounding
60
+ * is not presented as something to keep true.
51
61
  */
52
62
  export declare function requirementGroundingLines(requirementId: string, summarize: Summarize, options?: {
53
63
  maxFacts?: number;
package/dist/snippets.js CHANGED
@@ -18,6 +18,7 @@ const MAX_SYMBOLS_PER_REQUIREMENT = 3;
18
18
  const MAX_TESTS = 3;
19
19
  const MAX_TITLE = 80;
20
20
  const MAX_GROUNDING = 2;
21
+ const MAX_CLAIM = 160;
21
22
  /** Files larger than this are not scanned to locate an edit. */
22
23
  const MAX_FOCUS_SCAN_BYTES = 2 * 1024 * 1024;
23
24
  // implements REQ-claude-code-kibi-plugin-v1, REQ-cursor-kibi-plugin-v1
@@ -118,10 +119,25 @@ export function describeEntity(summary) {
118
119
  : " [exploratory]"
119
120
  : "";
120
121
  const notable = `${retired}${firmness}`;
121
- return summary.title
122
- ? `${summary.id}${notable}: ${truncate(summary.title, MAX_TITLE)}`
122
+ const title = displayTitle(summary);
123
+ return title
124
+ ? `${summary.id}${notable}: ${truncate(title, MAX_TITLE)}`
123
125
  : `${summary.id}${notable}`;
124
126
  }
127
+ /**
128
+ * The title, completed from the statement when an older title was cut at a
129
+ * dot inside a name ("...call console" for "...call console.log").
130
+ */
131
+ function displayTitle(summary) {
132
+ const { title, statement } = summary;
133
+ if (!title || !statement?.startsWith(title))
134
+ return title;
135
+ const rest = statement.slice(title.length);
136
+ if (!/^\.\S/.test(rest))
137
+ return title;
138
+ const end = rest.search(/[.!?](?:\s|$)/);
139
+ return end < 0 ? statement : statement.slice(0, title.length + end);
140
+ }
125
141
  function jsonString(value) {
126
142
  return JSON.stringify(value);
127
143
  }
@@ -178,12 +194,94 @@ export const GROUNDING_LINK_TYPES = [
178
194
  "requires_predicate",
179
195
  "requires_rule",
180
196
  ];
197
+ /** Set-valued properties whose claim reads as a verb on the members. */
198
+ const PROPERTY_VERBS = {
199
+ calls: "call",
200
+ imports: "import",
201
+ reads: "read",
202
+ uses: "use",
203
+ throws: "throw",
204
+ };
205
+ /** How a scalar or set property is constrained, by fact operator. */
206
+ const OPERATOR_PHRASES = {
207
+ eq: "must be",
208
+ neq: "must not be",
209
+ lt: "must be less than",
210
+ lte: "must be at most",
211
+ gt: "must be greater than",
212
+ gte: "must be at least",
213
+ matches: "must match",
214
+ includes: "must include",
215
+ subset_of: "must be within",
216
+ excludes: "must not include",
217
+ };
218
+ function joinMembers(values, conjunction) {
219
+ if (values.length <= 1)
220
+ return values[0] ?? "";
221
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.at(-1)}`;
222
+ }
223
+ const MODAL = /^(.{1,100}?)\s+(?:must|may|can|cannot|should|shall|will|never)\b/i;
224
+ /** The claim's own subject words, before its modal verb, else the key's. */
225
+ function subjectPhrase(claim) {
226
+ const spoken = MODAL.exec(claim.claimText ?? "")?.[1];
227
+ if (spoken)
228
+ return spoken;
229
+ return claim.subjectKey?.replace(/[._]+/g, " ").trim() || undefined;
230
+ }
231
+ // implements REQ-agent-core-edit-snippets
232
+ /**
233
+ * A property fact as a sentence naming its operator and members ("The greet
234
+ * function must not call console.log"); a predicate or rule fact as the
235
+ * claim text it models. Undefined for a subject fact and for a fact without
236
+ * readable fields.
237
+ */
238
+ export function factClaimSentence(summary) {
239
+ const claim = summary.claim;
240
+ if (!claim || claim.factKind === "subject")
241
+ return undefined;
242
+ const values = claim.values ?? [];
243
+ if (claim.propertyKey && claim.operator && values.length > 0) {
244
+ const subject = subjectPhrase(claim);
245
+ if (!subject)
246
+ return claim.claimText;
247
+ const verb = PROPERTY_VERBS[claim.propertyKey];
248
+ const verbal = verb
249
+ ? {
250
+ excludes: `must not ${verb} ${joinMembers(values, "or")}`,
251
+ includes: `must ${verb} ${joinMembers(values, "and")}`,
252
+ subset_of: `may ${verb} only ${joinMembers(values, "or")}`,
253
+ eq: `must ${verb} exactly ${joinMembers(values, "and")}`,
254
+ }[claim.operator]
255
+ : undefined;
256
+ if (verbal)
257
+ return `${subject} ${verbal}`;
258
+ const phrase = OPERATOR_PHRASES[claim.operator];
259
+ if (phrase) {
260
+ const property = claim.propertyKey.replace(/_+/g, " ");
261
+ const unit = claim.unit ? ` ${claim.unit}` : "";
262
+ return `${subject}: ${property} ${phrase} ${values.join(", ")}${unit}`;
263
+ }
264
+ }
265
+ return claim.claimText;
266
+ }
267
+ /** A fact's claim sentence, else its authored title; a subject fact adds nothing. */
268
+ function groundingText(summary) {
269
+ if (summary.claim?.factKind === "subject")
270
+ return undefined;
271
+ return factClaimSentence(summary) ?? summary.title;
272
+ }
273
+ function withoutClosingStop(text) {
274
+ return text.replace(/[.\s]+$/, "");
275
+ }
181
276
  // implements REQ-agent-core-edit-snippets, REQ-claude-code-kibi-plugin-v1, REQ-opencode-kibi-plugin-v1
182
277
  /**
183
- * What a requirement says must stay true (its linked facts) and the decision
184
- * behind it (a linked ADR), read from its frontmatter links and
185
- * relationship-shard records. A retired requirement is not current policy, so
186
- * its grounding is not presented as something to keep true.
278
+ * What a requirement says must stay true and the decision behind it (a
279
+ * linked ADR), read from its frontmatter links and relationship-shard
280
+ * records. Each linked fact is shown as the claim it models, with its
281
+ * operator and members (a fact without typed fields by its title); without
282
+ * one the requirement's own statement is shown. Fact IDs are internal and
283
+ * never shown. A retired requirement is not current policy, so its grounding
284
+ * is not presented as something to keep true.
187
285
  */
188
286
  export function requirementGroundingLines(requirementId, summarize, options = {}) {
189
287
  const maxFacts = options.maxFacts ?? MAX_GROUNDING;
@@ -203,11 +301,24 @@ export function requirementGroundingLines(requirementId, summarize, options = {}
203
301
  ];
204
302
  const lines = [];
205
303
  if (facts.length > 0) {
206
- const shown = facts
207
- .slice(0, maxFacts)
208
- .map((id) => describeEntity(summarize(id)));
209
- const more = facts.length > maxFacts ? ` +${facts.length - maxFacts}` : "";
210
- lines.push(`${requirementId} must keep true: ${shown.join("; ")}${more}.`);
304
+ const claims = [
305
+ ...new Set(facts
306
+ .map((id) => groundingText(summarize(id)))
307
+ .filter((sentence) => Boolean(sentence))
308
+ .map(withoutClosingStop)),
309
+ ];
310
+ const statement = requirement.statement
311
+ ? withoutClosingStop(requirement.statement)
312
+ : undefined;
313
+ const shown = claims.length > 0
314
+ ? claims.slice(0, maxFacts)
315
+ : statement
316
+ ? [statement]
317
+ : [];
318
+ const more = claims.length > maxFacts ? ` +${claims.length - maxFacts} more` : "";
319
+ if (shown.length > 0) {
320
+ lines.push(`${requirementId} must keep true: ${shown.map((claim) => truncate(claim, MAX_CLAIM)).join("; ")}${more}.`);
321
+ }
211
322
  }
212
323
  const adr = adrs[0];
213
324
  if (adr)
@@ -15,7 +15,10 @@
15
15
  * short-lived processes (hooks, the CLI) do not wait on the network.
16
16
  * `KIBI_TELEMETRY=0`, `DO_NOT_TRACK=1`, `kibi telemetry disable`, CI and test
17
17
  * runs, and sandboxes whose home directory is a temp directory (eval and
18
- * packaging harnesses) turn it off.
18
+ * packaging harnesses) turn it off. Runs of Kibi from a checkout of its own
19
+ * repository, and runs a harness marks with `KIBI_TELEMETRY_CHANNEL`, are
20
+ * still sent but carry a `channel` other than `release`, so they can be
21
+ * filtered out of real usage.
19
22
  */
20
23
  /** Public, write-only PostHog project key: it can send events, not read them. */
21
24
  export declare const DEFAULT_TELEMETRY_KEY = "phc_A5z8jRhD528abGJiTtPfgu4d4hpbEbfiJr9iUGn9TMsY";
@@ -33,7 +36,16 @@ export type TelemetryStatus = {
33
36
  /** Why telemetry is on or off, e.g. `KIBI_TELEMETRY=0` or `default`. */
34
37
  reason: string;
35
38
  directory: string;
39
+ /** The channel events carry, e.g. `release`, `source` or `agent-test`. */
40
+ channel: string;
41
+ /** Where the channel comes from. */
42
+ channelSource: TelemetryChannelSource;
36
43
  };
44
+ /**
45
+ * `KIBI_TELEMETRY_CHANNEL` (a harness set it), `checkout` (Kibi runs from a
46
+ * checkout of its own repository) or `installed` (an installed package).
47
+ */
48
+ export type TelemetryChannelSource = "KIBI_TELEMETRY_CHANNEL" | "checkout" | "installed";
37
49
  /** Summary of one Kibi operation run through MCP or the CLI. */
38
50
  export type OperationTelemetry = {
39
51
  interface: "mcp" | "cli";
@@ -85,6 +97,18 @@ export declare const SESSION_ENV_KEYS: readonly ["KIBI_SESSION_ID", "CLAUDE_CODE
85
97
  export declare function detectTelemetryHost(env?: Env): string | undefined;
86
98
  /** The host agent's session id from the environment, when it exposes one. */
87
99
  export declare function detectTelemetrySession(env?: Env): string | undefined;
100
+ /**
101
+ * Where a run comes from: the `KIBI_TELEMETRY_CHANNEL` a harness sets (for
102
+ * example `agent-test`), `source` when Kibi runs from a checkout of its own
103
+ * repository (local development, the repository's own hooks, e2e and agent
104
+ * test rounds built from source), and `release` for an installed package.
105
+ */
106
+ export declare function detectTelemetryChannel(env?: Env, moduleFile?: string | null): string;
107
+ /** The channel in effect and where it comes from. */
108
+ export declare function telemetryChannel(env?: Env, moduleFile?: string | null): {
109
+ channel: string;
110
+ source: TelemetryChannelSource;
111
+ };
88
112
  /** Per-user directory holding the telemetry config and queue. */
89
113
  export declare function telemetryDirectory(env?: Env): string;
90
114
  export declare function readTelemetryConfig(env?: Env): TelemetryConfig;
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
6
7
  /**
7
8
  * Anonymous usage telemetry shared by the CLI, the MCP server and the host
8
9
  * hooks.
@@ -20,7 +21,10 @@ import path from "node:path";
20
21
  * short-lived processes (hooks, the CLI) do not wait on the network.
21
22
  * `KIBI_TELEMETRY=0`, `DO_NOT_TRACK=1`, `kibi telemetry disable`, CI and test
22
23
  * runs, and sandboxes whose home directory is a temp directory (eval and
23
- * packaging harnesses) turn it off.
24
+ * packaging harnesses) turn it off. Runs of Kibi from a checkout of its own
25
+ * repository, and runs a harness marks with `KIBI_TELEMETRY_CHANNEL`, are
26
+ * still sent but carry a `channel` other than `release`, so they can be
27
+ * filtered out of real usage.
24
28
  */
25
29
  // implements REQ-kibi-anonymous-usage-telemetry
26
30
  /** Public, write-only PostHog project key: it can send events, not read them. */
@@ -183,6 +187,79 @@ function sandboxedHome(env) {
183
187
  const resolved = realDirectory(home);
184
188
  return resolved !== temp && resolved.startsWith(`${temp}${path.sep}`);
185
189
  }
190
+ /** The Kibi monorepo root's package name. */
191
+ const KIBI_REPOSITORY_PACKAGE = "kibi";
192
+ /** This module's file, or undefined when a bundle format drops `import.meta.url`. */
193
+ function thisModuleFile() {
194
+ try {
195
+ const url = import.meta.url;
196
+ return typeof url === "string" && url !== ""
197
+ ? fileURLToPath(url)
198
+ : undefined;
199
+ }
200
+ catch {
201
+ return undefined;
202
+ }
203
+ }
204
+ /**
205
+ * Whether a file belongs to a checkout of the Kibi repository rather than to
206
+ * an installed package. Installed copies live under `node_modules` or in a
207
+ * host's plugin cache; a checkout has the monorepo root `package.json` (named
208
+ * `kibi`, with a `packages/agent-core` workspace) above the file.
209
+ */
210
+ function insideKibiCheckout(file) {
211
+ const resolved = realDirectory(file);
212
+ if (resolved.split(/[\\/]/).includes("node_modules"))
213
+ return false;
214
+ let current = path.dirname(resolved);
215
+ for (;;) {
216
+ try {
217
+ const manifest = JSON.parse(fs.readFileSync(path.join(current, "package.json"), "utf8"));
218
+ if (manifest.name === KIBI_REPOSITORY_PACKAGE &&
219
+ fs.existsSync(path.join(current, "packages", "agent-core"))) {
220
+ return true;
221
+ }
222
+ }
223
+ catch {
224
+ // No readable manifest here; keep walking up.
225
+ }
226
+ const parent = path.dirname(current);
227
+ if (parent === current)
228
+ return false;
229
+ current = parent;
230
+ }
231
+ }
232
+ const checkoutCache = new Map();
233
+ // implements REQ-kibi-anonymous-usage-telemetry
234
+ /**
235
+ * Where a run comes from: the `KIBI_TELEMETRY_CHANNEL` a harness sets (for
236
+ * example `agent-test`), `source` when Kibi runs from a checkout of its own
237
+ * repository (local development, the repository's own hooks, e2e and agent
238
+ * test rounds built from source), and `release` for an installed package.
239
+ */
240
+ export function detectTelemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? null) {
241
+ return telemetryChannel(env, moduleFile).channel;
242
+ }
243
+ // implements REQ-kibi-anonymous-usage-telemetry
244
+ /** The channel in effect and where it comes from. */
245
+ export function telemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? null) {
246
+ const explicit = env.KIBI_TELEMETRY_CHANNEL?.trim();
247
+ if (explicit)
248
+ return {
249
+ channel: telemetryCategory(explicit) ?? "release",
250
+ source: "KIBI_TELEMETRY_CHANNEL",
251
+ };
252
+ if (!moduleFile)
253
+ return { channel: "release", source: "installed" };
254
+ let fromCheckout = checkoutCache.get(moduleFile);
255
+ if (fromCheckout === undefined) {
256
+ fromCheckout = insideKibiCheckout(moduleFile);
257
+ checkoutCache.set(moduleFile, fromCheckout);
258
+ }
259
+ return fromCheckout
260
+ ? { channel: "source", source: "checkout" }
261
+ : { channel: "release", source: "installed" };
262
+ }
186
263
  // implements REQ-kibi-anonymous-usage-telemetry
187
264
  /** Per-user directory holding the telemetry config and queue. */
188
265
  export function telemetryDirectory(env = process.env) {
@@ -230,10 +307,13 @@ export function setTelemetryPreference(preference, env = process.env) {
230
307
  */
231
308
  export function telemetryStatus(env = process.env) {
232
309
  const directory = telemetryDirectory(env);
310
+ const { channel, source } = telemetryChannel(env);
233
311
  const status = (enabled, reason) => ({
234
312
  enabled,
235
313
  reason,
236
314
  directory,
315
+ channel,
316
+ channelSource: source,
237
317
  });
238
318
  const explicit = flagValue(env.KIBI_TELEMETRY);
239
319
  if (explicit === false)
@@ -425,6 +505,7 @@ function baseProperties(source, env) {
425
505
  detectTelemetryHost(env) ||
426
506
  "unknown",
427
507
  remote: remoteEnvironment(env),
508
+ channel: detectTelemetryChannel(env),
428
509
  session: sessionProperty(source, env),
429
510
  os: process.platform,
430
511
  arch: process.arch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-agent-core",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Shared low-latency hook helpers for Kibi agent adapters",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",