@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.2

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 (119) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +20 -0
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
  3. package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
  4. package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
  5. package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
  6. package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
  7. package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
  8. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
  9. package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
  10. package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
  11. package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
  12. package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
  13. package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
  14. package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
  15. package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
  16. package/README.md +224 -6
  17. package/dist/heart/agent-entry.js +17 -0
  18. package/dist/heart/api-error.js +34 -0
  19. package/dist/heart/config.js +296 -0
  20. package/dist/heart/core.js +515 -0
  21. package/dist/heart/daemon/daemon-cli.js +675 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +313 -0
  24. package/dist/heart/daemon/hatch-flow.js +285 -0
  25. package/dist/heart/daemon/hatch-specialist.js +107 -0
  26. package/dist/heart/daemon/health-monitor.js +79 -0
  27. package/dist/heart/daemon/log-tailer.js +146 -0
  28. package/dist/heart/daemon/message-router.js +98 -0
  29. package/dist/heart/daemon/os-cron.js +260 -0
  30. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  32. package/dist/heart/daemon/ouro-entry.js +23 -0
  33. package/dist/heart/daemon/ouro-uti.js +212 -0
  34. package/dist/heart/daemon/process-manager.js +237 -0
  35. package/dist/heart/daemon/runtime-logging.js +98 -0
  36. package/dist/heart/daemon/subagent-installer.js +125 -0
  37. package/dist/heart/daemon/task-scheduler.js +240 -0
  38. package/dist/heart/harness.js +26 -0
  39. package/dist/heart/identity.js +281 -0
  40. package/dist/heart/kicks.js +144 -0
  41. package/dist/heart/primitives.js +4 -0
  42. package/dist/heart/providers/anthropic.js +329 -0
  43. package/dist/heart/providers/azure.js +66 -0
  44. package/dist/heart/providers/minimax.js +53 -0
  45. package/dist/heart/providers/openai-codex.js +162 -0
  46. package/dist/heart/streaming.js +412 -0
  47. package/dist/heart/turn-coordinator.js +62 -0
  48. package/dist/inner-worker-entry.js +4 -0
  49. package/dist/mind/associative-recall.js +197 -0
  50. package/dist/mind/bundle-manifest.js +118 -0
  51. package/dist/mind/context.js +302 -0
  52. package/dist/mind/first-impressions.js +43 -0
  53. package/dist/mind/format.js +56 -0
  54. package/dist/mind/friends/channel.js +41 -0
  55. package/dist/mind/friends/resolver.js +84 -0
  56. package/dist/mind/friends/store-file.js +171 -0
  57. package/dist/mind/friends/store.js +4 -0
  58. package/dist/mind/friends/tokens.js +26 -0
  59. package/dist/mind/friends/types.js +21 -0
  60. package/dist/mind/memory.js +388 -0
  61. package/dist/mind/pending.js +93 -0
  62. package/dist/mind/phrases.js +43 -0
  63. package/dist/mind/prompt-refresh.js +20 -0
  64. package/dist/mind/prompt.js +352 -0
  65. package/dist/mind/token-estimate.js +119 -0
  66. package/dist/nerves/cli-logging.js +31 -0
  67. package/dist/nerves/coverage/audit-rules.js +81 -0
  68. package/dist/nerves/coverage/audit.js +200 -0
  69. package/dist/nerves/coverage/cli-main.js +5 -0
  70. package/dist/nerves/coverage/cli.js +51 -0
  71. package/dist/nerves/coverage/contract.js +23 -0
  72. package/dist/nerves/coverage/file-completeness.js +56 -0
  73. package/dist/nerves/coverage/run-artifacts.js +77 -0
  74. package/dist/nerves/coverage/source-scanner.js +34 -0
  75. package/dist/nerves/index.js +152 -0
  76. package/dist/nerves/runtime.js +38 -0
  77. package/dist/repertoire/ado-client.js +211 -0
  78. package/dist/repertoire/ado-context.js +73 -0
  79. package/dist/repertoire/ado-semantic.js +841 -0
  80. package/dist/repertoire/ado-templates.js +146 -0
  81. package/dist/repertoire/coding/index.js +36 -0
  82. package/dist/repertoire/coding/manager.js +489 -0
  83. package/dist/repertoire/coding/monitor.js +60 -0
  84. package/dist/repertoire/coding/reporter.js +45 -0
  85. package/dist/repertoire/coding/spawner.js +102 -0
  86. package/dist/repertoire/coding/tools.js +167 -0
  87. package/dist/repertoire/coding/types.js +2 -0
  88. package/dist/repertoire/data/ado-endpoints.json +122 -0
  89. package/dist/repertoire/data/graph-endpoints.json +212 -0
  90. package/dist/repertoire/github-client.js +64 -0
  91. package/dist/repertoire/graph-client.js +118 -0
  92. package/dist/repertoire/skills.js +156 -0
  93. package/dist/repertoire/tasks/board.js +122 -0
  94. package/dist/repertoire/tasks/index.js +210 -0
  95. package/dist/repertoire/tasks/lifecycle.js +80 -0
  96. package/dist/repertoire/tasks/middleware.js +65 -0
  97. package/dist/repertoire/tasks/parser.js +173 -0
  98. package/dist/repertoire/tasks/scanner.js +132 -0
  99. package/dist/repertoire/tasks/transitions.js +145 -0
  100. package/dist/repertoire/tasks/types.js +2 -0
  101. package/dist/repertoire/tools-base.js +714 -0
  102. package/dist/repertoire/tools-github.js +53 -0
  103. package/dist/repertoire/tools-teams.js +308 -0
  104. package/dist/repertoire/tools.js +199 -0
  105. package/dist/senses/cli-entry.js +15 -0
  106. package/dist/senses/cli.js +604 -0
  107. package/dist/senses/commands.js +98 -0
  108. package/dist/senses/inner-dialog-worker.js +61 -0
  109. package/dist/senses/inner-dialog.js +231 -0
  110. package/dist/senses/session-lock.js +119 -0
  111. package/dist/senses/teams-entry.js +15 -0
  112. package/dist/senses/teams.js +696 -0
  113. package/dist/senses/trust-gate.js +150 -0
  114. package/package.json +34 -11
  115. package/subagents/README.md +73 -0
  116. package/subagents/work-doer.md +233 -0
  117. package/subagents/work-merger.md +624 -0
  118. package/subagents/work-planner.md +373 -0
  119. package/bin/ouro.js +0 -6
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // Shared formatting functions for tool results, kicks, and errors.
3
+ // Used by both CLI and Teams adapters for consistent output.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.formatToolResult = formatToolResult;
6
+ exports.formatKick = formatKick;
7
+ exports.formatError = formatError;
8
+ const runtime_1 = require("../nerves/runtime");
9
+ function compactSummary(summary) {
10
+ const oneLine = summary.replace(/\s+/g, " ").trim();
11
+ if (oneLine.length <= 120)
12
+ return oneLine;
13
+ return oneLine.slice(0, 120) + "...";
14
+ }
15
+ function formatToolResult(name, summary, success) {
16
+ const compacted = compactSummary(summary);
17
+ (0, runtime_1.emitNervesEvent)({
18
+ event: "channel.message_sent",
19
+ component: "channels",
20
+ message: "formatted tool result for channel output",
21
+ meta: {
22
+ kind: "tool_result",
23
+ name,
24
+ success,
25
+ has_summary: compacted.length > 0,
26
+ },
27
+ });
28
+ if (success) {
29
+ return "\u2713 " + name + (compacted ? " (" + compacted + ")" : "");
30
+ }
31
+ return "\u2717 " + name + ": " + compacted;
32
+ }
33
+ function formatKick() {
34
+ (0, runtime_1.emitNervesEvent)({
35
+ event: "channel.message_sent",
36
+ component: "channels",
37
+ message: "formatted kick message for channel output",
38
+ meta: {
39
+ kind: "kick",
40
+ },
41
+ });
42
+ return "\u21BB kick";
43
+ }
44
+ function formatError(error) {
45
+ (0, runtime_1.emitNervesEvent)({
46
+ level: "error",
47
+ event: "channel.error",
48
+ component: "channels",
49
+ message: "formatted channel error message",
50
+ meta: {
51
+ kind: "error",
52
+ error_message: error.message,
53
+ },
54
+ });
55
+ return "Error: " + error.message;
56
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // Channel capabilities -- hardcoded const map keyed by channel identifier.
3
+ // Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.getChannelCapabilities = getChannelCapabilities;
6
+ const runtime_1 = require("../../nerves/runtime");
7
+ const CHANNEL_CAPABILITIES = {
8
+ cli: {
9
+ channel: "cli",
10
+ availableIntegrations: [],
11
+ supportsMarkdown: false,
12
+ supportsStreaming: true,
13
+ supportsRichCards: false,
14
+ maxMessageLength: Infinity,
15
+ },
16
+ teams: {
17
+ channel: "teams",
18
+ availableIntegrations: ["ado", "graph", "github"],
19
+ supportsMarkdown: true,
20
+ supportsStreaming: true,
21
+ supportsRichCards: true,
22
+ maxMessageLength: Infinity,
23
+ },
24
+ };
25
+ const DEFAULT_CAPABILITIES = {
26
+ channel: "cli",
27
+ availableIntegrations: [],
28
+ supportsMarkdown: false,
29
+ supportsStreaming: false,
30
+ supportsRichCards: false,
31
+ maxMessageLength: Infinity,
32
+ };
33
+ function getChannelCapabilities(channel) {
34
+ (0, runtime_1.emitNervesEvent)({
35
+ component: "channels",
36
+ event: "channel.capabilities_lookup",
37
+ message: "channel capabilities lookup",
38
+ meta: { channel },
39
+ });
40
+ return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
41
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ // FriendResolver -- resolves external identity into a FriendRecord + channel capabilities.
3
+ // Created per-request (per-incoming-message), per-friend.
4
+ // Replaces the old ContextResolver: no authority checker, no separate memory resolution.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FriendResolver = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const channel_1 = require("./channel");
9
+ const runtime_1 = require("../../nerves/runtime");
10
+ const CURRENT_SCHEMA_VERSION = 1;
11
+ class FriendResolver {
12
+ store;
13
+ params;
14
+ constructor(store, params) {
15
+ this.store = store;
16
+ this.params = params;
17
+ }
18
+ async resolve() {
19
+ const friend = await this.resolveOrCreate();
20
+ const channel = (0, channel_1.getChannelCapabilities)(this.params.channel);
21
+ return { friend, channel };
22
+ }
23
+ async resolveOrCreate() {
24
+ // Try to find existing friend by external ID
25
+ let existing = null;
26
+ try {
27
+ existing = await this.store.findByExternalId(this.params.provider, this.params.externalId, this.params.tenantId);
28
+ }
29
+ catch {
30
+ // Store search failure -- fall through to create new (D16)
31
+ }
32
+ if (existing)
33
+ return existing;
34
+ // First encounter -- create new FriendRecord
35
+ const now = new Date().toISOString();
36
+ const externalId = {
37
+ provider: this.params.provider,
38
+ externalId: this.params.externalId,
39
+ linkedAt: now,
40
+ ...(this.params.tenantId !== undefined ? { tenantId: this.params.tenantId } : {}),
41
+ };
42
+ const tenantMemberships = this.params.tenantId ? [this.params.tenantId] : [];
43
+ let hasAnyFriends = false;
44
+ try {
45
+ if (typeof this.store.hasAnyFriends === "function") {
46
+ hasAnyFriends = await this.store.hasAnyFriends();
47
+ }
48
+ }
49
+ catch {
50
+ hasAnyFriends = false;
51
+ }
52
+ const isFirstImprint = !hasAnyFriends;
53
+ const friend = {
54
+ id: (0, crypto_1.randomUUID)(),
55
+ name: this.params.displayName,
56
+ role: isFirstImprint ? "primary" : "stranger",
57
+ trustLevel: isFirstImprint ? "family" : "stranger",
58
+ connections: [],
59
+ externalIds: [externalId],
60
+ tenantMemberships,
61
+ toolPreferences: {},
62
+ notes: this.params.displayName !== "Unknown" ? { name: { value: this.params.displayName, savedAt: now } } : {},
63
+ totalTokens: 0,
64
+ createdAt: now,
65
+ updatedAt: now,
66
+ schemaVersion: CURRENT_SCHEMA_VERSION,
67
+ };
68
+ // Persist -- log and continue on failure (D16)
69
+ try {
70
+ await this.store.put(friend.id, friend);
71
+ }
72
+ catch (err) {
73
+ (0, runtime_1.emitNervesEvent)({
74
+ level: "error",
75
+ event: "friends.persist_error",
76
+ component: "friends",
77
+ message: "failed to persist friend record",
78
+ meta: { reason: err instanceof Error ? err.message : String(err) },
79
+ });
80
+ }
81
+ return friend;
82
+ }
83
+ }
84
+ exports.FriendResolver = FriendResolver;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ // FileFriendStore -- filesystem adapter for FriendStore.
3
+ // Stores each friend as one unified JSON file in bundle `friends/`.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.FileFriendStore = void 0;
39
+ const fs = __importStar(require("fs"));
40
+ const fsPromises = __importStar(require("fs/promises"));
41
+ const path = __importStar(require("path"));
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const DEFAULT_ROLE = "friend";
44
+ const DEFAULT_TRUST_LEVEL = "friend";
45
+ class FileFriendStore {
46
+ friendsPath;
47
+ constructor(friendsPath) {
48
+ this.friendsPath = friendsPath;
49
+ fs.mkdirSync(friendsPath, { recursive: true });
50
+ (0, runtime_1.emitNervesEvent)({
51
+ component: "friends",
52
+ event: "friends.store_init",
53
+ message: "file friend store initialized",
54
+ meta: {},
55
+ });
56
+ }
57
+ async get(id) {
58
+ const record = await this.readJson(path.join(this.friendsPath, `${id}.json`));
59
+ if (!record)
60
+ return null;
61
+ return this.normalize(record);
62
+ }
63
+ async put(id, record) {
64
+ await this.writeJson(path.join(this.friendsPath, `${id}.json`), this.normalize(record));
65
+ }
66
+ async delete(id) {
67
+ await this.removeFile(path.join(this.friendsPath, `${id}.json`));
68
+ }
69
+ async findByExternalId(provider, externalId, tenantId) {
70
+ let entries;
71
+ try {
72
+ entries = await fsPromises.readdir(this.friendsPath);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ for (const entry of entries) {
78
+ if (!entry.endsWith(".json"))
79
+ continue;
80
+ const raw = await this.readJson(path.join(this.friendsPath, entry));
81
+ if (!raw)
82
+ continue;
83
+ const record = this.normalize(raw);
84
+ const match = record.externalIds.some((ext) => ext.provider === provider &&
85
+ ext.externalId === externalId &&
86
+ (tenantId === undefined || ext.tenantId === tenantId));
87
+ if (match) {
88
+ return record;
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ async hasAnyFriends() {
94
+ let entries;
95
+ try {
96
+ entries = await fsPromises.readdir(this.friendsPath);
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ return entries.some((entry) => entry.endsWith(".json"));
102
+ }
103
+ normalize(raw) {
104
+ const trustLevel = raw.trustLevel;
105
+ const normalizedTrustLevel = trustLevel === "family" ||
106
+ trustLevel === "friend" ||
107
+ trustLevel === "acquaintance" ||
108
+ trustLevel === "stranger"
109
+ ? trustLevel
110
+ : DEFAULT_TRUST_LEVEL;
111
+ return {
112
+ id: raw.id,
113
+ name: raw.name,
114
+ role: typeof raw.role === "string" && raw.role.trim() ? raw.role : DEFAULT_ROLE,
115
+ trustLevel: normalizedTrustLevel,
116
+ connections: Array.isArray(raw.connections)
117
+ ? raw.connections
118
+ .filter((connection) => (typeof connection === "object" &&
119
+ connection !== null &&
120
+ typeof connection.name === "string" &&
121
+ typeof connection.relationship === "string"))
122
+ .map((connection) => ({
123
+ name: connection.name,
124
+ relationship: connection.relationship,
125
+ }))
126
+ : [],
127
+ externalIds: Array.isArray(raw.externalIds) ? raw.externalIds : [],
128
+ tenantMemberships: Array.isArray(raw.tenantMemberships) ? raw.tenantMemberships : [],
129
+ toolPreferences: raw.toolPreferences && typeof raw.toolPreferences === "object"
130
+ ? raw.toolPreferences
131
+ : {},
132
+ notes: raw.notes && typeof raw.notes === "object" ? raw.notes : {},
133
+ totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
134
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
135
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
136
+ schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
137
+ };
138
+ }
139
+ async readJson(filePath) {
140
+ try {
141
+ const raw = await fsPromises.readFile(filePath, "utf-8");
142
+ try {
143
+ const parsed = JSON.parse(raw);
144
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
145
+ return null;
146
+ }
147
+ return parsed;
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ async writeJson(filePath, data) {
158
+ await fsPromises.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
159
+ }
160
+ async removeFile(filePath) {
161
+ try {
162
+ await fsPromises.unlink(filePath);
163
+ }
164
+ catch (err) {
165
+ if (err?.code === "ENOENT")
166
+ return;
167
+ throw err;
168
+ }
169
+ }
170
+ }
171
+ exports.FileFriendStore = FileFriendStore;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Friend store abstraction.
3
+ // All friend persistence goes through FriendStore -- no friend module imports `fs` directly.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // Token accumulation helper.
3
+ // Tracks cumulative token usage per friend across turns.
4
+ // Called from both CLI and Teams adapters after each agent turn.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.accumulateFriendTokens = accumulateFriendTokens;
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ async function accumulateFriendTokens(store, friendId, usage) {
9
+ if (!usage?.output_tokens)
10
+ return;
11
+ const record = await store.get(friendId);
12
+ if (!record)
13
+ return;
14
+ // Only count output tokens (what the model generated for this friend).
15
+ // Input tokens are mostly system prompt re-sent every turn -- counting them
16
+ // would inflate the total and make the onboarding threshold meaningless.
17
+ record.totalTokens = (record.totalTokens ?? 0) + usage.output_tokens;
18
+ record.updatedAt = new Date().toISOString();
19
+ await store.put(record.id, record);
20
+ (0, runtime_1.emitNervesEvent)({
21
+ component: "friends",
22
+ event: "friends.tokens_accumulated",
23
+ message: "tokens accumulated for friend",
24
+ meta: { friendId, outputTokens: usage.output_tokens },
25
+ });
26
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // Context kernel type definitions.
3
+ // FriendRecord (merged identity + memory), channel capabilities, and resolved context.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.isIdentityProvider = isIdentityProvider;
6
+ exports.isIntegration = isIntegration;
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation"]);
9
+ function isIdentityProvider(value) {
10
+ (0, runtime_1.emitNervesEvent)({
11
+ component: "friends",
12
+ event: "friends.identity_provider_check",
13
+ message: "identity provider validation",
14
+ meta: {},
15
+ });
16
+ return typeof value === "string" && IDENTITY_PROVIDERS.has(value);
17
+ }
18
+ const INTEGRATIONS = new Set(["ado", "github", "graph"]);
19
+ function isIntegration(value) {
20
+ return typeof value === "string" && INTEGRATIONS.has(value);
21
+ }