@opengeni/contracts 0.50.0 → 1.0.0
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/dist/agent-authored-durable-text.d.ts +70 -0
- package/dist/agent-config.d.ts +869 -0
- package/dist/agent-instruction-changes.d.ts +110 -0
- package/dist/agent-learning.d.ts +243 -0
- package/dist/agent-topology.d.ts +292 -0
- package/dist/allowance-refusal.d.ts +9 -0
- package/dist/allowance-refusal.js +50 -0
- package/dist/allowance-refusal.js.map +1 -0
- package/dist/analytics-consent-report.d.ts +19 -0
- package/dist/analytics-consent-report.js +10 -0
- package/dist/analytics-consent-report.js.map +1 -0
- package/dist/api-key-presets.d.ts +19 -0
- package/dist/artifact-catalog.d.ts +161 -0
- package/dist/artifacts.d.ts +135 -11
- package/dist/atlassian-native-retirement.d.ts +16 -0
- package/dist/atlassian-native-retirement.js +30 -0
- package/dist/atlassian-native-retirement.js.map +1 -0
- package/dist/atlassian.d.ts +2 -0
- package/dist/atlassian.js +26 -12
- package/dist/browser-storage.d.ts +5 -0
- package/dist/browser-storage.js +9 -0
- package/dist/browser-storage.js.map +1 -0
- package/dist/bundled-skills.d.ts +40 -0
- package/dist/chunk-2ACYHZFB.js +292 -0
- package/dist/chunk-2ACYHZFB.js.map +1 -0
- package/dist/chunk-2VORLN2R.js +353 -0
- package/dist/chunk-2VORLN2R.js.map +1 -0
- package/dist/{chunk-Y3MLSLB4.js → chunk-3SIZGL3O.js} +42 -30
- package/dist/chunk-3SIZGL3O.js.map +1 -0
- package/dist/chunk-46KLD2UH.js +186 -0
- package/dist/chunk-46KLD2UH.js.map +1 -0
- package/dist/chunk-4JGHSSB7.js +11 -0
- package/dist/chunk-4JGHSSB7.js.map +1 -0
- package/dist/{chunk-M5ZGRVIQ.js → chunk-5IXYF6XN.js} +14 -5
- package/dist/chunk-5IXYF6XN.js.map +1 -0
- package/dist/chunk-BCS7WHCK.js +139 -0
- package/dist/chunk-BCS7WHCK.js.map +1 -0
- package/dist/chunk-EK7NDGFR.js +128 -0
- package/dist/chunk-EK7NDGFR.js.map +1 -0
- package/dist/{chunk-5UDNP7AO.js → chunk-FZ6BXQHL.js} +78 -38
- package/dist/chunk-FZ6BXQHL.js.map +1 -0
- package/dist/{chunk-CM6BMECR.js → chunk-GRG7PHWQ.js} +2 -1
- package/dist/chunk-GRG7PHWQ.js.map +1 -0
- package/dist/chunk-KHHZTBP7.js +11 -0
- package/dist/chunk-KHHZTBP7.js.map +1 -0
- package/dist/chunk-KR2V42UZ.js +187 -0
- package/dist/chunk-KR2V42UZ.js.map +1 -0
- package/dist/chunk-MZLBPKFE.js +168 -0
- package/dist/chunk-MZLBPKFE.js.map +1 -0
- package/dist/{chunk-LW7OH6CS.js → chunk-NRMCVHAU.js} +6 -5
- package/dist/chunk-NRMCVHAU.js.map +1 -0
- package/dist/{chunk-TZ6YEYN4.js → chunk-NVJMJWLL.js} +3 -1
- package/dist/chunk-NVJMJWLL.js.map +1 -0
- package/dist/chunk-O77VSFYZ.js +70 -0
- package/dist/chunk-O77VSFYZ.js.map +1 -0
- package/dist/chunk-PX3VCKAU.js +29 -0
- package/dist/chunk-PX3VCKAU.js.map +1 -0
- package/dist/chunk-QR5D2IQC.js +61 -0
- package/dist/chunk-QR5D2IQC.js.map +1 -0
- package/dist/chunk-RJ76DRPO.js +117 -0
- package/dist/chunk-RJ76DRPO.js.map +1 -0
- package/dist/chunk-RKX3NQYS.js +28 -0
- package/dist/chunk-RKX3NQYS.js.map +1 -0
- package/dist/{chunk-JBCIY7QD.js → chunk-SIOU2K4U.js} +45 -22
- package/dist/chunk-SIOU2K4U.js.map +1 -0
- package/dist/chunk-TSCDAGET.js +75 -0
- package/dist/chunk-TSCDAGET.js.map +1 -0
- package/dist/{chunk-LSB7GRLG.js → chunk-TXTQIYCV.js} +15 -11
- package/dist/chunk-TXTQIYCV.js.map +1 -0
- package/dist/chunk-X7ETVO4P.js +26544 -0
- package/dist/chunk-X7ETVO4P.js.map +1 -0
- package/dist/{chunk-CADSX6UN.js → chunk-Y3KNXVTD.js} +2 -2
- package/dist/chunk-YBW5JSA3.js +251 -0
- package/dist/chunk-YBW5JSA3.js.map +1 -0
- package/dist/chunk-YRXAWWCU.js +45 -0
- package/dist/chunk-YRXAWWCU.js.map +1 -0
- package/dist/claude-provider-account-authority.d.ts +18 -0
- package/dist/claude-subscription-oauth.d.ts +42 -0
- package/dist/claude-subscription-usage.d.ts +159 -0
- package/dist/client-error-report.d.ts +109 -0
- package/dist/client-error-report.js +87 -0
- package/dist/client-error-report.js.map +1 -0
- package/dist/code-search.d.ts +52 -0
- package/dist/code-search.js +40 -0
- package/dist/code-search.js.map +1 -0
- package/dist/company-brain-governed-writes.d.ts +475 -0
- package/dist/company-brain.d.ts +336 -0
- package/dist/company-profile.d.ts +566 -0
- package/dist/connect.d.ts +325 -0
- package/dist/connect.js +191 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-account-label.d.ts +2 -0
- package/dist/connection-account-label.js +35 -0
- package/dist/connection-account-label.js.map +1 -0
- package/dist/connection-authority.d.ts +193 -0
- package/dist/connection-authority.js +50 -0
- package/dist/connection-authority.js.map +1 -0
- package/dist/connector-attachments.d.ts +95 -0
- package/dist/connector-attachments.js +51 -0
- package/dist/connector-attachments.js.map +1 -0
- package/dist/connector-tool-permissions.d.ts +73 -0
- package/dist/direct-model-provider.d.ts +36 -0
- package/dist/editable-artifact-codec-registry.d.ts +16 -7
- package/dist/editable-artifact-codec-registry.js +18 -8
- package/dist/editable-artifact-committed-transaction.d.ts +2 -2
- package/dist/editable-artifact-committed-transaction.js +2 -2
- package/dist/editable-artifact-live.d.ts +11 -12
- package/dist/editable-artifact-live.js +110 -45
- package/dist/editable-artifact-live.js.map +1 -1
- package/dist/editable-artifact-serialized-commit.d.ts +2 -1
- package/dist/editable-artifact-serialized-commit.js +8 -7
- package/dist/editable-artifact-versions.d.ts +20 -5
- package/dist/editable-artifact-versions.js +23 -5
- package/dist/editable-artifacts.d.ts +2 -0
- package/dist/editable-artifacts.js +47 -35
- package/dist/external-identities.d.ts +784 -0
- package/dist/external-identities.js +44 -0
- package/dist/external-identities.js.map +1 -0
- package/dist/feedback.d.ts +51 -0
- package/dist/github-repository-contracts.d.ts +36 -0
- package/dist/github-repository-contracts.js +59 -0
- package/dist/github-repository-contracts.js.map +1 -0
- package/dist/github-repository.d.ts +14 -0
- package/dist/github-repository.js +36 -0
- package/dist/github-repository.js.map +1 -0
- package/dist/google-drive.d.ts +90 -1
- package/dist/google-drive.js +90 -16
- package/dist/google-drive.js.map +1 -1
- package/dist/governed-learning-activation.d.ts +154 -0
- package/dist/governed-learning-evaluator.d.ts +152 -0
- package/dist/host-mcp-bindings.d.ts +326 -0
- package/dist/host-mcp-bindings.js +252 -0
- package/dist/host-mcp-bindings.js.map +1 -0
- package/dist/image-generation.d.ts +1 -0
- package/dist/index.d.ts +32396 -11386
- package/dist/index.js +2198 -164
- package/dist/insights-usage.d.ts +877 -0
- package/dist/insights-usage.js +509 -0
- package/dist/insights-usage.js.map +1 -0
- package/dist/interaction.d.ts +344 -0
- package/dist/knowledge-entries.d.ts +982 -0
- package/dist/knowledge-preparation.d.ts +237 -0
- package/dist/knowledge.d.ts +197 -0
- package/dist/managed-auth-session-sets.d.ts +241 -0
- package/dist/managed-auth-session-sets.js +191 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-sign-in-methods.d.ts +56 -0
- package/dist/managed-sign-in-methods.js +46 -0
- package/dist/managed-sign-in-methods.js.map +1 -0
- package/dist/mcp-catalog-limits.d.ts +2 -0
- package/dist/mcp-endpoint.d.ts +7 -0
- package/dist/mcp-endpoint.js +9 -0
- package/dist/mcp-endpoint.js.map +1 -0
- package/dist/mcp-oauth.d.ts +71 -0
- package/dist/modal-native-proof-v2.d.ts +587 -0
- package/dist/modal-native-proof-v2.js +37 -0
- package/dist/modal-native-proof-v2.js.map +1 -0
- package/dist/model-availability.d.ts +15 -0
- package/dist/model-connection-access.d.ts +24 -0
- package/dist/model-context-inspector.d.ts +329 -0
- package/dist/model-display.d.ts +38 -0
- package/dist/model-display.js +134 -0
- package/dist/model-display.js.map +1 -0
- package/dist/model-picker-order.d.ts +25 -0
- package/dist/model-picker-order.js +42 -0
- package/dist/model-picker-order.js.map +1 -0
- package/dist/organization-access.d.ts +231 -0
- package/dist/organization-access.js +34 -0
- package/dist/organization-access.js.map +1 -0
- package/dist/organization-integration-policy.d.ts +27 -0
- package/dist/organization-membership-lifecycle.d.ts +1022 -0
- package/dist/organization-model-usage.d.ts +146 -0
- package/dist/organization-model-usage.js +67 -0
- package/dist/organization-model-usage.js.map +1 -0
- package/dist/organization-recovery.d.ts +337 -0
- package/dist/organization-recovery.js +37 -0
- package/dist/organization-recovery.js.map +1 -0
- package/dist/organization-usage.d.ts +138 -0
- package/dist/permissions.d.ts +54 -0
- package/dist/personal-github.d.ts +320 -0
- package/dist/personal-github.js +222 -0
- package/dist/personal-github.js.map +1 -0
- package/dist/plugin-discovery.d.ts +35 -0
- package/dist/plugin-discovery.js +7 -0
- package/dist/plugin-discovery.js.map +1 -0
- package/dist/preference-registry.d.ts +44 -1
- package/dist/product-analytics.d.ts +113 -0
- package/dist/product-lifecycle-facts.d.ts +73 -0
- package/dist/remember.d.ts +350 -0
- package/dist/retained-output.d.ts +8 -0
- package/dist/sandbox-file-artifacts.d.ts +57 -0
- package/dist/sandbox-provider-command.d.ts +113 -0
- package/dist/sandbox-recovery.d.ts +146 -0
- package/dist/sandbox-snapshots.d.ts +39 -0
- package/dist/session-final-reply.d.ts +6 -0
- package/dist/session-final-reply.js +9 -0
- package/dist/session-final-reply.js.map +1 -0
- package/dist/session-goal-reports.d.ts +21 -0
- package/dist/session-history-import.d.ts +947 -0
- package/dist/session-list-entries.d.ts +4 -0
- package/dist/session-list-entries.js +51 -0
- package/dist/session-list-entries.js.map +1 -0
- package/dist/session-mcp-projections.d.ts +215 -0
- package/dist/session-message-search.d.ts +68 -0
- package/dist/session-titles.d.ts +68 -0
- package/dist/session-titles.js +25 -0
- package/dist/session-titles.js.map +1 -0
- package/dist/session-topology-primitives.d.ts +22 -0
- package/dist/signup-attribution.d.ts +39 -0
- package/dist/site-request-headers.test.d.ts +1 -0
- package/dist/site-session-http.d.ts +11 -0
- package/dist/site-session-http.js +11 -0
- package/dist/site-session-http.js.map +1 -0
- package/dist/skill-catalog-context.d.ts +7 -0
- package/dist/skill-files.d.ts +12 -0
- package/dist/skill-metadata.d.ts +9 -0
- package/dist/skill-use.d.ts +61 -0
- package/dist/skills.d.ts +185 -0
- package/dist/slack-bot-scopes.d.ts +46 -21
- package/dist/slack-bot-scopes.js +14 -3
- package/dist/slack-rest-mcp.d.ts +31 -0
- package/dist/slack-rest-mcp.js +19 -0
- package/dist/slack-rest-mcp.js.map +1 -0
- package/dist/spreadsheet-artifact-commands.d.ts +2 -3
- package/dist/spreadsheet-artifact-commands.js +4 -2
- package/dist/spreadsheet-artifact-query.js +3 -2
- package/dist/subscription-account-pools.d.ts +292 -0
- package/dist/task-notes.d.ts +42 -1
- package/dist/tool-catalog.d.ts +286 -0
- package/dist/tool-result-spill.d.ts +16 -0
- package/dist/usage-allowances.d.ts +285 -0
- package/dist/usage-allowances.js +49 -0
- package/dist/usage-allowances.js.map +1 -0
- package/dist/video-generation.d.ts +3 -0
- package/dist/video-generation.js +1 -1
- package/dist/work-claims.d.ts +411 -0
- package/dist/workspace-instruction-policies.d.ts +94 -0
- package/dist/workspace-integration-wire.d.ts +36 -0
- package/dist/workspace-integration-wire.js +23 -0
- package/dist/workspace-integration-wire.js.map +1 -0
- package/dist/workspace-integrations.d.ts +948 -0
- package/dist/workspace-learning-administration.d.ts +289 -0
- package/dist/workspace-learning-policy.d.ts +1 -1
- package/dist/workspace-state.d.ts +85 -416
- package/dist/xai-provider-account-authority.d.ts +3 -0
- package/dist/xai-provider-account-authority.js +1 -1
- package/package.json +134 -1
- package/src/agent-authored-durable-text.ts +116 -0
- package/src/agent-config.ts +1527 -0
- package/src/agent-instruction-changes.ts +98 -0
- package/src/agent-learning.ts +161 -0
- package/src/agent-topology.ts +69 -0
- package/src/allowance-refusal.ts +81 -0
- package/src/analytics-consent-report.ts +20 -0
- package/src/api-key-presets.ts +25 -0
- package/src/artifact-catalog.ts +60 -0
- package/src/artifacts.ts +146 -18
- package/src/atlassian-native-retirement.ts +33 -0
- package/src/browser-storage.ts +10 -0
- package/src/bundled-skills.ts +73 -0
- package/src/claude-provider-account-authority.ts +12 -0
- package/src/claude-subscription-oauth.ts +62 -0
- package/src/claude-subscription-usage.ts +59 -0
- package/src/client-error-report.ts +184 -0
- package/src/code-search.ts +105 -0
- package/src/company-brain-governed-writes.ts +356 -0
- package/src/company-brain.ts +201 -0
- package/src/company-profile.ts +167 -1
- package/src/connect.ts +202 -0
- package/src/connection-account-label.ts +46 -0
- package/src/connection-authority.ts +213 -0
- package/src/connector-attachments.ts +327 -0
- package/src/connector-tool-permissions.ts +60 -0
- package/src/direct-model-provider.ts +98 -0
- package/src/editable-artifact-codec-registry.ts +55 -17
- package/src/editable-artifact-committed-transaction.ts +44 -31
- package/src/editable-artifact-live.ts +123 -57
- package/src/editable-artifact-serialized-commit.ts +2 -1
- package/src/editable-artifact-versions.ts +26 -5
- package/src/editable-artifacts.ts +2 -0
- package/src/external-identities.ts +285 -0
- package/src/feedback.ts +39 -0
- package/src/github-repository-contracts.ts +85 -0
- package/src/github-repository.ts +59 -0
- package/src/google-drive.ts +76 -4
- package/src/governed-learning-activation.ts +191 -0
- package/src/governed-learning-evaluator.ts +176 -0
- package/src/host-mcp-bindings.ts +298 -0
- package/src/image-generation.ts +8 -2
- package/src/index.ts +7093 -1466
- package/src/insights-usage.ts +679 -0
- package/src/interaction.ts +220 -5
- package/src/knowledge-entries.ts +375 -0
- package/src/knowledge-preparation.ts +50 -0
- package/src/knowledge.ts +134 -6
- package/src/managed-auth-session-sets.ts +253 -0
- package/src/managed-sign-in-methods.ts +37 -0
- package/src/mcp-catalog-limits.ts +2 -0
- package/src/mcp-endpoint.ts +30 -0
- package/src/mcp-oauth.ts +66 -0
- package/src/modal-native-proof-v2.ts +398 -0
- package/src/model-availability.ts +22 -0
- package/src/model-connection-access.ts +17 -0
- package/src/model-context-inspector.ts +139 -0
- package/src/model-display.ts +195 -0
- package/src/model-picker-order.ts +97 -0
- package/src/organization-access.ts +176 -0
- package/src/organization-integration-policy.ts +54 -0
- package/src/organization-membership-lifecycle.ts +559 -0
- package/src/organization-model-usage.ts +72 -0
- package/src/organization-recovery.ts +186 -0
- package/src/organization-usage.ts +79 -0
- package/src/permissions.ts +58 -0
- package/src/personal-github.ts +267 -0
- package/src/plugin-discovery.ts +33 -0
- package/src/preference-registry.ts +20 -1
- package/src/product-analytics.ts +253 -0
- package/src/product-lifecycle-facts.ts +121 -0
- package/src/remember.ts +243 -0
- package/src/retained-output.ts +20 -3
- package/src/sandbox-file-artifacts.ts +73 -0
- package/src/sandbox-provider-command.ts +82 -0
- package/src/sandbox-recovery.ts +116 -0
- package/src/sandbox-snapshots.ts +142 -0
- package/src/session-final-reply.ts +16 -0
- package/src/session-goal-reports.ts +55 -0
- package/src/session-history-import.ts +159 -0
- package/src/session-list-entries.ts +91 -0
- package/src/session-mcp-projections.ts +273 -0
- package/src/session-message-search.ts +54 -0
- package/src/session-titles.ts +467 -0
- package/src/session-topology-primitives.ts +21 -0
- package/src/signup-attribution.ts +81 -0
- package/src/site-request-headers.test.ts +28 -0
- package/src/site-session-http.ts +63 -0
- package/src/skill-catalog-context.ts +50 -0
- package/src/skill-files.ts +52 -0
- package/src/skill-metadata.ts +40 -0
- package/src/skill-use.ts +77 -0
- package/src/skills.ts +136 -0
- package/src/slack-bot-scopes.ts +106 -42
- package/src/slack-rest-mcp.ts +194 -0
- package/src/spreadsheet-artifact-commands.ts +12 -13
- package/src/subscription-account-pools.ts +44 -0
- package/src/task-notes.ts +14 -1
- package/src/tool-catalog.ts +155 -0
- package/src/tool-result-spill.ts +35 -0
- package/src/usage-allowances.ts +187 -0
- package/src/work-claims.ts +266 -0
- package/src/workspace-instruction-policies.ts +31 -0
- package/src/workspace-integration-wire.ts +99 -0
- package/src/workspace-integrations.ts +640 -0
- package/src/workspace-learning-administration.ts +88 -0
- package/src/workspace-learning-policy.ts +4 -3
- package/src/workspace-state.ts +16 -126
- package/src/xai-provider-account-authority.ts +1 -0
- package/dist/chunk-5UDNP7AO.js.map +0 -1
- package/dist/chunk-BVGIYW7D.js +0 -11
- package/dist/chunk-BVGIYW7D.js.map +0 -1
- package/dist/chunk-CM6BMECR.js.map +0 -1
- package/dist/chunk-JBCIY7QD.js.map +0 -1
- package/dist/chunk-LSB7GRLG.js.map +0 -1
- package/dist/chunk-LW7OH6CS.js.map +0 -1
- package/dist/chunk-M5ZGRVIQ.js.map +0 -1
- package/dist/chunk-Q6A7XT2N.js +0 -16734
- package/dist/chunk-Q6A7XT2N.js.map +0 -1
- package/dist/chunk-TZ6YEYN4.js.map +0 -1
- package/dist/chunk-Y3MLSLB4.js.map +0 -1
- /package/dist/{chunk-CADSX6UN.js.map → chunk-Y3KNXVTD.js.map} +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Durable, model-visible catalog context. Text markers survive SDK/provider replay. */
|
|
2
|
+
export const SKILL_CATALOG_CONTEXT_PREFIX = "<opengeni_skill_catalog>\n";
|
|
3
|
+
export const SKILL_CATALOG_CONTEXT_SUFFIX = "\n</opengeni_skill_catalog>";
|
|
4
|
+
const GUIDANCE =
|
|
5
|
+
"This is the current configured Skill index and replaces earlier configured Skill indexes. " +
|
|
6
|
+
"Entries describe discoverability, not permission or full instructions. " +
|
|
7
|
+
"Use skill_read for instructions and skill_search for current discovery; tool authorization remains authoritative.\n\n";
|
|
8
|
+
|
|
9
|
+
export function skillCatalogContextItem(catalog: string): Record<string, unknown> {
|
|
10
|
+
return {
|
|
11
|
+
type: "message",
|
|
12
|
+
role: "developer",
|
|
13
|
+
content: SKILL_CATALOG_CONTEXT_PREFIX + GUIDANCE + catalog + SKILL_CATALOG_CONTEXT_SUFFIX,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Accept the Chat Completions projection and SDK text-part normalization too. */
|
|
18
|
+
export function readSkillCatalogContext(item: Record<string, unknown>): string | null {
|
|
19
|
+
if (item.type === "unknown" && item.providerData && typeof item.providerData === "object") {
|
|
20
|
+
const data = item.providerData as Record<string, unknown>;
|
|
21
|
+
if (data.type === "message") return readSkillCatalogContext(data);
|
|
22
|
+
}
|
|
23
|
+
if (item.type !== "message" || (item.role !== "developer" && item.role !== "system")) return null;
|
|
24
|
+
const content =
|
|
25
|
+
typeof item.content === "string"
|
|
26
|
+
? item.content
|
|
27
|
+
: Array.isArray(item.content) &&
|
|
28
|
+
item.content.every(
|
|
29
|
+
(part) =>
|
|
30
|
+
part &&
|
|
31
|
+
(part.type === "input_text" || part.type === "output_text" || part.type === "text") &&
|
|
32
|
+
typeof part.text === "string",
|
|
33
|
+
)
|
|
34
|
+
? item.content.map((part) => part.text).join("")
|
|
35
|
+
: null;
|
|
36
|
+
const prefix = SKILL_CATALOG_CONTEXT_PREFIX + GUIDANCE;
|
|
37
|
+
return content?.startsWith(prefix) && content.endsWith(SKILL_CATALOG_CONTEXT_SUFFIX)
|
|
38
|
+
? content.slice(prefix.length, -SKILL_CATALOG_CONTEXT_SUFFIX.length)
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function latestSkillCatalogContext(
|
|
43
|
+
items: readonly Record<string, unknown>[],
|
|
44
|
+
): Record<string, unknown> | undefined {
|
|
45
|
+
for (let index = items.length - 1; index >= 0; index--) {
|
|
46
|
+
const catalog = readSkillCatalogContext(items[index]!);
|
|
47
|
+
if (catalog !== null) return skillCatalogContextItem(catalog);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Shared text-folder limits for API admission, runtime imports and checkout. */
|
|
2
|
+
export const SKILL_MAX_FILES = 1024;
|
|
3
|
+
export const SKILL_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
4
|
+
export const SKILL_MAX_TOTAL_BYTES = 8 * 1024 * 1024;
|
|
5
|
+
|
|
6
|
+
export function isSafeSkillRelativePath(path: string): boolean {
|
|
7
|
+
return (
|
|
8
|
+
path.length > 0 &&
|
|
9
|
+
path.length <= 512 &&
|
|
10
|
+
!/[\\:\u0000-\u001f\u007f]/u.test(path) &&
|
|
11
|
+
path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..") &&
|
|
12
|
+
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
13
|
+
new TextEncoder().encode(path),
|
|
14
|
+
) === path
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Validates bytes, not extensions. Does not rewrite text or interpret metadata. */
|
|
19
|
+
export function validateSkillTextFiles(files: readonly { path: string; content: string }[]): {
|
|
20
|
+
totalBytes: number;
|
|
21
|
+
} {
|
|
22
|
+
if (files.length === 0 || files.length > SKILL_MAX_FILES)
|
|
23
|
+
throw new Error(`Skill artifact must contain 1-${SKILL_MAX_FILES} files`);
|
|
24
|
+
const paths = new Set<string>();
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
27
|
+
let totalBytes = 0;
|
|
28
|
+
for (const { path, content } of files) {
|
|
29
|
+
if (!isSafeSkillRelativePath(path))
|
|
30
|
+
throw new Error(`Skill artifact contains an unsafe path: ${path}`);
|
|
31
|
+
if (paths.has(path)) throw new Error(`Skill artifact contains duplicate file path: ${path}`);
|
|
32
|
+
paths.add(path);
|
|
33
|
+
if (content.includes("\u0000")) throw new Error(`Skill artifact contains NUL bytes: ${path}`);
|
|
34
|
+
const bytes = encoder.encode(content);
|
|
35
|
+
if (decoder.decode(bytes) !== content)
|
|
36
|
+
throw new Error(`Skill artifact contains malformed Unicode text: ${path}`);
|
|
37
|
+
if (bytes.byteLength > SKILL_MAX_FILE_BYTES)
|
|
38
|
+
throw new Error(`Skill artifact file exceeds ${SKILL_MAX_FILE_BYTES} bytes: ${path}`);
|
|
39
|
+
totalBytes += bytes.byteLength;
|
|
40
|
+
if (totalBytes > SKILL_MAX_TOTAL_BYTES)
|
|
41
|
+
throw new Error(`Skill artifact exceeds ${SKILL_MAX_TOTAL_BYTES} bytes`);
|
|
42
|
+
}
|
|
43
|
+
for (const path of paths) {
|
|
44
|
+
const segments = path.split("/");
|
|
45
|
+
for (let index = 1; index < segments.length; index++) {
|
|
46
|
+
const prefix = segments.slice(0, index).join("/");
|
|
47
|
+
if (paths.has(prefix))
|
|
48
|
+
throw new Error(`Skill artifact uses a path as both a file and a directory: ${prefix}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { totalBytes };
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseDocument } from "yaml";
|
|
2
|
+
|
|
3
|
+
/** The one interpretation of Skill metadata for imports, writes and migration. */
|
|
4
|
+
export function parseSkillFrontmatter(markdown: string): {
|
|
5
|
+
name: string | null;
|
|
6
|
+
description: string | null;
|
|
7
|
+
} {
|
|
8
|
+
const lines = markdown.split(/\r?\n/u);
|
|
9
|
+
if (lines[0]?.trim() !== "---") return { name: null, description: null };
|
|
10
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
11
|
+
if (end === -1) return { name: null, description: null };
|
|
12
|
+
const document = parseDocument(lines.slice(1, end).join("\n"), {
|
|
13
|
+
schema: "core",
|
|
14
|
+
uniqueKeys: true,
|
|
15
|
+
prettyErrors: false,
|
|
16
|
+
});
|
|
17
|
+
if (document.errors.length || document.warnings.length) {
|
|
18
|
+
const issue = document.errors[0] ?? document.warnings[0]!;
|
|
19
|
+
throw new Error(
|
|
20
|
+
`This skill's SKILL.md header contains invalid YAML: ${issue.message}. The source file needs correcting.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const metadata: unknown = document.toJS({ maxAliasCount: 20 });
|
|
24
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
25
|
+
throw new Error("Skill artifact SKILL.md frontmatter must be a mapping");
|
|
26
|
+
const fields = metadata as Record<string, unknown>;
|
|
27
|
+
return {
|
|
28
|
+
name: typeof fields.name === "string" ? fields.name : null,
|
|
29
|
+
description: typeof fields.description === "string" ? fields.description : null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readSkillMetadata(markdown: string): { name: string; description: string } {
|
|
34
|
+
const { name, description } = parseSkillFrontmatter(markdown);
|
|
35
|
+
if (!name || name.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
|
|
36
|
+
throw new Error("Skill artifact SKILL.md must declare a safe name");
|
|
37
|
+
if (!description?.trim() || description.length > 1024)
|
|
38
|
+
throw new Error("Skill artifact SKILL.md must declare a description of 1–1024 characters");
|
|
39
|
+
return { name, description };
|
|
40
|
+
}
|
package/src/skill-use.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `_meta` key of the content-free Skill-use fact on a model `skill_read`
|
|
5
|
+
* result. MCP `_meta` never reaches the model: it survives only into the
|
|
6
|
+
* `agent.toolCall.output` event projection, never into model history.
|
|
7
|
+
*/
|
|
8
|
+
export const SKILL_USE_META_KEY = "opengeni/skillUse" as const;
|
|
9
|
+
|
|
10
|
+
/** Where the Skill a read resolved to comes from. `personal` is a user-scope Skill. */
|
|
11
|
+
export const SkillUseSource = z.enum([
|
|
12
|
+
"builtin",
|
|
13
|
+
"session",
|
|
14
|
+
"workspace",
|
|
15
|
+
"organization",
|
|
16
|
+
"personal",
|
|
17
|
+
]);
|
|
18
|
+
export type SkillUseSource = z.infer<typeof SkillUseSource>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a `skill_read` returned: `full` is the default SKILL.md read, `files`
|
|
22
|
+
* explicit paths, `list` a path inventory, and `already_in_context` the short
|
|
23
|
+
* repeat receipt. `refused` means the read returned an error instead of Skill
|
|
24
|
+
* text; it has no result, so it appears only in metrics.
|
|
25
|
+
*/
|
|
26
|
+
export const SkillReadKind = z.enum(["full", "already_in_context", "files", "list", "refused"]);
|
|
27
|
+
export type SkillReadKind = z.infer<typeof SkillReadKind>;
|
|
28
|
+
|
|
29
|
+
const skillUseFields = {
|
|
30
|
+
id: z.string().min(1).max(512),
|
|
31
|
+
source: SkillUseSource,
|
|
32
|
+
label: z.string().min(1).max(128).optional(),
|
|
33
|
+
/** Whole-artifact digest, for a Skill without a ledger revision. */
|
|
34
|
+
contentSha256: z
|
|
35
|
+
.string()
|
|
36
|
+
.regex(/^[0-9a-f]{64}$/u)
|
|
37
|
+
.optional(),
|
|
38
|
+
/** Ledger revision of a workspace, organization, or personal Skill. */
|
|
39
|
+
revisionId: z.string().min(1).max(128).optional(),
|
|
40
|
+
kind: SkillReadKind.exclude(["refused"]),
|
|
41
|
+
/** UTF-8 bytes of the result text the read returned, before model tool-output truncation. */
|
|
42
|
+
bytes: z.number().int().nonnegative(),
|
|
43
|
+
/** The Skill appeared in the Skill index the model saw this turn. */
|
|
44
|
+
inIndex: z.boolean(),
|
|
45
|
+
/** skill_search returned this Skill earlier in the same turn attempt. */
|
|
46
|
+
searchedThisTurn: z.boolean(),
|
|
47
|
+
};
|
|
48
|
+
const oneProvenance = (use: {
|
|
49
|
+
contentSha256?: string | undefined;
|
|
50
|
+
revisionId?: string | undefined;
|
|
51
|
+
}) => use.contentSha256 === undefined || use.revisionId === undefined;
|
|
52
|
+
const oneProvenanceMessage = {
|
|
53
|
+
message: "A Skill use carries a content digest or a revision, not both",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ids, digests, and counts only. Never Skill text, user text, or a Skill's
|
|
58
|
+
* title or description. `label` is reserved for a platform display label of a
|
|
59
|
+
* built-in; user-authored Skills never set it. This is the writer's closed
|
|
60
|
+
* shape: an unknown field fails the parse, so nothing else can be attached.
|
|
61
|
+
*/
|
|
62
|
+
export const SkillUse = z.strictObject(skillUseFields).refine(oneProvenance, oneProvenanceMessage);
|
|
63
|
+
export type SkillUse = z.infer<typeof SkillUse>;
|
|
64
|
+
|
|
65
|
+
// Stored events outlive the worker that wrote them. A reader drops fields a
|
|
66
|
+
// newer writer added instead of dropping the whole fact, and what it returns
|
|
67
|
+
// still holds only the known, content-free fields.
|
|
68
|
+
const StoredSkillUse = z.object(skillUseFields).refine(oneProvenance, oneProvenanceMessage);
|
|
69
|
+
|
|
70
|
+
/** The Skill-use fact on a tool result or its event projection, when present and valid. */
|
|
71
|
+
export function skillUseFromToolOutput(output: unknown): SkillUse | null {
|
|
72
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) return null;
|
|
73
|
+
const meta = (output as { _meta?: unknown })._meta;
|
|
74
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
75
|
+
const parsed = StoredSkillUse.safeParse((meta as Record<string, unknown>)[SKILL_USE_META_KEY]);
|
|
76
|
+
return parsed.success ? parsed.data : null;
|
|
77
|
+
}
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export { parseSkillFrontmatter, readSkillMetadata } from "./skill-metadata";
|
|
3
|
+
export * from "./skill-files";
|
|
4
|
+
|
|
5
|
+
/** Files are UTF-8 text, relative to the Skill root. No executable authority. */
|
|
6
|
+
export const SkillFile = z.object({ path: z.string().min(1).max(512), content: z.string() });
|
|
7
|
+
export type SkillFile = z.infer<typeof SkillFile>;
|
|
8
|
+
export const SkillActor = z.discriminatedUnion("kind", [
|
|
9
|
+
z.object({
|
|
10
|
+
kind: z.literal("service"),
|
|
11
|
+
subjectId: z.string().min(1),
|
|
12
|
+
principalKind: z.enum(["service", "api_key", "configured_key"]),
|
|
13
|
+
}),
|
|
14
|
+
z.object({
|
|
15
|
+
kind: z.literal("human"),
|
|
16
|
+
subjectId: z.string().min(1),
|
|
17
|
+
principalKind: z.literal("human_session"),
|
|
18
|
+
}),
|
|
19
|
+
z.object({
|
|
20
|
+
kind: z.literal("agent"),
|
|
21
|
+
sessionId: z.uuid(),
|
|
22
|
+
turnId: z.uuid(),
|
|
23
|
+
attemptId: z.uuid(),
|
|
24
|
+
executionGeneration: z.number().int().positive(),
|
|
25
|
+
}),
|
|
26
|
+
]);
|
|
27
|
+
export type SkillActor = z.infer<typeof SkillActor>;
|
|
28
|
+
export type SkillScope = "workspace" | "organization" | "user";
|
|
29
|
+
export type SkillWriteContext = { accountId: string; workspaceId: string; actor: SkillActor };
|
|
30
|
+
export type SkillSaveInput = SkillWriteContext & {
|
|
31
|
+
operationId: string;
|
|
32
|
+
skillId: string;
|
|
33
|
+
expectedRevisionId: string | null;
|
|
34
|
+
expectedScopeVersion: number;
|
|
35
|
+
stableKey: string;
|
|
36
|
+
files: SkillFile[];
|
|
37
|
+
scope?: SkillScope;
|
|
38
|
+
reason: string;
|
|
39
|
+
};
|
|
40
|
+
export type SkillRevisionInput = SkillWriteContext & {
|
|
41
|
+
removalOperationId?: string | undefined;
|
|
42
|
+
operationId: string;
|
|
43
|
+
skillId: string;
|
|
44
|
+
revisionId: string;
|
|
45
|
+
expectedRevisionId: string | null;
|
|
46
|
+
expectedScopeVersion: number;
|
|
47
|
+
reason: string;
|
|
48
|
+
};
|
|
49
|
+
export type SkillRemoveInput = SkillWriteContext & {
|
|
50
|
+
operationId: string;
|
|
51
|
+
skillId: string;
|
|
52
|
+
expectedRevisionId: string | null;
|
|
53
|
+
expectedScopeVersion: number;
|
|
54
|
+
reason: string;
|
|
55
|
+
};
|
|
56
|
+
export type SkillInstallInput = SkillWriteContext & {
|
|
57
|
+
operationId: string;
|
|
58
|
+
skillFacetId: string;
|
|
59
|
+
reason: string;
|
|
60
|
+
};
|
|
61
|
+
export const SkillReviewReference = z.object({
|
|
62
|
+
removalOperationId: z.uuid().optional(),
|
|
63
|
+
sourceOperationId: z.uuid(),
|
|
64
|
+
skillId: z.uuid(),
|
|
65
|
+
revisionId: z.uuid(),
|
|
66
|
+
expectedRevisionId: z.uuid().nullable(),
|
|
67
|
+
expectedScopeVersion: z.number().int().positive(),
|
|
68
|
+
});
|
|
69
|
+
export type SkillReviewReference = z.infer<typeof SkillReviewReference>;
|
|
70
|
+
export function skillReviewHumanInput(skillReview: SkillReviewReference) {
|
|
71
|
+
const removing = Boolean(skillReview.removalOperationId);
|
|
72
|
+
return {
|
|
73
|
+
questions: [
|
|
74
|
+
{
|
|
75
|
+
id: `skill:${skillReview.revisionId}`,
|
|
76
|
+
kind: "single_select" as const,
|
|
77
|
+
label: removing ? "Permanently delete this Skill?" : "Save this Skill?",
|
|
78
|
+
prompt: removing
|
|
79
|
+
? "Permanently delete this Skill and all its stored revisions?"
|
|
80
|
+
: "Save this exact Skill revision for this workspace?",
|
|
81
|
+
helpText: removing
|
|
82
|
+
? "This cannot be undone. All stored Skill revisions will be deleted; conversations remain unchanged."
|
|
83
|
+
: "Review the complete files before saving. Saving activates this revision immediately.",
|
|
84
|
+
options: [
|
|
85
|
+
{ id: "save", label: removing ? "Permanently delete" : "Save" },
|
|
86
|
+
{ id: "skip", label: removing ? "Keep Skill" : "Don't save" },
|
|
87
|
+
],
|
|
88
|
+
required: true,
|
|
89
|
+
allowOther: false,
|
|
90
|
+
skillReview,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export const SkillWriteReceipt = z.object({
|
|
96
|
+
removed: z.boolean().optional(),
|
|
97
|
+
operationId: z.uuid(),
|
|
98
|
+
skillId: z.uuid(),
|
|
99
|
+
revisionId: z.uuid(),
|
|
100
|
+
outcome: z.enum(["applied", "pending", "preserved"]),
|
|
101
|
+
decision: z.literal("rejected").optional(),
|
|
102
|
+
pendingReason: z.enum(["approval", "source_finalization"]).optional(),
|
|
103
|
+
skillReview: SkillReviewReference.optional(),
|
|
104
|
+
replayed: z.boolean(),
|
|
105
|
+
});
|
|
106
|
+
export type SkillWriteReceipt = z.infer<typeof SkillWriteReceipt>;
|
|
107
|
+
export const SkillPublicationReceipt = SkillWriteReceipt.extend({
|
|
108
|
+
sourceOperationId: z.uuid(),
|
|
109
|
+
activationEventId: z.uuid().nullable(),
|
|
110
|
+
});
|
|
111
|
+
export type SkillPublicationReceipt = z.infer<typeof SkillPublicationReceipt>;
|
|
112
|
+
export const SkillSourceReleaseReceipt = z.object({
|
|
113
|
+
skillId: z.uuid(),
|
|
114
|
+
revisionId: z.uuid().nullable(),
|
|
115
|
+
disposition: z.enum(["deactivated", "preserved", "inactive"]),
|
|
116
|
+
eventId: z.uuid().nullable(),
|
|
117
|
+
warning: z.string().nullable(),
|
|
118
|
+
});
|
|
119
|
+
export type SkillSourceReleaseReceipt = z.infer<typeof SkillSourceReleaseReceipt>;
|
|
120
|
+
export type SkillRecord = {
|
|
121
|
+
removalOperationId?: string | null;
|
|
122
|
+
activationMode: "workspace_managed" | "session_selected";
|
|
123
|
+
pendingRevisionIds: string[];
|
|
124
|
+
id: string;
|
|
125
|
+
stableKey: string;
|
|
126
|
+
scope: SkillScope;
|
|
127
|
+
scopeVersion: number;
|
|
128
|
+
status: string;
|
|
129
|
+
activeRevisionId: string | null;
|
|
130
|
+
revisionId: string | null;
|
|
131
|
+
title: string | null;
|
|
132
|
+
description: string | null;
|
|
133
|
+
contentHash: string | null;
|
|
134
|
+
files: SkillFile[];
|
|
135
|
+
source: { pluginId: string; facetKey: string; skillFacetId: string } | null;
|
|
136
|
+
};
|
package/src/slack-bot-scopes.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { OPENGENI_SLACK_REST_USER_SCOPES } from "./slack-rest-mcp";
|
|
2
|
+
|
|
1
3
|
export const OPENGENI_SLACK_BOT_REQUIRED_SCOPES = [
|
|
2
4
|
"app_mentions:read",
|
|
3
5
|
"canvases:read",
|
|
@@ -19,20 +21,45 @@ export const OPENGENI_SLACK_BOT_REQUIRED_SCOPES = [
|
|
|
19
21
|
/** Read-only scope needed only for the optional emoji-reaction summon surface. */
|
|
20
22
|
export const OPENGENI_SLACK_REACTION_REQUIRED_SCOPE = "reactions:read" as const;
|
|
21
23
|
|
|
24
|
+
/** Optional grant for explicit retained-file delivery to a task's Slack thread. */
|
|
25
|
+
export const OPENGENI_SLACK_FILE_UPLOAD_REQUIRED_SCOPE = "files:write" as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Bot-token search scopes for Slack's Real-time Search API
|
|
29
|
+
* (`assistant.search.context`). Slack accepts these as BOT scopes, so
|
|
30
|
+
* workspace-wide public search runs under the bot identity and never needs a
|
|
31
|
+
* human proxy token. Private-channel, DM, and MPIM search remain user-token
|
|
32
|
+
* only and belong exclusively to the personal hosted-MCP grant.
|
|
33
|
+
*/
|
|
34
|
+
export const OPENGENI_SLACK_BOT_SEARCH_SCOPES = [
|
|
35
|
+
"search:read.public",
|
|
36
|
+
"search:read.files",
|
|
37
|
+
"search:read.users",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
22
40
|
/**
|
|
23
41
|
* Scopes requested by the managed and generated self-hosted manifests.
|
|
24
42
|
*
|
|
25
|
-
* `reactions:read`
|
|
26
|
-
* legacy installations may continue using existing
|
|
27
|
-
* tools while the reaction setting stays disabled
|
|
28
|
-
* reinstall.
|
|
43
|
+
* `reactions:read`, `files:write`, and the bot search scopes are not part of the
|
|
44
|
+
* base eligibility contract: legacy installations may continue using existing
|
|
45
|
+
* Slack interactions and tools while the reaction setting stays disabled, bot
|
|
46
|
+
* search and file delivery stay unavailable, and the tools ask an admin to reinstall.
|
|
29
47
|
*/
|
|
30
48
|
export const OPENGENI_SLACK_BOT_REQUESTED_SCOPES = [
|
|
31
49
|
...OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
|
|
32
50
|
OPENGENI_SLACK_REACTION_REQUIRED_SCOPE,
|
|
51
|
+
OPENGENI_SLACK_FILE_UPLOAD_REQUIRED_SCOPE,
|
|
33
52
|
] as const;
|
|
34
53
|
|
|
54
|
+
export function openGeniSlackBotRequestedScopes(mode: "limited" | "full" = "limited"): string[] {
|
|
55
|
+
return [
|
|
56
|
+
...OPENGENI_SLACK_BOT_REQUESTED_SCOPES,
|
|
57
|
+
...(mode === "full" ? OPENGENI_SLACK_BOT_SEARCH_SCOPES : []),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
export const OPENGENI_SLACK_BOT_EVENTS = [
|
|
62
|
+
"app_home_opened",
|
|
36
63
|
"app_mention",
|
|
37
64
|
"message.channels",
|
|
38
65
|
"message.groups",
|
|
@@ -41,37 +68,42 @@ export const OPENGENI_SLACK_BOT_EVENTS = [
|
|
|
41
68
|
"reaction_added",
|
|
42
69
|
] as const;
|
|
43
70
|
|
|
44
|
-
/**
|
|
45
|
-
export const OPENGENI_SLACK_MCP_USER_SCOPES =
|
|
46
|
-
"search:read.public",
|
|
47
|
-
"search:read.private",
|
|
48
|
-
"search:read.mpim",
|
|
49
|
-
"search:read.im",
|
|
50
|
-
"search:read.files",
|
|
51
|
-
"files:read",
|
|
52
|
-
"emoji:read",
|
|
53
|
-
"search:read.users",
|
|
54
|
-
"chat:write",
|
|
55
|
-
"channels:history",
|
|
56
|
-
"groups:history",
|
|
57
|
-
"mpim:history",
|
|
58
|
-
"im:history",
|
|
59
|
-
"channels:write",
|
|
60
|
-
"groups:write",
|
|
61
|
-
"im:write",
|
|
62
|
-
"mpim:write",
|
|
63
|
-
"reactions:write",
|
|
64
|
-
"canvases:read",
|
|
65
|
-
"canvases:write",
|
|
66
|
-
"users:read",
|
|
67
|
-
"users:read.email",
|
|
68
|
-
"channels:read",
|
|
69
|
-
"groups:read",
|
|
70
|
-
"mpim:read",
|
|
71
|
-
] as const;
|
|
71
|
+
/** User grants required by the reviewed Slack API MCP tools. */
|
|
72
|
+
export const OPENGENI_SLACK_MCP_USER_SCOPES = OPENGENI_SLACK_REST_USER_SCOPES;
|
|
72
73
|
|
|
73
74
|
export const OPENGENI_MANAGED_PUBLIC_BASE_URL = "https://app.opengeni.ai" as const;
|
|
74
75
|
|
|
76
|
+
export type OpenGeniSlackBotManifestOptions = Readonly<{
|
|
77
|
+
appName?: string;
|
|
78
|
+
botDisplayName?: string;
|
|
79
|
+
slashCommand?: string;
|
|
80
|
+
shortcutName?: string;
|
|
81
|
+
accessMode?: "limited" | "full";
|
|
82
|
+
}>;
|
|
83
|
+
|
|
84
|
+
function normalizedSlackManifestText(
|
|
85
|
+
value: string | undefined,
|
|
86
|
+
fallback: string,
|
|
87
|
+
label: string,
|
|
88
|
+
maxLength: number,
|
|
89
|
+
): string {
|
|
90
|
+
const normalized = value?.trim() || fallback;
|
|
91
|
+
if (normalized.length > maxLength || /[\r\n\0]/u.test(normalized)) {
|
|
92
|
+
throw new Error(`Slack bot manifest ${label} must be at most ${maxLength} plain characters`);
|
|
93
|
+
}
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizedSlackCommand(value: string | undefined): string {
|
|
98
|
+
const command = value?.trim() || "/opengeni";
|
|
99
|
+
if (!/^\/[a-z0-9_-]{1,31}$/u.test(command)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Slack bot manifest slash command must start with / and contain at most 31 lowercase letters, digits, hyphens, or underscores",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return command;
|
|
105
|
+
}
|
|
106
|
+
|
|
75
107
|
function normalizedSlackManifestBaseUrl(publicBaseUrl: string): string {
|
|
76
108
|
const parsed = new URL(publicBaseUrl);
|
|
77
109
|
if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
|
|
@@ -84,16 +116,38 @@ function normalizedSlackManifestBaseUrl(publicBaseUrl: string): string {
|
|
|
84
116
|
}
|
|
85
117
|
|
|
86
118
|
/** Canonical managed/self-hosted Slack app manifest. Slack accepts JSON manifests. */
|
|
87
|
-
export function buildOpenGeniSlackBotManifest(
|
|
119
|
+
export function buildOpenGeniSlackBotManifest(
|
|
120
|
+
publicBaseUrl: string,
|
|
121
|
+
options: OpenGeniSlackBotManifestOptions = {},
|
|
122
|
+
) {
|
|
88
123
|
const baseUrl = normalizedSlackManifestBaseUrl(publicBaseUrl);
|
|
124
|
+
const appName = normalizedSlackManifestText(options.appName, "OpenGeni", "app name", 35);
|
|
125
|
+
const botDisplayName = normalizedSlackManifestText(
|
|
126
|
+
options.botDisplayName,
|
|
127
|
+
"OpenGeni",
|
|
128
|
+
"bot display name",
|
|
129
|
+
80,
|
|
130
|
+
);
|
|
131
|
+
const slashCommand = normalizedSlackCommand(options.slashCommand);
|
|
132
|
+
const shortcutName = normalizedSlackManifestText(
|
|
133
|
+
options.shortcutName,
|
|
134
|
+
"Open in Opengeni",
|
|
135
|
+
"shortcut name",
|
|
136
|
+
35,
|
|
137
|
+
);
|
|
89
138
|
return {
|
|
90
|
-
display_information: { name:
|
|
139
|
+
display_information: { name: appName },
|
|
91
140
|
features: {
|
|
92
|
-
|
|
141
|
+
app_home: {
|
|
142
|
+
home_tab_enabled: true,
|
|
143
|
+
messages_tab_enabled: true,
|
|
144
|
+
messages_tab_read_only_enabled: false,
|
|
145
|
+
},
|
|
146
|
+
bot_user: { display_name: botDisplayName, always_online: false },
|
|
93
147
|
slash_commands: [
|
|
94
148
|
{
|
|
95
|
-
command:
|
|
96
|
-
description: "Start an
|
|
149
|
+
command: slashCommand,
|
|
150
|
+
description: "Start an Opengeni task in this channel",
|
|
97
151
|
should_escape: false,
|
|
98
152
|
url: `${baseUrl}/v1/integrations/slack/commands`,
|
|
99
153
|
},
|
|
@@ -101,8 +155,8 @@ export function buildOpenGeniSlackBotManifest(publicBaseUrl: string) {
|
|
|
101
155
|
shortcuts: [
|
|
102
156
|
{
|
|
103
157
|
callback_id: "opengeni_message",
|
|
104
|
-
description: "Start an
|
|
105
|
-
name:
|
|
158
|
+
description: "Start an Opengeni task from this Slack message",
|
|
159
|
+
name: shortcutName,
|
|
106
160
|
type: "message",
|
|
107
161
|
},
|
|
108
162
|
],
|
|
@@ -113,7 +167,7 @@ export function buildOpenGeniSlackBotManifest(publicBaseUrl: string) {
|
|
|
113
167
|
`${baseUrl}/v1/integrations/slack/callback`,
|
|
114
168
|
],
|
|
115
169
|
scopes: {
|
|
116
|
-
bot:
|
|
170
|
+
bot: openGeniSlackBotRequestedScopes(options.accessMode),
|
|
117
171
|
user: [...OPENGENI_SLACK_MCP_USER_SCOPES],
|
|
118
172
|
},
|
|
119
173
|
},
|
|
@@ -135,15 +189,25 @@ export function buildOpenGeniSlackBotManifest(publicBaseUrl: string) {
|
|
|
135
189
|
}
|
|
136
190
|
|
|
137
191
|
/**
|
|
138
|
-
* Optional bot grants
|
|
139
|
-
*
|
|
192
|
+
* Optional bot grants used by the shipped read/identity and task-file-delivery
|
|
193
|
+
* surfaces. Every other unrequired scope fails closed, including unknown future
|
|
140
194
|
* Slack scopes, so verification, core routing, and UI eligibility cannot drift.
|
|
141
195
|
*/
|
|
142
196
|
export const OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES = [
|
|
143
197
|
"team:read",
|
|
144
198
|
OPENGENI_SLACK_REACTION_REQUIRED_SCOPE,
|
|
199
|
+
OPENGENI_SLACK_FILE_UPLOAD_REQUIRED_SCOPE,
|
|
200
|
+
...OPENGENI_SLACK_BOT_SEARCH_SCOPES,
|
|
145
201
|
] as const;
|
|
146
202
|
|
|
203
|
+
export function hasOpenGeniSlackFileUploadScope(grantedScopes: readonly string[]): boolean {
|
|
204
|
+
return grantedScopes.includes(OPENGENI_SLACK_FILE_UPLOAD_REQUIRED_SCOPE);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function hasOpenGeniSlackBotSearchScopes(grantedScopes: readonly string[]): boolean {
|
|
208
|
+
return OPENGENI_SLACK_BOT_SEARCH_SCOPES.every((scope) => grantedScopes.includes(scope));
|
|
209
|
+
}
|
|
210
|
+
|
|
147
211
|
/** @deprecated Use evaluateOpenGeniSlackBotScopes; an allowlist is the policy. */
|
|
148
212
|
export const OPENGENI_SLACK_BOT_FORBIDDEN_SCOPES = ["channels:join", "chat:write.public"] as const;
|
|
149
213
|
|