@nimbalyst/extension-sdk 0.2.0 → 0.2.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.
- package/CHANGELOG.md +20 -0
- package/README.md +32 -0
- package/dist/__tests__/createTextCollabContentAdapter.test.d.ts +2 -0
- package/dist/__tests__/createTextCollabContentAdapter.test.d.ts.map +1 -0
- package/dist/__tests__/createTextCollabContentAdapter.test.js +60 -0
- package/dist/__tests__/externalsPlugin.test.d.ts +2 -0
- package/dist/__tests__/externalsPlugin.test.d.ts.map +1 -0
- package/dist/__tests__/externalsPlugin.test.js +16 -0
- package/dist/__tests__/fileDirectoryTree.test.d.ts +2 -0
- package/dist/__tests__/fileDirectoryTree.test.d.ts.map +1 -0
- package/dist/__tests__/fileDirectoryTree.test.js +54 -0
- package/dist/__tests__/hasSeedableContent.test.d.ts +2 -0
- package/dist/__tests__/hasSeedableContent.test.d.ts.map +1 -0
- package/dist/__tests__/hasSeedableContent.test.js +25 -0
- package/dist/__tests__/manifestValidation.agentProviders.test.d.ts +2 -0
- package/dist/__tests__/manifestValidation.agentProviders.test.d.ts.map +1 -0
- package/dist/__tests__/manifestValidation.agentProviders.test.js +44 -0
- package/dist/agents/AgentProtocol.d.ts +233 -0
- package/dist/agents/AgentProtocol.d.ts.map +1 -0
- package/dist/agents/AgentProtocol.js +23 -0
- package/dist/agents/AgentProtocolHost.d.ts +190 -0
- package/dist/agents/AgentProtocolHost.d.ts.map +1 -0
- package/dist/agents/AgentProtocolHost.js +42 -0
- package/dist/agents/index.d.ts +26 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +24 -0
- package/dist/collab/createTextCollabContentAdapter.d.ts +26 -0
- package/dist/collab/createTextCollabContentAdapter.d.ts.map +1 -0
- package/dist/collab/createTextCollabContentAdapter.js +79 -0
- package/dist/externalsPlugin.js +10 -0
- package/dist/fileDirectoryTree.d.ts +19 -0
- package/dist/fileDirectoryTree.d.ts.map +1 -0
- package/dist/fileDirectoryTree.js +98 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/manifestValidation.d.ts +118 -0
- package/dist/manifestValidation.d.ts.map +1 -0
- package/dist/manifestValidation.js +475 -0
- package/dist/trackerReferences.d.ts +32 -0
- package/dist/trackerReferences.d.ts.map +1 -0
- package/dist/trackerReferences.js +7 -0
- package/dist/types/collab.d.ts +138 -0
- package/dist/types/collab.d.ts.map +1 -0
- package/dist/types/collab.js +1 -0
- package/dist/types/editor.d.ts +81 -0
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/editors.d.ts +4 -0
- package/dist/types/editors.d.ts.map +1 -1
- package/dist/types/extension.d.ts +364 -5
- package/dist/types/extension.d.ts.map +1 -1
- package/dist/types/extension.js +11 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/panel.d.ts +50 -0
- package/dist/types/panel.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +119 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +33 -0
- package/dist/types/theme.d.ts +50 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/dist/types/theme.js +7 -1
- package/dist/types/trackerImporter.d.ts +147 -0
- package/dist/types/trackerImporter.d.ts.map +1 -0
- package/dist/types/trackerImporter.js +33 -0
- package/dist/useCollaborativeEditor.d.ts +84 -30
- package/dist/useCollaborativeEditor.d.ts.map +1 -1
- package/dist/useCollaborativeEditor.js +98 -7
- package/dist/validate.browser.d.ts +5 -0
- package/dist/validate.browser.d.ts.map +1 -0
- package/dist/validate.browser.js +27 -0
- package/dist/validate.d.ts +2 -8
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +117 -0
- package/dist/validationTypes.d.ts +9 -0
- package/dist/validationTypes.d.ts.map +1 -0
- package/dist/validationTypes.js +1 -0
- package/package.json +8 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure (no Node/Electron deps) validation helpers for manifest fields.
|
|
3
|
+
*
|
|
4
|
+
* Runs at both build time (via validate.ts, which reads manifest.json from
|
|
5
|
+
* disk) and runtime (when the host loads an extension and needs to refuse
|
|
6
|
+
* invalid contributions). Keep this file dependency-free so it can be bundled
|
|
7
|
+
* into either context without dragging in fs/path.
|
|
8
|
+
*/
|
|
9
|
+
import type { ExtensionPermissionId } from './types/permissions.js';
|
|
10
|
+
/**
|
|
11
|
+
* Manifest validation rejects extensions declaring more than this many AI
|
|
12
|
+
* agent providers. Chosen by symmetry with
|
|
13
|
+
* {@link MAX_BACKEND_MODULES_PER_EXTENSION}: an extension that ships more than
|
|
14
|
+
* a handful of agent providers is almost certainly mis-modeled (split it into
|
|
15
|
+
* multiple extensions, or surface them as models on one provider). The cap
|
|
16
|
+
* also keeps the settings UI -- which lists each provider as its own row --
|
|
17
|
+
* from running away.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_AGENT_PROVIDERS_PER_EXTENSION = 4;
|
|
20
|
+
export interface BackendModuleValidationIssue {
|
|
21
|
+
/** The module being validated, or undefined for whole-extension issues */
|
|
22
|
+
moduleId?: string;
|
|
23
|
+
message: string;
|
|
24
|
+
/**
|
|
25
|
+
* Non-fatal issues (e.g., a deprecated permission id that the validator
|
|
26
|
+
* silently drops) carry `severity: "warning"`. Callers that fail builds
|
|
27
|
+
* on issues should ignore warnings.
|
|
28
|
+
*/
|
|
29
|
+
severity?: 'error' | 'warning';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate the `contributions.backendModules` array on a manifest.
|
|
33
|
+
* Returns the list of problems found - empty means valid.
|
|
34
|
+
*
|
|
35
|
+
* Callers decide whether to treat issues as fatal (host loader: refuse the
|
|
36
|
+
* extension) or as warnings (build-time validator: print and continue with
|
|
37
|
+
* non-zero exit).
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateBackendModules(backendModules: unknown): BackendModuleValidationIssue[];
|
|
40
|
+
/**
|
|
41
|
+
* Convenience wrapper that throws if any fatal issues are found. Warnings
|
|
42
|
+
* (currently: deprecated permission ids) are surfaced via the issues array
|
|
43
|
+
* but never throw, so an extension that still references `spawn-process`
|
|
44
|
+
* keeps loading -- the host just drops the id when computing effective
|
|
45
|
+
* permissions.
|
|
46
|
+
*
|
|
47
|
+
* Use this in main-process load paths where invalid manifests should refuse
|
|
48
|
+
* the extension outright.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertBackendModulesValid(extensionId: string, backendModules: unknown): void;
|
|
51
|
+
/**
|
|
52
|
+
* Filter a raw `permissions` array on a backend-module contribution down to
|
|
53
|
+
* the ids the host actually understands. Deprecated catalog ids
|
|
54
|
+
* (`spawn-process`, `network-loopback`, `network-internet`, `filesystem`)
|
|
55
|
+
* are dropped silently -- they never gated anything inside the backend
|
|
56
|
+
* runtime, so the host treats them as no-ops. Unknown ids are also dropped;
|
|
57
|
+
* `validateBackendModules` raises a separate error for those, so the loader
|
|
58
|
+
* will refuse the module before this is consulted in earnest.
|
|
59
|
+
*/
|
|
60
|
+
export declare function effectiveModulePermissions(raw: unknown): ExtensionPermissionId[];
|
|
61
|
+
/**
|
|
62
|
+
* Issue surface used by {@link validateAgentProviders}. Shaped to mirror
|
|
63
|
+
* {@link BackendModuleValidationIssue} so callers can pool the two streams
|
|
64
|
+
* (build-time validator, runtime loader) through one error pipeline.
|
|
65
|
+
*/
|
|
66
|
+
export interface AgentProviderValidationIssue {
|
|
67
|
+
/** The provider being validated, or undefined for whole-extension issues */
|
|
68
|
+
providerId?: string;
|
|
69
|
+
message: string;
|
|
70
|
+
/**
|
|
71
|
+
* Non-fatal issues carry `severity: "warning"`. Callers that fail builds
|
|
72
|
+
* on issues should ignore warnings.
|
|
73
|
+
*/
|
|
74
|
+
severity?: 'error' | 'warning';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Extract the set of declared backend-module ids from a manifest's
|
|
78
|
+
* `contributions.backendModules` array. Used by
|
|
79
|
+
* {@link validateAgentProviders} so the `backendModuleId` cross-check does
|
|
80
|
+
* not need the caller to pre-compute the set.
|
|
81
|
+
*
|
|
82
|
+
* Returns an empty set if `backendModules` is missing or malformed. Exported
|
|
83
|
+
* so build tooling can reuse the same extraction logic when checking entry
|
|
84
|
+
* files against the declared modules.
|
|
85
|
+
*/
|
|
86
|
+
export declare function extractBackendModuleIds(backendModules: unknown): Set<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Validate the `contributions.aiAgentProviders` array on a manifest.
|
|
89
|
+
*
|
|
90
|
+
* AI agent providers are surfaced in Nimbalyst's chat / agent UI as selectable
|
|
91
|
+
* providers (alongside built-in Claude Code, Claude, OpenAI, etc.). Each
|
|
92
|
+
* provider is implemented by a backend module that runs outside the renderer
|
|
93
|
+
* (so it can drive a CLI, hold long-lived processes, or speak a custom
|
|
94
|
+
* protocol). The contribution shape, in practice:
|
|
95
|
+
*
|
|
96
|
+
* {
|
|
97
|
+
* id: string, // unique within the extension
|
|
98
|
+
* displayName: string, // shown in the provider dropdown
|
|
99
|
+
* backendModuleId: string, // must match a declared backendModule
|
|
100
|
+
* models?: Array<{ id, name, ... }> // models the provider advertises
|
|
101
|
+
* toolFileLinks?: { [toolName]: ... } // optional UI hint table
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* This validator checks the manifest-side wiring; it does not introspect the
|
|
105
|
+
* bundle. See {@link validateExtensionBundle} (Node-only) for the bundle-side
|
|
106
|
+
* check that the referenced backend module actually exports an
|
|
107
|
+
* `activate`/`createAgentProvider` function.
|
|
108
|
+
*
|
|
109
|
+
* Returns the list of problems found - empty means valid.
|
|
110
|
+
*/
|
|
111
|
+
export declare function validateAgentProviders(aiAgentProviders: unknown, backendModules: unknown): AgentProviderValidationIssue[];
|
|
112
|
+
/**
|
|
113
|
+
* Convenience wrapper that throws if any fatal aiAgentProviders issues are
|
|
114
|
+
* found. Mirrors {@link assertBackendModulesValid}. Use this in main-process
|
|
115
|
+
* load paths where invalid manifests should refuse the extension outright.
|
|
116
|
+
*/
|
|
117
|
+
export declare function assertAgentProvidersValid(extensionId: string, aiAgentProviders: unknown, backendModules: unknown): void;
|
|
118
|
+
//# sourceMappingURL=manifestValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifestValidation.d.ts","sourceRoot":"","sources":["../src/manifestValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAGV,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAuCnD,MAAM,WAAW,4BAA4B;IAC3C,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,OAAO,GACtB,4BAA4B,EAAE,CAoKhC;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,OAAO,GACtB,IAAI,CAYN;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,GACX,qBAAqB,EAAE,CAazB;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAW5E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,OAAO,EACzB,cAAc,EAAE,OAAO,GACtB,4BAA4B,EAAE,CAgMhC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,OAAO,EACzB,cAAc,EAAE,OAAO,GACtB,IAAI,CAYN"}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure (no Node/Electron deps) validation helpers for manifest fields.
|
|
3
|
+
*
|
|
4
|
+
* Runs at both build time (via validate.ts, which reads manifest.json from
|
|
5
|
+
* disk) and runtime (when the host loads an extension and needs to refuse
|
|
6
|
+
* invalid contributions). Keep this file dependency-free so it can be bundled
|
|
7
|
+
* into either context without dragging in fs/path.
|
|
8
|
+
*/
|
|
9
|
+
import { MAX_BACKEND_MODULES_PER_EXTENSION } from './types/extension.js';
|
|
10
|
+
/**
|
|
11
|
+
* Manifest validation rejects extensions declaring more than this many AI
|
|
12
|
+
* agent providers. Chosen by symmetry with
|
|
13
|
+
* {@link MAX_BACKEND_MODULES_PER_EXTENSION}: an extension that ships more than
|
|
14
|
+
* a handful of agent providers is almost certainly mis-modeled (split it into
|
|
15
|
+
* multiple extensions, or surface them as models on one provider). The cap
|
|
16
|
+
* also keeps the settings UI -- which lists each provider as its own row --
|
|
17
|
+
* from running away.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_AGENT_PROVIDERS_PER_EXTENSION = 4;
|
|
20
|
+
const AGENT_PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
21
|
+
/**
|
|
22
|
+
* Mirrors the host's permission registry. We duplicate the list here so
|
|
23
|
+
* extensions don't have to depend on Electron internals to validate locally.
|
|
24
|
+
* Adding a new id requires updating both this list AND
|
|
25
|
+
* `packages/electron/src/main/extensions/permissionRegistry.ts`.
|
|
26
|
+
*/
|
|
27
|
+
const KNOWN_PERMISSION_IDS = [
|
|
28
|
+
'workspace-files',
|
|
29
|
+
'nimbalyst-database-read',
|
|
30
|
+
'nimbalyst-database-write',
|
|
31
|
+
'secrets-read',
|
|
32
|
+
'mcp-server-register',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Permission ids that used to be in the catalog but never enforced anything
|
|
36
|
+
* meaningful at the backend boundary (ambient Node capabilities). Manifests
|
|
37
|
+
* that still reference them are accepted -- the validator silently drops the
|
|
38
|
+
* id from the effective list -- so we don't break older extensions during
|
|
39
|
+
* the catalog cleanup. Authors get a non-fatal warning issue back.
|
|
40
|
+
*/
|
|
41
|
+
const DEPRECATED_PERMISSION_IDS = [
|
|
42
|
+
'spawn-process',
|
|
43
|
+
'network-loopback',
|
|
44
|
+
'network-internet',
|
|
45
|
+
'filesystem',
|
|
46
|
+
];
|
|
47
|
+
const KNOWN_RUNTIMES = [
|
|
48
|
+
'utility-process',
|
|
49
|
+
'worker-thread',
|
|
50
|
+
];
|
|
51
|
+
const BACKEND_MODULE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
52
|
+
/**
|
|
53
|
+
* Validate the `contributions.backendModules` array on a manifest.
|
|
54
|
+
* Returns the list of problems found - empty means valid.
|
|
55
|
+
*
|
|
56
|
+
* Callers decide whether to treat issues as fatal (host loader: refuse the
|
|
57
|
+
* extension) or as warnings (build-time validator: print and continue with
|
|
58
|
+
* non-zero exit).
|
|
59
|
+
*/
|
|
60
|
+
export function validateBackendModules(backendModules) {
|
|
61
|
+
if (backendModules === undefined) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(backendModules)) {
|
|
65
|
+
return [{ message: 'contributions.backendModules must be an array' }];
|
|
66
|
+
}
|
|
67
|
+
const issues = [];
|
|
68
|
+
if (backendModules.length > MAX_BACKEND_MODULES_PER_EXTENSION) {
|
|
69
|
+
issues.push({
|
|
70
|
+
message: `contributions.backendModules declares ${backendModules.length} modules; ` +
|
|
71
|
+
`the maximum is ${MAX_BACKEND_MODULES_PER_EXTENSION}. ` +
|
|
72
|
+
'Consolidate modules to keep the consent prompt manageable.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const seenIds = new Set();
|
|
76
|
+
for (let i = 0; i < backendModules.length; i += 1) {
|
|
77
|
+
const raw = backendModules[i];
|
|
78
|
+
if (!raw || typeof raw !== 'object') {
|
|
79
|
+
issues.push({ message: `backendModules[${i}] must be an object` });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const module = raw;
|
|
83
|
+
const moduleLabel = typeof module.id === 'string' ? module.id : `index ${i}`;
|
|
84
|
+
if (typeof module.id !== 'string' || !BACKEND_MODULE_ID_PATTERN.test(module.id)) {
|
|
85
|
+
issues.push({
|
|
86
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
87
|
+
message: `backendModules[${moduleLabel}].id must be a lowercase string matching ` +
|
|
88
|
+
`${BACKEND_MODULE_ID_PATTERN.source} (got ${JSON.stringify(module.id)})`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (seenIds.has(module.id)) {
|
|
92
|
+
issues.push({
|
|
93
|
+
moduleId: module.id,
|
|
94
|
+
message: `backendModules contains duplicate id "${module.id}"`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
seenIds.add(module.id);
|
|
99
|
+
}
|
|
100
|
+
if (typeof module.entry !== 'string' || module.entry.length === 0) {
|
|
101
|
+
issues.push({
|
|
102
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
103
|
+
message: `backendModules[${moduleLabel}].entry must be a non-empty relative path string`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (module.entry.startsWith('/') || module.entry.includes('..')) {
|
|
107
|
+
// Refuse absolute paths and parent-directory traversal so a module
|
|
108
|
+
// can't escape its extension root. The host resolves entry relative
|
|
109
|
+
// to the extension directory; only safe relative paths belong here.
|
|
110
|
+
issues.push({
|
|
111
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
112
|
+
message: `backendModules[${moduleLabel}].entry must be a relative path within the extension root ` +
|
|
113
|
+
`(no leading "/", no ".." segments)`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (!KNOWN_RUNTIMES.includes(module.runtime)) {
|
|
117
|
+
issues.push({
|
|
118
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
119
|
+
message: `backendModules[${moduleLabel}].runtime must be one of: ${KNOWN_RUNTIMES.join(', ')} ` +
|
|
120
|
+
`(got ${JSON.stringify(module.runtime)})`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (module.permissions !== undefined && !Array.isArray(module.permissions)) {
|
|
124
|
+
issues.push({
|
|
125
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
126
|
+
message: `backendModules[${moduleLabel}].permissions must be an array of ` +
|
|
127
|
+
`host-brokered capability ids (or omitted). The implicit "run native ` +
|
|
128
|
+
`code" grant is conferred by enabling the module itself, not by an ` +
|
|
129
|
+
`entry in this array.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (Array.isArray(module.permissions)) {
|
|
133
|
+
const uniquePermissions = new Set();
|
|
134
|
+
for (const permission of module.permissions) {
|
|
135
|
+
if (typeof permission !== 'string') {
|
|
136
|
+
issues.push({
|
|
137
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
138
|
+
message: `backendModules[${moduleLabel}].permissions contains non-string entry`,
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (DEPRECATED_PERMISSION_IDS.includes(permission)) {
|
|
143
|
+
// Older manifests still ship these. They never meaningfully gated
|
|
144
|
+
// anything inside the backend runtime (ambient Node access), and
|
|
145
|
+
// the catalog cleanup removed them. Warn the author and silently
|
|
146
|
+
// drop the id from the effective list.
|
|
147
|
+
issues.push({
|
|
148
|
+
severity: 'warning',
|
|
149
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
150
|
+
message: `backendModules[${moduleLabel}].permissions includes deprecated id "${permission}". ` +
|
|
151
|
+
`This id is unenforceable inside a Node backend (the module can require child_process/fs/net ` +
|
|
152
|
+
`directly) and has been removed from the catalog. Granting the module is itself the consent ` +
|
|
153
|
+
`to run native code; drop this id from your manifest.`,
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!KNOWN_PERMISSION_IDS.includes(permission)) {
|
|
158
|
+
issues.push({
|
|
159
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
160
|
+
message: `backendModules[${moduleLabel}].permissions contains unknown id "${permission}". ` +
|
|
161
|
+
`Valid ids: ${KNOWN_PERMISSION_IDS.join(', ')}`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (uniquePermissions.has(permission)) {
|
|
165
|
+
issues.push({
|
|
166
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
167
|
+
message: `backendModules[${moduleLabel}].permissions contains duplicate "${permission}"`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
uniquePermissions.add(permission);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const enablement = module.enablement;
|
|
174
|
+
if (!enablement || typeof enablement !== 'object') {
|
|
175
|
+
issues.push({
|
|
176
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
177
|
+
message: `backendModules[${moduleLabel}].enablement is required`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
if (enablement.default !== 'disabled') {
|
|
182
|
+
issues.push({
|
|
183
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
184
|
+
message: `backendModules[${moduleLabel}].enablement.default must be "disabled". ` +
|
|
185
|
+
'Privileged capabilities are always opt-in.',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (enablement.promptOn !== 'firstUse') {
|
|
189
|
+
issues.push({
|
|
190
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
191
|
+
message: `backendModules[${moduleLabel}].enablement.promptOn must be "firstUse"`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (typeof enablement.purpose !== 'string' || enablement.purpose.trim().length === 0) {
|
|
195
|
+
issues.push({
|
|
196
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
197
|
+
message: `backendModules[${moduleLabel}].enablement.purpose must be a non-empty string. ` +
|
|
198
|
+
'This is shown verbatim in the consent prompt - write it from the user\'s perspective.',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else if (enablement.purpose.length > 280) {
|
|
202
|
+
issues.push({
|
|
203
|
+
moduleId: typeof module.id === 'string' ? module.id : undefined,
|
|
204
|
+
message: `backendModules[${moduleLabel}].enablement.purpose is too long (${enablement.purpose.length} chars). ` +
|
|
205
|
+
'Keep it under 280 characters; long copy doesn\'t fit the consent prompt.',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return issues;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Convenience wrapper that throws if any fatal issues are found. Warnings
|
|
214
|
+
* (currently: deprecated permission ids) are surfaced via the issues array
|
|
215
|
+
* but never throw, so an extension that still references `spawn-process`
|
|
216
|
+
* keeps loading -- the host just drops the id when computing effective
|
|
217
|
+
* permissions.
|
|
218
|
+
*
|
|
219
|
+
* Use this in main-process load paths where invalid manifests should refuse
|
|
220
|
+
* the extension outright.
|
|
221
|
+
*/
|
|
222
|
+
export function assertBackendModulesValid(extensionId, backendModules) {
|
|
223
|
+
const issues = validateBackendModules(backendModules);
|
|
224
|
+
const fatal = issues.filter((i) => i.severity !== 'warning');
|
|
225
|
+
if (fatal.length === 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const lines = fatal.map((i) => i.moduleId ? ` - [${i.moduleId}] ${i.message}` : ` - ${i.message}`);
|
|
229
|
+
throw new Error(`Extension ${extensionId} has invalid backendModules declarations:\n${lines.join('\n')}`);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Filter a raw `permissions` array on a backend-module contribution down to
|
|
233
|
+
* the ids the host actually understands. Deprecated catalog ids
|
|
234
|
+
* (`spawn-process`, `network-loopback`, `network-internet`, `filesystem`)
|
|
235
|
+
* are dropped silently -- they never gated anything inside the backend
|
|
236
|
+
* runtime, so the host treats them as no-ops. Unknown ids are also dropped;
|
|
237
|
+
* `validateBackendModules` raises a separate error for those, so the loader
|
|
238
|
+
* will refuse the module before this is consulted in earnest.
|
|
239
|
+
*/
|
|
240
|
+
export function effectiveModulePermissions(raw) {
|
|
241
|
+
if (!Array.isArray(raw))
|
|
242
|
+
return [];
|
|
243
|
+
const out = [];
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
for (const entry of raw) {
|
|
246
|
+
if (typeof entry !== 'string')
|
|
247
|
+
continue;
|
|
248
|
+
if (seen.has(entry))
|
|
249
|
+
continue;
|
|
250
|
+
seen.add(entry);
|
|
251
|
+
if (KNOWN_PERMISSION_IDS.includes(entry)) {
|
|
252
|
+
out.push(entry);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Extract the set of declared backend-module ids from a manifest's
|
|
259
|
+
* `contributions.backendModules` array. Used by
|
|
260
|
+
* {@link validateAgentProviders} so the `backendModuleId` cross-check does
|
|
261
|
+
* not need the caller to pre-compute the set.
|
|
262
|
+
*
|
|
263
|
+
* Returns an empty set if `backendModules` is missing or malformed. Exported
|
|
264
|
+
* so build tooling can reuse the same extraction logic when checking entry
|
|
265
|
+
* files against the declared modules.
|
|
266
|
+
*/
|
|
267
|
+
export function extractBackendModuleIds(backendModules) {
|
|
268
|
+
const ids = new Set();
|
|
269
|
+
if (!Array.isArray(backendModules))
|
|
270
|
+
return ids;
|
|
271
|
+
for (const raw of backendModules) {
|
|
272
|
+
if (!raw || typeof raw !== 'object')
|
|
273
|
+
continue;
|
|
274
|
+
const id = raw.id;
|
|
275
|
+
if (typeof id === 'string' && id.length > 0) {
|
|
276
|
+
ids.add(id);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return ids;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate the `contributions.aiAgentProviders` array on a manifest.
|
|
283
|
+
*
|
|
284
|
+
* AI agent providers are surfaced in Nimbalyst's chat / agent UI as selectable
|
|
285
|
+
* providers (alongside built-in Claude Code, Claude, OpenAI, etc.). Each
|
|
286
|
+
* provider is implemented by a backend module that runs outside the renderer
|
|
287
|
+
* (so it can drive a CLI, hold long-lived processes, or speak a custom
|
|
288
|
+
* protocol). The contribution shape, in practice:
|
|
289
|
+
*
|
|
290
|
+
* {
|
|
291
|
+
* id: string, // unique within the extension
|
|
292
|
+
* displayName: string, // shown in the provider dropdown
|
|
293
|
+
* backendModuleId: string, // must match a declared backendModule
|
|
294
|
+
* models?: Array<{ id, name, ... }> // models the provider advertises
|
|
295
|
+
* toolFileLinks?: { [toolName]: ... } // optional UI hint table
|
|
296
|
+
* }
|
|
297
|
+
*
|
|
298
|
+
* This validator checks the manifest-side wiring; it does not introspect the
|
|
299
|
+
* bundle. See {@link validateExtensionBundle} (Node-only) for the bundle-side
|
|
300
|
+
* check that the referenced backend module actually exports an
|
|
301
|
+
* `activate`/`createAgentProvider` function.
|
|
302
|
+
*
|
|
303
|
+
* Returns the list of problems found - empty means valid.
|
|
304
|
+
*/
|
|
305
|
+
export function validateAgentProviders(aiAgentProviders, backendModules) {
|
|
306
|
+
if (aiAgentProviders === undefined) {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
if (!Array.isArray(aiAgentProviders)) {
|
|
310
|
+
return [
|
|
311
|
+
{ message: 'contributions.aiAgentProviders must be an array' },
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
const issues = [];
|
|
315
|
+
if (aiAgentProviders.length > MAX_AGENT_PROVIDERS_PER_EXTENSION) {
|
|
316
|
+
issues.push({
|
|
317
|
+
message: `contributions.aiAgentProviders declares ${aiAgentProviders.length} providers; ` +
|
|
318
|
+
`the maximum is ${MAX_AGENT_PROVIDERS_PER_EXTENSION}. ` +
|
|
319
|
+
'Split into multiple extensions, or surface variants as models on a single provider.',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
const knownBackendModuleIds = extractBackendModuleIds(backendModules);
|
|
323
|
+
const seenProviderIds = new Set();
|
|
324
|
+
for (let i = 0; i < aiAgentProviders.length; i += 1) {
|
|
325
|
+
const raw = aiAgentProviders[i];
|
|
326
|
+
if (!raw || typeof raw !== 'object') {
|
|
327
|
+
issues.push({
|
|
328
|
+
message: `aiAgentProviders[${i}] must be an object`,
|
|
329
|
+
});
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const provider = raw;
|
|
333
|
+
const providerLabel = typeof provider.id === 'string' && provider.id.length > 0
|
|
334
|
+
? provider.id
|
|
335
|
+
: `index ${i}`;
|
|
336
|
+
// (b) id presence + shape + uniqueness within the extension.
|
|
337
|
+
if (typeof provider.id !== 'string' ||
|
|
338
|
+
!AGENT_PROVIDER_ID_PATTERN.test(provider.id)) {
|
|
339
|
+
issues.push({
|
|
340
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
341
|
+
message: `aiAgentProviders[${providerLabel}].id must be a lowercase string matching ` +
|
|
342
|
+
`${AGENT_PROVIDER_ID_PATTERN.source} (got ${JSON.stringify(provider.id)})`,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else if (seenProviderIds.has(provider.id)) {
|
|
346
|
+
issues.push({
|
|
347
|
+
providerId: provider.id,
|
|
348
|
+
message: `aiAgentProviders contains duplicate id "${provider.id}"`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
seenProviderIds.add(provider.id);
|
|
353
|
+
}
|
|
354
|
+
// displayName: non-empty string. Surfaced verbatim in the provider
|
|
355
|
+
// dropdown, so it has to be readable; not currently length-capped because
|
|
356
|
+
// we have no signal yet on what the UI tolerates. Matches the
|
|
357
|
+
// AiAgentProviderContribution type, which names this field `displayName`.
|
|
358
|
+
if (typeof provider.displayName !== 'string' || provider.displayName.trim().length === 0) {
|
|
359
|
+
issues.push({
|
|
360
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
361
|
+
message: `aiAgentProviders[${providerLabel}].displayName must be a non-empty string ` +
|
|
362
|
+
'(shown verbatim in the provider dropdown).',
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// (a) backendModuleId must reference a real backendModules[*].id.
|
|
366
|
+
if (typeof provider.backendModuleId !== 'string' ||
|
|
367
|
+
provider.backendModuleId.length === 0) {
|
|
368
|
+
issues.push({
|
|
369
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
370
|
+
message: `aiAgentProviders[${providerLabel}].backendModuleId must be a non-empty string ` +
|
|
371
|
+
'referencing a backendModules[*].id declared on the same manifest.',
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (!knownBackendModuleIds.has(provider.backendModuleId)) {
|
|
375
|
+
issues.push({
|
|
376
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
377
|
+
message: `aiAgentProviders[${providerLabel}].backendModuleId "${provider.backendModuleId}" ` +
|
|
378
|
+
'does not match any contributions.backendModules[*].id. ' +
|
|
379
|
+
'Agent providers must be implemented by a declared backend module on the same extension.',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
// (c) models, if present, must be an array of { id, name }. These are
|
|
383
|
+
// the only required fields on the AiAgentProviderModel type; the host
|
|
384
|
+
// derives the provider from the contribution id, so models carry no
|
|
385
|
+
// per-entry provider field.
|
|
386
|
+
if (provider.models !== undefined) {
|
|
387
|
+
if (!Array.isArray(provider.models)) {
|
|
388
|
+
issues.push({
|
|
389
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
390
|
+
message: `aiAgentProviders[${providerLabel}].models must be an array (or omitted).`,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const seenModelIds = new Set();
|
|
395
|
+
for (let m = 0; m < provider.models.length; m += 1) {
|
|
396
|
+
const rawModel = provider.models[m];
|
|
397
|
+
const modelLabel = `models[${m}]`;
|
|
398
|
+
if (!rawModel || typeof rawModel !== 'object') {
|
|
399
|
+
issues.push({
|
|
400
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
401
|
+
message: `aiAgentProviders[${providerLabel}].${modelLabel} must be an object`,
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const model = rawModel;
|
|
406
|
+
if (typeof model.id !== 'string' ||
|
|
407
|
+
model.id.trim().length === 0) {
|
|
408
|
+
issues.push({
|
|
409
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
410
|
+
message: `aiAgentProviders[${providerLabel}].${modelLabel}.id must be a non-empty string`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
else if (seenModelIds.has(model.id)) {
|
|
414
|
+
issues.push({
|
|
415
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
416
|
+
message: `aiAgentProviders[${providerLabel}].models contains duplicate id "${model.id}"`,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
seenModelIds.add(model.id);
|
|
421
|
+
}
|
|
422
|
+
if (typeof model.name !== 'string' ||
|
|
423
|
+
model.name.trim().length === 0) {
|
|
424
|
+
issues.push({
|
|
425
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
426
|
+
message: `aiAgentProviders[${providerLabel}].${modelLabel}.name must be a non-empty string`,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// (d) toolFileLinks, if present, must be a plain object whose keys are
|
|
433
|
+
// non-empty strings (tool names). We deliberately do NOT validate the
|
|
434
|
+
// values: the host-side schema for the link payload is still in flux,
|
|
435
|
+
// and rejecting unknown value shapes here would force every shape change
|
|
436
|
+
// through this SDK file. Keys are the only contract this validator
|
|
437
|
+
// pins.
|
|
438
|
+
if (provider.toolFileLinks !== undefined) {
|
|
439
|
+
if (typeof provider.toolFileLinks !== 'object' ||
|
|
440
|
+
provider.toolFileLinks === null ||
|
|
441
|
+
Array.isArray(provider.toolFileLinks)) {
|
|
442
|
+
issues.push({
|
|
443
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
444
|
+
message: `aiAgentProviders[${providerLabel}].toolFileLinks must be a plain object ` +
|
|
445
|
+
'mapping tool names to link descriptors (or omitted).',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
for (const key of Object.keys(provider.toolFileLinks)) {
|
|
450
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
451
|
+
issues.push({
|
|
452
|
+
providerId: typeof provider.id === 'string' ? provider.id : undefined,
|
|
453
|
+
message: `aiAgentProviders[${providerLabel}].toolFileLinks contains an empty/invalid tool name key`,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return issues;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Convenience wrapper that throws if any fatal aiAgentProviders issues are
|
|
464
|
+
* found. Mirrors {@link assertBackendModulesValid}. Use this in main-process
|
|
465
|
+
* load paths where invalid manifests should refuse the extension outright.
|
|
466
|
+
*/
|
|
467
|
+
export function assertAgentProvidersValid(extensionId, aiAgentProviders, backendModules) {
|
|
468
|
+
const issues = validateAgentProviders(aiAgentProviders, backendModules);
|
|
469
|
+
const fatal = issues.filter((i) => i.severity !== 'warning');
|
|
470
|
+
if (fatal.length === 0) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const lines = fatal.map((i) => i.providerId ? ` - [${i.providerId}] ${i.message}` : ` - ${i.message}`);
|
|
474
|
+
throw new Error(`Extension ${extensionId} has invalid aiAgentProviders declarations:\n${lines.join('\n')}`);
|
|
475
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
export interface ResolvedTrackerReference {
|
|
3
|
+
id: string;
|
|
4
|
+
issueKey?: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
priority?: string;
|
|
9
|
+
owner?: string;
|
|
10
|
+
updatedAt?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TrackerReferenceChipProps {
|
|
13
|
+
referenceKey: string;
|
|
14
|
+
nodeKey?: string;
|
|
15
|
+
variant?: 'default' | 'compact';
|
|
16
|
+
}
|
|
17
|
+
export interface TrackerReferencePickerProps {
|
|
18
|
+
value: readonly string[];
|
|
19
|
+
onChange(value: string[]): void;
|
|
20
|
+
multiple?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
maxResults?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Host-provided live tracker reference chip. */
|
|
27
|
+
export declare const TrackerReferenceChip: ComponentType<TrackerReferenceChipProps>;
|
|
28
|
+
/** Host-provided canonical tracker search and selection UI. */
|
|
29
|
+
export declare const TrackerReferencePicker: ComponentType<TrackerReferencePickerProps>;
|
|
30
|
+
export declare const useResolvedTrackerReference: (referenceKey: string) => ResolvedTrackerReference | null;
|
|
31
|
+
export declare const navigateToTrackerReference: (reference: ResolvedTrackerReference) => void;
|
|
32
|
+
//# sourceMappingURL=trackerReferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackerReferences.d.ts","sourceRoot":"","sources":["../src/trackerReferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAQ3C,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,iDAAiD;AACjD,eAAO,MAAM,oBAAoB,EAA+B,aAAa,CAAC,yBAAyB,CAAC,CAAC;AAEzG,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAiC,aAAa,CAAC,2BAA2B,CAAC,CAAC;AAE/G,eAAO,MAAM,2BAA2B,EAAsC,CAC5E,YAAY,EAAE,MAAM,KACjB,wBAAwB,GAAG,IAAI,CAAC;AAErC,eAAO,MAAM,0BAA0B,EAAqC,CAC1E,SAAS,EAAE,wBAAwB,KAChC,IAAI,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TrackerReferenceChip as HostTrackerReferenceChip, TrackerReferencePicker as HostTrackerReferencePicker, navigateToTrackerReference as hostNavigateToTrackerReference, useResolvedTrackerReference as hostUseResolvedTrackerReference, } from '@nimbalyst/runtime';
|
|
2
|
+
/** Host-provided live tracker reference chip. */
|
|
3
|
+
export const TrackerReferenceChip = HostTrackerReferenceChip;
|
|
4
|
+
/** Host-provided canonical tracker search and selection UI. */
|
|
5
|
+
export const TrackerReferencePicker = HostTrackerReferencePicker;
|
|
6
|
+
export const useResolvedTrackerReference = hostUseResolvedTrackerReference;
|
|
7
|
+
export const navigateToTrackerReference = hostNavigateToTrackerReference;
|