@neuralis/brain-core 0.3.2 → 0.4.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/README.md +79 -1409
- package/app/README.md +1 -1
- package/app/cards/FilesystemCardShell.tsx +42 -4
- package/app/cards/FilesystemChangeCard.tsx +34 -11
- package/app/cards/README.md +6 -5
- package/app/cards/diffUtils.ts +99 -1
- package/app/filesystem/FilesystemHost.tsx +17 -2
- package/app/filesystem/api/README.md +3 -3
- package/app/filesystem/api/index.ts +35 -11
- package/app/filesystem/api/projectPackages.ts +13 -102
- package/app/filesystem/domain/types.ts +30 -10
- package/app/filesystem/hooks/README.md +3 -3
- package/app/filesystem/hooks/useDiscoverableSources.ts +19 -30
- package/app/filesystem/hooks/useFilesystemSSE.ts +1 -1
- package/app/filesystem/host/README.md +5 -3
- package/app/filesystem/host/buildFilesystemHostValue.ts +19 -0
- package/app/filesystem/host/installFilesystemHostComponents.tsx +1 -1
- package/app/filesystem/lib/sourceKindForm.ts +118 -39
- package/app/filesystem/lib/treeVisualState.tsx +98 -4
- package/app/filesystem/store/README.md +1 -0
- package/app/filesystem/store/filesystemStore.ts +86 -5
- package/app/filesystem/store/gitSlice.ts +5 -0
- package/app/filesystem/store/helpers.ts +17 -0
- package/app/filesystem/store/listSlice.ts +8 -0
- package/app/filesystem/ui/CodeEditor.tsx +18 -3
- package/app/filesystem/ui/FileEditor.tsx +15 -10
- package/app/filesystem/ui/FileTree.tsx +41 -16
- package/app/filesystem/ui/FolderPicker.tsx +251 -0
- package/app/filesystem/ui/GitControlPanel.tsx +5 -0
- package/app/filesystem/ui/PackagesPanel.tsx +52 -374
- package/app/filesystem/ui/PolicyEditor.tsx +109 -24
- package/app/filesystem/ui/README.md +17 -11
- package/app/filesystem/ui/RawMediaPreview.tsx +58 -0
- package/app/filesystem/ui/SourceBranchHeader.tsx +4 -4
- package/app/filesystem/ui/SourcesPanel.tsx +420 -360
- package/app/filesystem/ui/Toolbar.tsx +1 -2
- package/app/shared/filesystem.png +0 -0
- package/dist/app/host.js +57 -49
- package/dist/scripts/sourceRootPairV1.d.ts +47 -0
- package/dist/scripts/sourceRootPairV1.d.ts.map +1 -0
- package/dist/scripts/sourceRootPairV1.js +147 -0
- package/dist/scripts/sourceRootPairV1.js.map +1 -0
- package/dist/scripts/sourceToFolderV1.d.ts +56 -0
- package/dist/scripts/sourceToFolderV1.d.ts.map +1 -0
- package/dist/scripts/sourceToFolderV1.js +152 -0
- package/dist/scripts/sourceToFolderV1.js.map +1 -0
- package/dist/scripts/sourceToHostV1.d.ts +56 -0
- package/dist/scripts/sourceToHostV1.d.ts.map +1 -0
- package/dist/scripts/sourceToHostV1.js +132 -0
- package/dist/scripts/sourceToHostV1.js.map +1 -0
- package/dist/src/connectors/AppSourceConfigStore.d.ts +17 -3
- package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
- package/dist/src/connectors/AppSourceConfigStore.js +46 -6
- package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
- package/dist/src/connectors/HostConnector.d.ts +201 -0
- package/dist/src/connectors/HostConnector.d.ts.map +1 -0
- package/dist/src/connectors/HostConnector.js +903 -0
- package/dist/src/connectors/HostConnector.js.map +1 -0
- package/dist/src/connectors/inProcessTransport.d.ts +131 -0
- package/dist/src/connectors/inProcessTransport.d.ts.map +1 -0
- package/dist/src/connectors/inProcessTransport.js +447 -0
- package/dist/src/connectors/inProcessTransport.js.map +1 -0
- package/dist/src/domain/access/callerPermissions.d.ts +29 -0
- package/dist/src/domain/access/callerPermissions.d.ts.map +1 -1
- package/dist/src/domain/access/callerPermissions.js +85 -5
- package/dist/src/domain/access/callerPermissions.js.map +1 -1
- package/dist/src/domain/access/nativeLocalFence.d.ts +65 -0
- package/dist/src/domain/access/nativeLocalFence.d.ts.map +1 -0
- package/dist/src/domain/access/nativeLocalFence.js +171 -0
- package/dist/src/domain/access/nativeLocalFence.js.map +1 -0
- package/dist/src/domain/access/uriPermission.js +3 -3
- package/dist/src/domain/access/uriPermission.js.map +1 -1
- package/dist/src/domain/byteRange.d.ts +15 -0
- package/dist/src/domain/byteRange.d.ts.map +1 -0
- package/dist/src/domain/byteRange.js +30 -0
- package/dist/src/domain/byteRange.js.map +1 -0
- package/dist/src/domain/defaultPermissions.d.ts.map +1 -1
- package/dist/src/domain/defaultPermissions.js +29 -0
- package/dist/src/domain/defaultPermissions.js.map +1 -1
- package/dist/src/domain/mime.d.ts +8 -16
- package/dist/src/domain/mime.d.ts.map +1 -1
- package/dist/src/domain/mime.js +10 -99
- package/dist/src/domain/mime.js.map +1 -1
- package/dist/src/domain/paths.d.ts.map +1 -1
- package/dist/src/domain/paths.js +5 -3
- package/dist/src/domain/paths.js.map +1 -1
- package/dist/src/domain/sourceConfig.d.ts +52 -19
- package/dist/src/domain/sourceConfig.d.ts.map +1 -1
- package/dist/src/domain/sourceConfig.js +103 -19
- package/dist/src/domain/sourceConfig.js.map +1 -1
- package/dist/src/domain/sourcePackages.d.ts +8 -8
- package/dist/src/domain/sourcePackages.d.ts.map +1 -1
- package/dist/src/domain/sourceRootPair.d.ts +88 -0
- package/dist/src/domain/sourceRootPair.d.ts.map +1 -0
- package/dist/src/domain/sourceRootPair.js +108 -0
- package/dist/src/domain/sourceRootPair.js.map +1 -0
- package/dist/src/domain/syncTypes.d.ts +8 -1
- package/dist/src/domain/syncTypes.d.ts.map +1 -1
- package/dist/src/domain/syncTypes.js +1 -0
- package/dist/src/domain/syncTypes.js.map +1 -1
- package/dist/src/domain/types.d.ts +24 -1
- package/dist/src/domain/types.d.ts.map +1 -1
- package/dist/src/domain/uri.d.ts.map +1 -1
- package/dist/src/domain/uri.js +3 -2
- package/dist/src/domain/uri.js.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.js +2 -5
- package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.js +2 -3
- package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.d.ts +2 -0
- package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.js +10 -2
- package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.d.ts +2 -0
- package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.js +8 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
- package/dist/src/embeddings/factory.d.ts +5 -3
- package/dist/src/embeddings/factory.d.ts.map +1 -1
- package/dist/src/embeddings/factory.js +7 -3
- package/dist/src/embeddings/factory.js.map +1 -1
- package/dist/src/embeddings/models.d.ts +1 -1
- package/dist/src/embeddings/models.d.ts.map +1 -1
- package/dist/src/embeddings/retry.js +1 -1
- package/dist/src/embeddings/retry.js.map +1 -1
- package/dist/src/embeddings/types.d.ts +6 -0
- package/dist/src/embeddings/types.d.ts.map +1 -1
- package/dist/src/embeddings/types.js +11 -0
- package/dist/src/embeddings/types.js.map +1 -1
- package/dist/src/git/GitRunner.d.ts +110 -30
- package/dist/src/git/GitRunner.d.ts.map +1 -1
- package/dist/src/git/GitRunner.js +511 -199
- package/dist/src/git/GitRunner.js.map +1 -1
- package/dist/src/git/GitWatchRegistry.d.ts +41 -35
- package/dist/src/git/GitWatchRegistry.d.ts.map +1 -1
- package/dist/src/git/GitWatchRegistry.js +61 -79
- package/dist/src/git/GitWatchRegistry.js.map +1 -1
- package/dist/src/git/gitPlace.d.ts +61 -0
- package/dist/src/git/gitPlace.d.ts.map +1 -0
- package/dist/src/git/gitPlace.js +74 -0
- package/dist/src/git/gitPlace.js.map +1 -0
- package/dist/src/git/sourceGitControl.d.ts +31 -19
- package/dist/src/git/sourceGitControl.d.ts.map +1 -1
- package/dist/src/git/sourceGitControl.js +208 -92
- package/dist/src/git/sourceGitControl.js.map +1 -1
- package/dist/src/git/sourceGitStatus.js +1 -1
- package/dist/src/git/sourceGitStatus.js.map +1 -1
- package/dist/src/git/untrackedStats.js +1 -1
- package/dist/src/git/untrackedStats.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lifecycle.d.ts +8 -11
- package/dist/src/lifecycle.d.ts.map +1 -1
- package/dist/src/lifecycle.js +2 -1
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/routes/git.d.ts.map +1 -1
- package/dist/src/routes/git.js +183 -54
- package/dist/src/routes/git.js.map +1 -1
- package/dist/src/routes/helpers/uriGuard.d.ts +1 -1
- package/dist/src/routes/helpers/uriGuard.d.ts.map +1 -1
- package/dist/src/routes/helpers/uriGuard.js +3 -2
- package/dist/src/routes/helpers/uriGuard.js.map +1 -1
- package/dist/src/routes/helpers.d.ts +10 -3
- package/dist/src/routes/helpers.d.ts.map +1 -1
- package/dist/src/routes/helpers.js +12 -5
- package/dist/src/routes/helpers.js.map +1 -1
- package/dist/src/routes/list.d.ts.map +1 -1
- package/dist/src/routes/list.js +2 -3
- package/dist/src/routes/list.js.map +1 -1
- package/dist/src/routes/policies.d.ts +6 -4
- package/dist/src/routes/policies.d.ts.map +1 -1
- package/dist/src/routes/policies.js +22 -16
- package/dist/src/routes/policies.js.map +1 -1
- package/dist/src/routes/raw.d.ts.map +1 -1
- package/dist/src/routes/raw.js +65 -11
- package/dist/src/routes/raw.js.map +1 -1
- package/dist/src/routes/sources.d.ts +60 -18
- package/dist/src/routes/sources.d.ts.map +1 -1
- package/dist/src/routes/sources.js +411 -299
- package/dist/src/routes/sources.js.map +1 -1
- package/dist/src/services/BridgeService.d.ts +15 -35
- package/dist/src/services/BridgeService.d.ts.map +1 -1
- package/dist/src/services/BridgeService.js +30 -88
- package/dist/src/services/BridgeService.js.map +1 -1
- package/dist/src/services/FilesystemService.d.ts +3 -2
- package/dist/src/services/FilesystemService.d.ts.map +1 -1
- package/dist/src/services/FilesystemService.js +110 -32
- package/dist/src/services/FilesystemService.js.map +1 -1
- package/dist/src/services/FilesystemUiService.d.ts +4 -1
- package/dist/src/services/FilesystemUiService.d.ts.map +1 -1
- package/dist/src/services/FilesystemUiService.js +66 -25
- package/dist/src/services/FilesystemUiService.js.map +1 -1
- package/dist/src/services/SyncScheduler.d.ts +12 -4
- package/dist/src/services/SyncScheduler.d.ts.map +1 -1
- package/dist/src/services/SyncScheduler.js +37 -8
- package/dist/src/services/SyncScheduler.js.map +1 -1
- package/dist/src/services/baselineRepair.d.ts +13 -3
- package/dist/src/services/baselineRepair.d.ts.map +1 -1
- package/dist/src/services/baselineRepair.js +13 -3
- package/dist/src/services/baselineRepair.js.map +1 -1
- package/dist/src/services/declaredSources.d.ts +125 -42
- package/dist/src/services/declaredSources.d.ts.map +1 -1
- package/dist/src/services/declaredSources.js +117 -45
- package/dist/src/services/declaredSources.js.map +1 -1
- package/dist/src/store/dataFormats.d.ts +17 -2
- package/dist/src/store/dataFormats.d.ts.map +1 -1
- package/dist/src/store/dataFormats.js +18 -3
- package/dist/src/store/dataFormats.js.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.js +144 -4
- package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
- package/dist/src/sync/SyncJobQueue.d.ts +7 -2
- package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
- package/dist/src/sync/SyncJobQueue.js +16 -10
- package/dist/src/sync/SyncJobQueue.js.map +1 -1
- package/dist/src/sync/SyncWatchService.d.ts +21 -4
- package/dist/src/sync/SyncWatchService.d.ts.map +1 -1
- package/dist/src/sync/SyncWatchService.js +115 -10
- package/dist/src/sync/SyncWatchService.js.map +1 -1
- package/dist/src/sync/categoryInference.d.ts +1 -1
- package/dist/src/sync/categoryInference.js +1 -1
- package/dist/src/usecases/admitSourceRoot.d.ts +65 -0
- package/dist/src/usecases/admitSourceRoot.d.ts.map +1 -0
- package/dist/src/usecases/admitSourceRoot.js +85 -0
- package/dist/src/usecases/admitSourceRoot.js.map +1 -0
- package/dist/src/usecases/browseSourceFolders.d.ts +85 -0
- package/dist/src/usecases/browseSourceFolders.d.ts.map +1 -0
- package/dist/src/usecases/browseSourceFolders.js +210 -0
- package/dist/src/usecases/browseSourceFolders.js.map +1 -0
- package/dist/src/usecases/createDeclaredSources.d.ts +13 -2
- package/dist/src/usecases/createDeclaredSources.d.ts.map +1 -1
- package/dist/src/usecases/createDeclaredSources.js +39 -0
- package/dist/src/usecases/createDeclaredSources.js.map +1 -1
- package/dist/src/usecases/list.d.ts.map +1 -1
- package/dist/src/usecases/list.js +83 -26
- package/dist/src/usecases/list.js.map +1 -1
- package/dist/src/usecases/listAttachableDevices.d.ts +13 -0
- package/dist/src/usecases/listAttachableDevices.d.ts.map +1 -0
- package/dist/src/usecases/listAttachableDevices.js +10 -0
- package/dist/src/usecases/listAttachableDevices.js.map +1 -0
- package/dist/src/usecases/listSourcePackages.d.ts.map +1 -1
- package/dist/src/usecases/listSourcePackages.js +2 -5
- package/dist/src/usecases/listSourcePackages.js.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.js +3 -2
- package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
- package/dist/src/usecases/readFiles.d.ts.map +1 -1
- package/dist/src/usecases/readFiles.js +15 -0
- package/dist/src/usecases/readFiles.js.map +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.js +2 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -1
- package/dist/src/usecases/revokePrincipal.d.ts +7 -5
- package/dist/src/usecases/revokePrincipal.d.ts.map +1 -1
- package/dist/src/usecases/revokePrincipal.js +29 -7
- package/dist/src/usecases/revokePrincipal.js.map +1 -1
- package/dist/src/usecases/scheduleSourceSync.d.ts +7 -0
- package/dist/src/usecases/scheduleSourceSync.d.ts.map +1 -1
- package/dist/src/usecases/scheduleSourceSync.js +6 -5
- package/dist/src/usecases/scheduleSourceSync.js.map +1 -1
- package/dist/src/usecases/triggerSourceSync.d.ts +46 -0
- package/dist/src/usecases/triggerSourceSync.d.ts.map +1 -0
- package/dist/src/usecases/triggerSourceSync.js +75 -0
- package/dist/src/usecases/triggerSourceSync.js.map +1 -0
- package/dist/src/usecases/uploadBinary.d.ts +2 -1
- package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
- package/dist/src/usecases/uploadBinary.js +2 -2
- package/dist/src/usecases/uploadBinary.js.map +1 -1
- package/dist/src/usecases/vector/endpointGuard.d.ts +1 -1
- package/dist/src/usecases/vector/endpointGuard.js +1 -1
- package/dist/src/vector/qdrantClient.d.ts +1 -1
- package/dist/src/vector/qdrantClient.js +1 -1
- package/dist/src/wiring.d.ts +3 -0
- package/dist/src/wiring.d.ts.map +1 -1
- package/dist/src/wiring.js +72 -6
- package/dist/src/wiring.js.map +1 -1
- package/package.json +145 -54
- package/skills/connect-business-data/SKILL.md +2 -2
- package/skills/create-package/SKILL.md +18 -18
- package/skills/create-package/references/config-and-credentials.md +16 -13
- package/skills/create-package/references/connectors-and-uri-policies.md +36 -27
- package/skills/create-package/references/manifest-and-discovery.md +12 -12
- package/skills/create-package/references/runtime-and-build.md +18 -18
- package/skills/create-package/references/skills-and-context.md +35 -19
- package/skills/create-package/references/surfaces-and-design.md +16 -16
- package/skills/create-package/references/team-and-workflows.md +2 -2
- package/skills/create-package/references/test-and-ship.md +17 -17
- package/skills/create-package/references/tools-and-schemas.md +5 -5
- package/skills/create-package/scripts/{check-package.sh → check-package.mjs} +78 -64
- package/skills/create-package/scripts/{verify-loaded.sh → verify-loaded.mjs} +156 -162
- package/skills/git/SKILL.md +31 -29
- package/skills/manage-files/SKILL.md +11 -14
- package/skills/manage-sources/SKILL.md +85 -45
- package/skills/manage-uri-policy/SKILL.md +9 -6
- package/skills/sync/SKILL.md +1 -1
- package/dist/src/connectors/LocalDiskConnector.d.ts +0 -171
- package/dist/src/connectors/LocalDiskConnector.d.ts.map +0 -1
- package/dist/src/connectors/LocalDiskConnector.js +0 -1233
- package/dist/src/connectors/LocalDiskConnector.js.map +0 -1
- package/skills/manage-files/scripts/copy.sh +0 -7
- package/skills/manage-files/scripts/create.sh +0 -7
- package/skills/manage-files/scripts/mkdir.sh +0 -7
- package/skills/manage-files/scripts/modify.sh +0 -7
- package/skills/manage-files/scripts/upload.sh +0 -16
- package/skills/manage-files/scripts/write.sh +0 -29
- package/skills/manage-sources/scripts/reembed.sh +0 -23
|
@@ -61,10 +61,10 @@ arguments:
|
|
|
61
61
|
Target environment: {{environment}}
|
|
62
62
|
|
|
63
63
|
1. Run the checks — call this with `cwd: "${SKILL_DIR}"`:
|
|
64
|
-
`
|
|
64
|
+
`node "${SKILL_DIR}/scripts/verify.mjs" {{environment}}`
|
|
65
65
|
2. Read `${SKILL_DIR}/references/runbook.md` for the manual steps.
|
|
66
66
|
|
|
67
|
-
Done when `verify.
|
|
67
|
+
Done when `verify.mjs` exited 0 and every runbook step is finished or explicitly reported as skipped.
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
No registration call, no index file, no manifest entry: discovery finds the `SKILL.md`, sets `category: skill` from the folder, derives the id, enumerates the bundle, and turns `requiredFeatures` into the visibility gate.
|
|
@@ -115,18 +115,26 @@ lines and move detail into `references/`, one level deep, each with its own trig
|
|
|
115
115
|
## Ship scripts with a skill
|
|
116
116
|
|
|
117
117
|
There is **no separate script runner**: scripts run through the ordinary shell —
|
|
118
|
-
`execute action="shell" command="
|
|
118
|
+
`execute action="shell" command="node scripts/verify.mjs production"` at the skill's own working
|
|
119
119
|
directory, which is also what selects the execution provider (a source slug is not a kind).
|
|
120
120
|
|
|
121
|
+
**Write a script that must run everywhere in Node (`.mjs`).** The same skill runs under bash on
|
|
122
|
+
Linux, macOS and the Docker app and under PowerShell on a Windows device, which has no bash; `node`
|
|
123
|
+
is present wherever `neuralis request` is, because the CLI is a Node program. A bash script is a
|
|
124
|
+
POSIX-only script — on a Windows device it needs Git for Windows. Quote the path
|
|
125
|
+
(`node "${SKILL_DIR}/scripts/x.mjs"`, a Windows path may hold spaces), and pass JSON and free text
|
|
126
|
+
on stdin or through the `execute` tool's `env`, never as an argument: PowerShell 5.1 strips the
|
|
127
|
+
quotes inside one.
|
|
128
|
+
|
|
121
129
|
**Always pass the emitted working directory** — normally `cwd: "${SKILL_DIR}"`, or, for a
|
|
122
130
|
connector-backed skill, the directory URI itself (`cwd: "<source>://bundle/skills/check"`), never
|
|
123
|
-
that URI rewritten as an
|
|
131
|
+
that URI rewritten as an absolute path. The default working directory is the project data
|
|
124
132
|
root, deliberately, so relative output does not land in the package tree: a body saying
|
|
125
|
-
`
|
|
133
|
+
`node scripts/verify.mjs` with no `cwd` produces a `Cannot find module` error.
|
|
126
134
|
|
|
127
135
|
Body substitutions: `${SKILL_DIR}`, `${SKILL_URI}`, `${SKILL_CONNECTOR}`, `${SKILL_NAME}`, `${SESSION_ID}`, `${NEURALIS_API}`, `${SKILL_CREDENTIALS_RESOLVED}`, `${SKILL_CREDENTIALS_MISSING}`, `${MISSING:<credential-id>}` (renders `(present: id)` / `(missing: id)`).
|
|
128
136
|
|
|
129
|
-
- Syntax is `${UPPER_SNAKE}` **only**. `{SKILL_DIR}` and `$SKILL_DIR` are not substitutions — they are left intact by design, the model passes the literal text to
|
|
137
|
+
- Syntax is `${UPPER_SNAKE}` **only**. `{SKILL_DIR}` and `$SKILL_DIR` are not substitutions — they are left intact by design, the model passes the literal text to the shell, the shell expands it to nothing, and your script path becomes `/scripts/verify.mjs`.
|
|
130
138
|
- **Argument injection is a different syntax and runs first**: `$ARGUMENTS`, `$1`…`$N` (positional, in args-object order), `{{name}}`. Only string values substitute, and `required: true` is **not** enforced — a missing argument renders empty and the model proceeds with a half-rendered body. `$ARGUMENTS` on a no-argument activation expands to the empty string — the agentskills.io/Claude Code semantic, kept so imported skills behave here as they do at home. Write the defensive shape yourself when the empty case matters ("if the line below is empty, do X"); a bare `$N` with nothing behind it stays LITERAL (dollar amounts in prose survive).
|
|
131
139
|
- Scripts must be non-interactive, answer `--help`, and bound their output. For app/host execution, an active skill’s local `SKILL_DIR` subtree is a read and cwd allowance in the shell path gate — not writable, and the only implicit path grant activation confers. Connector execution retains its provider’s gates.
|
|
132
140
|
|
|
@@ -138,10 +146,18 @@ execution: the activation envelope can supply a directory URI for an exec-capabl
|
|
|
138
146
|
that URI with a relative script, and never pass the sentinel as `cwd`.
|
|
139
147
|
|
|
140
148
|
**Authenticating back to the platform from app/host execution:** call package routes with
|
|
141
|
-
`neuralis request <METHOD> <package-id>/<route>` (`-f key=value` fields
|
|
142
|
-
body). It authenticates with the shell's per-stream `NEURALIS_SESSION_TOKEN` — the caller’s
|
|
149
|
+
`neuralis request <METHOD> <package-id>/<route>` (`-f key=value` fields; a JSON
|
|
150
|
+
body as `echo '<json>' | neuralis request … --input -`, which bash and PowerShell both parse). It authenticates with the shell's per-stream `NEURALIS_SESSION_TOKEN` — the caller’s
|
|
143
151
|
real identity, re-minted per call — and on a developer's own machine with their `neuralis login`;
|
|
144
|
-
your script never handles the ticket itself.
|
|
152
|
+
your script never handles the ticket itself. A Node script that makes several calls starts the
|
|
153
|
+
CLI with no shell: `execFileSync(process.execPath, [process.env.NEURALIS_CLI_ENTRY, 'request', 'GET',
|
|
154
|
+
'<package-id>/<route>'], { encoding: 'utf8' })` — every Neuralis runner exports `NEURALIS_CLI_ENTRY`,
|
|
155
|
+
the CLI's own JS entry. With it absent, spawn `neuralis` directly on Linux and macOS and stop with a
|
|
156
|
+
message naming the variable on Windows; never pass `shell: true` (cmd.exe re-reads every argument).
|
|
157
|
+
A script that only reshapes ONE answer reads it from a pipe instead —
|
|
158
|
+
`neuralis request GET <package-id>/<route> | node "${SKILL_DIR}/scripts/x.mjs"` — and must exit non-zero on an empty
|
|
159
|
+
or non-JSON stdin: a non-2xx leaves stdout empty, and neither shell reports the first stage's exit
|
|
160
|
+
code. It reaches ONLY package routes under
|
|
145
161
|
`/api/packages/<package-id>/…`; host-level `/api/*` routes (projects, auth, admin) reject it by design. **Never invent a bearer token and never ask the user for one.** `credentials:` is for **external** secrets only — each entry is both the credential-store id and the env-var name projected into the child process. A miss is non-fatal: it renders `(missing: <id>)` and is reported in the activation result, and the model never sees a value. **Credentials pushed by an activation are scoped to the conversation and the caller, and outlive the turn**;
|
|
146
162
|
app/host shell calls receive the union of the caller's still-active skills. Clear them with
|
|
147
163
|
`args.deactivate=true`. **Connector execution receives no platform ticket, credential env or
|
|
@@ -150,9 +166,9 @@ entirely — never promise that a platform-authenticated script runs there uncha
|
|
|
150
166
|
|
|
151
167
|
**`neuralis request` keeps a route's error body:** a non-2xx answer prints an `HTTP <code>` line
|
|
152
168
|
and the body on stderr, leaves stdout empty and exits 1. So a script that passes the answer
|
|
153
|
-
through needs nothing more, and one that consumes it (`
|
|
154
|
-
|
|
155
|
-
branch on into a `jq` error. Branch on the code by reading that `HTTP` line from stderr; never send
|
|
169
|
+
through needs nothing more, and one that consumes it (`execFileSync` throws on the exit 1;
|
|
170
|
+
`X="$(neuralis request …)"` under `set -e`; `--jq`) stops before a projection can turn the `{code,error}` your SKILL.md tells the model to
|
|
171
|
+
branch on into a `jq` error. Branch on the code by reading that `HTTP` line from stderr (`err.stderr` in Node); never send
|
|
156
172
|
the stderr to `/dev/null`.
|
|
157
173
|
|
|
158
174
|
**For any other HTTP call — an external API — never let curl throw away the error body: `-f` is
|
|
@@ -162,7 +178,7 @@ depends on what the script does with the response:
|
|
|
162
178
|
|
|
163
179
|
- **passes it through** (curl's output IS the script's output) → `curl --fail-with-body -sS …`.
|
|
164
180
|
The body reaches the model, the exit stays non-zero, nothing else changes. (Needs curl ≥ 7.76,
|
|
165
|
-
March 2021; the
|
|
181
|
+
March 2021; the Docker image carries 8.21 — a native host's is its own, check `curl --version`.)
|
|
166
182
|
- **consumes it** (captured into a variable, or piped into a `jq` projection) → capture the status
|
|
167
183
|
instead: `RESPONSE="$(curl -sS -w '\n%{http_code}' …)"`, split `STATUS` off the tail, and print
|
|
168
184
|
the RAW body then `exit 1` **before** the projection runs. Under `set -e` a failing assignment
|
|
@@ -171,7 +187,7 @@ depends on what the script does with the response:
|
|
|
171
187
|
- **is documented as being piped by its CALLER** (`my-script.sh | jq …`) → write the error body to
|
|
172
188
|
**stderr**, or the caller's filter eats it exactly the same way one level up.
|
|
173
189
|
|
|
174
|
-
Neuralis' own
|
|
190
|
+
Neuralis' own native bundles are swept for both rules by a static test; a package you ship
|
|
175
191
|
should carry the equivalent check over its own `scripts/`, because nothing in the platform
|
|
176
192
|
inspects a third-party script for you.
|
|
177
193
|
|
|
@@ -215,7 +231,7 @@ missed a whole class:
|
|
|
215
231
|
|
|
216
232
|
3. **Decide "does this mutate?" from the INVOCATION LINE, never from the script
|
|
217
233
|
file.** A generic helper that takes its verb as an argument —
|
|
218
|
-
`
|
|
234
|
+
`node "${SKILL_DIR}/scripts/api.mjs" POST commit` — hides its own verb
|
|
219
235
|
behind a positional parameter. Keyed on the file, such a helper looks like a GET
|
|
220
236
|
and every one of its write routes vanishes from the audit. If you ship such a
|
|
221
237
|
helper, say so in its header comment; if you audit one, read the argument.
|
|
@@ -275,7 +291,7 @@ Only `rules/` and `instructions/` inject by category. Choose a rule only for beh
|
|
|
275
291
|
- **A file with no `activation` block is injected every turn**; the default is `always`, and **`invocation: auto` is identical to `always`** — only `manual` keeps a file out of the prompt. There is no relevance-based injection.
|
|
276
292
|
- **`activation.target` accepts exactly two values**, `prompt-context` and `workspace`. Anything else — a tool-scoped selector, say — is cast through unvalidated and silently ignored, and the file still injects because its folder made it a rule. There is no tool-conditional injection.
|
|
277
293
|
|
|
278
|
-
**Only an installed
|
|
294
|
+
**Only an installed native package can ship a rule at all.** For anything else the loader sanitises at discovery: every `rules/` file is physically moved into the docs array so it can never inject, `overwrite` is forced false, and `activation.target: prompt-context` becomes `workspace`. A package found inside a synced source goes further — its rules and universal identity files (`AGENTS.md`, `CLAUDE.md`) are **default-OFF** until someone opts in per agent or per conversation, and its docs are server-pinned to manual. Design a dropped or synced package so its behaviour never depends on a rule.
|
|
279
295
|
|
|
280
296
|
Three craft rules for the floor: **one fact, one file** — copies of the same guidance drift apart; **floor versus method** — the always-on file carries only what must be obeyed every turn and the how-to goes in a skill, but a floor must **never** live only in a skill body, because an un-activated skill loses it entirely; **never hardcode the tool, skill or source inventory into a rule** — the runtime renders it already and your copy is wrong the moment the runtime changes.
|
|
281
297
|
|
|
@@ -303,7 +319,7 @@ listed as an agent, never summonable — and a skipped file in an installed pack
|
|
|
303
319
|
|
|
304
320
|
In both forms the agent is callable BY NAME through its declared `id`/`name`; a
|
|
305
321
|
filename-identity row without one (a `commands/*.md` skill fork) is listed in `<packages>`
|
|
306
|
-
but not summonable — reachable by address only (`uri`, host path,
|
|
322
|
+
but not summonable — reachable by address only (`uri`, host path, local path).
|
|
307
323
|
|
|
308
324
|
**Same key, two jobs: `allowed-tools` is ENFORCED on an agent file and ADVISORY on a skill.**
|
|
309
325
|
|
|
@@ -337,7 +353,7 @@ Two parsers run on the same file: **discovery** keeps every top-level key, **act
|
|
|
337
353
|
| `allowed-tools` | a CEILING on agent files (a `delegate` call intersects, never replaces); advisory on skills. Absent = inherit; `[]` = no tools. |
|
|
338
354
|
| `arguments` | real `{{name}}` / `$1` substitution; not validated, `required` not enforced. |
|
|
339
355
|
| `disable-model-invocation` | sets a field the overview and file catalog display. On a **skill** it gates nothing — the model can still activate it. Load-bearing only on an `instruction`/`rule`, or on a file with an explicit `activation.pinned` / `activation.target: prompt-context`. |
|
|
340
|
-
| `overwrite` | carried, but no replacement semantics exist; forced off for anything not
|
|
356
|
+
| `overwrite` | carried, but no replacement semantics exist; forced off for anything not native. |
|
|
341
357
|
| `hooks` | **dropped by the activation parser** — no hooks, no warning, no log line. Use the package-level `hooks.json`. |
|
|
342
358
|
| `user-invocable`, `contextMode`, `agentType`, `preferredModel`, `maxSteps`, `manualOnly` | accepted (the first for ecosystem compatibility) and read by nothing today. Zero effect, zero diagnostics. |
|
|
343
359
|
|
|
@@ -358,4 +374,4 @@ Two parsers run on the same file: **discovery** keeps every top-level key, **act
|
|
|
358
374
|
|
|
359
375
|
---
|
|
360
376
|
|
|
361
|
-
Full public reference: **https://docs.neuralisapp.com/package-system/skills** — and the five-category contribution contract at **https://docs.neuralisapp.com/package-system
|
|
377
|
+
Full public reference: **https://docs.neuralisapp.com/package-system/skills** — and the five-category contribution contract at **https://docs.neuralisapp.com/package-system**.
|
|
@@ -18,7 +18,7 @@ Every surface lives in `package.json` under `neuralis.app.surfaces[]` — a unio
|
|
|
18
18
|
| `dock` | `kind`, `id`, `label`, `action` | `icon`, `image`, `position`, `section`, `dock`, `tooltip`, `color` |
|
|
19
19
|
| `card` | `kind`, `id`, `type` | `title`, `render`, `match`, `bridge` |
|
|
20
20
|
|
|
21
|
-
**Every icon comes from ONE platform library** — about 270 curated lucide names in categories, the `ICON_LIBRARY` export of `@neuralis/package-system/icons`. Write the PascalCase name (`ChartLine`) or its lucide-kebab form (`chart-line`); a few aliases (`layout`, `terminal`, `admin`) also resolve. The same rule covers a surface `icon`, a team member's `appearance.icon` and a workflow template's `icon`. Any other name renders the fallback glyph, warns at load, and is an ERROR in `check-package.
|
|
21
|
+
**Every icon comes from ONE platform library** — about 270 curated lucide names in categories, the `ICON_LIBRARY` export of `@neuralis/package-system/icons`. Write the PascalCase name (`ChartLine`) or its lucide-kebab form (`chart-line`); a few aliases (`layout`, `terminal`, `admin`) also resolve. The same rule covers a surface `icon`, a team member's `appearance.icon` and a workflow template's `icon`. Any other name renders the fallback glyph, warns at load, and is an ERROR in `check-package.mjs`.
|
|
22
22
|
|
|
23
23
|
## Task: ship a standing panel
|
|
24
24
|
|
|
@@ -68,7 +68,7 @@ The smallest complete, **reachable** UI a project-dropped package can ship. Four
|
|
|
68
68
|
|
|
69
69
|
**Gate the widget, not the dock entry** — dock visibility inherits the target widget's `requires.features` automatically. Duplicating `requires` only creates drift: a user holding the widget's features but not your accidentally-different dock feature gets a widget they can never open.
|
|
70
70
|
|
|
71
|
-
**An app picture is optional: `"image": "app/shared/<name>.png"`.** A PNG, JPEG or WebP under `app/shared/`, at most 256 KiB, each path segment letters, digits, `.`, `_` or `-`. The dock, the app menu and the inbox show it in a fixed-size box to whoever can see the dock entry; keep the `icon` glyph — it renders until the picture arrives and whenever it cannot be shown. The platform judges the BYTES, never the extension: an SVG, a renamed document or an oversized file is not served and the glyph stays. An invalid path (absolute, remote, outside `app/shared/`, `..`, a non-raster extension) is a validation error that fails the whole package. `check-package.
|
|
71
|
+
**An app picture is optional: `"image": "app/shared/<name>.png"`.** A PNG, JPEG or WebP under `app/shared/`, at most 256 KiB, each path segment letters, digits, `.`, `_` or `-`. The dock, the app menu and the inbox show it in a fixed-size box to whoever can see the dock entry; keep the `icon` glyph — it renders until the picture arrives and whenever it cannot be shown. The platform judges the BYTES, never the extension: an SVG, a renamed document or an oversized file is not served and the glyph stays. An invalid path (absolute, remote, outside `app/shared/`, `..`, a non-raster extension) is a validation error that fails the whole package. `check-package.mjs` checks the path, the file and its bytes.
|
|
72
72
|
|
|
73
73
|
## Task: pick a renderer
|
|
74
74
|
|
|
@@ -76,17 +76,17 @@ The smallest complete, **reachable** UI a project-dropped package can ship. Four
|
|
|
76
76
|
|
|
77
77
|
| renderer | validates for | how it renders |
|
|
78
78
|
|---|---|---|
|
|
79
|
-
| `direct` | `access.trust: "
|
|
80
|
-
| `iframe` | any trust with a **relative** url; an absolute (`https://`, `//`, leading `/`) url needs trusted or
|
|
81
|
-
| `mcp` | trusted or
|
|
79
|
+
| `direct` | `access.trust: "native"` only — hard error otherwise | compiled React the package's **own** host entrypoint registers |
|
|
80
|
+
| `iframe` | any trust with a **relative** url; an absolute (`https://`, `//`, leading `/`) url needs trusted or native | a sandboxed document — every non-native path |
|
|
81
|
+
| `mcp` | trusted or native; widgets only, **rejected on cards** | a marker for imported external surfaces, not an authoring target |
|
|
82
82
|
|
|
83
|
-
**Use `iframe` with a relative url for a dropped package, `direct` for an installed
|
|
83
|
+
**Use `iframe` with a relative url for a dropped package, `direct` for an installed native one.** A project-dropped package is pinned untrusted at scan and capped at trusted; it can **never** be native, so `direct` stays unavailable even after an admin trust elevation. In a dropped package a missing `access.trust` is a warning and the package is treated as untrusted — `direct` in its trust-less manifest fails too. And `direct` is only half a declaration: the manifest entry alone renders nothing, because the host never imports package TSX — the package's own `install…HostComponents` entrypoint is what registers the component, and a native package ships that entrypoint as a prebuilt module named in `app.module` (built with `neuralis-build ui`).
|
|
84
84
|
|
|
85
85
|
An absolute url also needs the remote page to permit framing — `X-Frame-Options: DENY` or `frame-ancestors 'none'` (most large SaaS, banks, mail providers) renders blank whatever the host allows; drive those through the virtual-desktop / browser-automation capability instead.
|
|
86
86
|
|
|
87
87
|
### The entry lives in its own root, and it is self-contained
|
|
88
88
|
|
|
89
|
-
Two rules decide whether a relative `iframe` surface loads at all. Both are checked by `check-package.
|
|
89
|
+
Two rules decide whether a relative `iframe` surface loads at all. Both are checked by `check-package.mjs` before you ship.
|
|
90
90
|
|
|
91
91
|
**1. The canonical root.** Every asset-backed entry lives under its **own** dedicated directory:
|
|
92
92
|
|
|
@@ -159,7 +159,7 @@ The frame mounts `sandbox="allow-scripts allow-forms"` — deliberately **withou
|
|
|
159
159
|
|
|
160
160
|
**Nothing is pushed into a widget iframe.** There is no host→widget message channel: the frame posts nothing and listens for nothing. **A card is different** — see the next section: a sandboxed card gets a bounded, identity-free, strictly one-way `neuralis:card-data/v1` feed. **Do not write a widget that waits for a message**; it will wait forever.
|
|
161
161
|
|
|
162
|
-
The privileged `window.neuralis` JSON-RPC bridge (`submitApproval`, `requestData`) is a separate, opt-in layer for **cards**, gated by `bridge: { enabled: true }`, restricted to trusted/
|
|
162
|
+
The privileged `window.neuralis` JSON-RPC bridge (`submitApproval`, `requestData`) is a separate, opt-in layer for **cards**, gated by `bridge: { enabled: true }`, restricted to trusted/native, and further restricted at runtime to a **remote HTTPS origin that differs from the host's** — an untrusted package opting in is a hard validation error, and a host-served or same-origin frame is refused the bridge fail-closed even when trusted.
|
|
163
163
|
|
|
164
164
|
Asset serving is authenticated per request, scope-bound, traversal-safe, `Cache-Control: private, no-store`, `Referrer-Policy: no-referrer`, capped at 2 MiB per asset, and limited to an extension allowlist (html, css, js, mjs, map, json, images, fonts, txt) — `.ts`/`.tsx` are excluded so package internals never leak. Every asset carries a hard CSP: `default-src 'none'`, `script-src`/`style-src` `'self' 'unsafe-inline'` (an entry's own inline script and style are the point), `img-src 'self' data: blob:`, `font-src 'self' data:`, `connect-src 'self'`, `form-action 'none'`, `base-uri 'self'`, `frame-ancestors 'self'` and `sandbox allow-scripts allow-forms`. That last directive mirrors the sandbox attribute the frame already carries, so it costs your surface nothing — what it buys is that the document stays in an opaque origin even if it is ever opened *outside* the workspace frame. No CDN script, no remote font, no third-party XHR target — every source list is `'self'`-based, and in an opaque sandboxed document `'self'` resolves to the origin that served the document, which is what lets a bundle surface's own same-origin files load. So two shapes work and no others: **a single self-contained document**, or **one document plus its own sibling files under `assetMode: "bundle"`**.
|
|
165
165
|
|
|
@@ -193,7 +193,7 @@ Transparency resolves per-instance override → manifest `chrome.transparent`
|
|
|
193
193
|
|
|
194
194
|
A card binds to a tool through the tool's `x-neuralis.ui.cardType` matching the card's `type`: the handler returns `structuredContent`, the host resolves a card type, the registry maps it to a component, it renders inline in chat. An unknown card type falls back to a generic default card with a console warning. Three names must agree — the tool's `ui.cardType`, the card surface's `type`, and, for `direct`, the type your host entrypoint registered. **Render `running`, `success` and `error`**, or the card shows nothing while the tool runs and nothing when it fails.
|
|
195
195
|
|
|
196
|
-
**While the tool call is still streaming, `tool.input` is a partial JSON STRING, not the parsed object — a `direct` card must never `JSON.parse` (or whole-string-regex) it per delta.** That is O(n²) across a large call and a real per-commit performance cost class in the timeline. Use the exported streaming helpers from `@neuralis/agent-core/app/chat` instead: `previewInput(input, isPending)` for the standard bounded input preview, `extractStreamingField(input, field)` to stream ONE named string field (a command, a task, a question) progressively into your header or body — it reads the settled object and the partial buffer alike — `extractSettledArrayItems(input, field)` to list the already-closed elements of ONE array field while it streams, and `parseSettledObject(input)` as the O(1) "is it complete?" gate before your one real parse. None of them truncates the input; iframe cards need none of this (the `card-data/v1` snapshot already delivers the bounded streaming input).
|
|
196
|
+
**While the tool call is still streaming, `tool.input` is a partial JSON STRING, not the parsed object — a `direct` card must never `JSON.parse` (or whole-string-regex) it per delta.** That is O(n²) across a large call and a real per-commit performance cost class in the timeline. Use the exported streaming helpers from `@neuralis/agent-core/app/chat` instead: `previewInput(input, isPending)` for the standard bounded input preview, `extractStreamingField(input, field)` to stream ONE named string field (a command, a task, a question) progressively into your header or body — it reads the settled object and the partial buffer alike — `extractSettledArrayItems(input, field)` to list the already-closed elements of ONE array field while it streams (and `extractStreamingArrayTail(input, field)` for the element still open), and `parseSettledObject(input)` as the O(1) "is it complete?" gate before your one real parse. None of them truncates the input; iframe cards need none of this (the `card-data/v1` snapshot already delivers the bounded streaming input).
|
|
197
197
|
|
|
198
198
|
Cards may declare `match.state: "interaction-required" | "approval-required"`. An **approval card is preview-only** — the host wraps it with approve/deny chrome. Painting your own approve/deny buttons is a **warning, not a rejection** (the validator cannot scan HTML statically), so the package loads, ships duplicate controls, and users click a button that does nothing.
|
|
199
199
|
|
|
@@ -209,9 +209,9 @@ A sandboxed `iframe` card — and **only** a card — receives the tool's own da
|
|
|
209
209
|
|
|
210
210
|
**The channel is owner-gated.** Only a card whose registration is bound to its **own** package's tool receives the real input and result; a cardType that matches a foreign package's tool, or that no owner claims, gets a safe generic placeholder instead. This is not a cross-package data channel.
|
|
211
211
|
|
|
212
|
-
**The host owns the performance, not your card — with ONE exception: your card's height.** Stream pacing, coalescing, virtualizer retention and the single multiplexed realtime hub are all host-side. Do not write a scheduler, a keep-alive or a per-card `EventSource` into a card — a handful of per-surface streams exhausts the browser's connection pool, and a card that re-renders on every delta is what the host's pacing exists to prevent. And bound your MOUNT cost to what is visible: a compact body clipped at ~90px shows only a handful of rows, so do not mount hundreds of row elements into it (clamp the element count while collapsed and render the full set on expand — the
|
|
212
|
+
**The host owns the performance, not your card — with ONE exception: your card's height.** Stream pacing, coalescing, virtualizer retention and the single multiplexed realtime hub are all host-side. Do not write a scheduler, a keep-alive or a per-card `EventSource` into a card — a handful of per-surface streams exhausts the browser's connection pool, and a card that re-renders on every delta is what the host's pacing exists to prevent. And bound your MOUNT cost to what is visible: a compact body clipped at ~90px shows only a handful of rows, so do not mount hundreds of row elements into it (clamp the element count while collapsed and render the full set on expand — the native diff card renders 8 rows collapsed, 200 expanded), and defer any mount-time network fetch to idle or to expand so a fast scroll over your cards is not a request burst — and cache the fetch RESULT (the negative "nothing there" answer included) in a bounded module-level map keyed by stable identity, because the timeline remounts your card on every scroll-back and idle deferral alone only delays the refetch, it does not remove it.
|
|
213
213
|
|
|
214
|
-
The exception exists because the host CANNOT know it. The timeline is virtualized, so it must reserve a height for a row it has not measured yet, and reserving the wrong amount is exactly what makes a chat list jump when a card appears. That guess used to live in a host-side table that knew only the built-in cards; everything else shared one constant, measured wrong by up to 579px. A **
|
|
214
|
+
The exception exists because the host CANNOT know it. The timeline is virtualized, so it must reserve a height for a row it has not measured yet, and reserving the wrong amount is exactly what makes a chat list jump when a card appears. That guess used to live in a host-side table that knew only the built-in cards; everything else shared one constant, measured wrong by up to 579px. A **native `direct` card registered from your own host entrypoint** may therefore pass an `estimateHeight` model next to its renderer and mount policy:
|
|
215
215
|
|
|
216
216
|
```tsx
|
|
217
217
|
// module scope — NOT an inline arrow at the call site
|
|
@@ -235,7 +235,7 @@ Every rule below has already been violated at least once in this codebase:
|
|
|
235
235
|
- Keep it a **module-level reference**. The registry compares estimators by identity to decide whether a re-registration changed anything; a fresh closure per call turns every reconcile pass into a replace, a fingerprint bump and a retained-View eviction.
|
|
236
236
|
- Keep it **cheap and pure** — it runs inside the virtualizer's measurement arithmetic, for every unmeasured row, on every offset rebuild (and that rebuild sits in a pre-paint `flushSync` window, so an unmemoized parse is scroll jank). If it parses the payload, memoize by `tool` reference **together with every input the result depends on — the `status` it was computed at AND the wrap width**: the tool object mutates BY REFERENCE across pending→settle during a live stream, so a reference-only memo keeps serving the pending-era "Running…" height after the tool settles, and a memo that forgets the width keeps serving the previous panel size after a resize — silently cancelling your `widthScale` branch. Store `{ status, charsPerLine, height }` and recompute when either changes.
|
|
237
237
|
- Compute it **from the same data the component renders from**, ideally by calling the same parser. An estimator that models a different shape than the renderer is worse than none. For text bodies, count lines with the exported `countRenderedLines(text, charsPerLine)` (from `@neuralis/agent-core/app/chat`) — a flat `ceil(length / charsPerLine)` cannot see hard newlines and understated a real markdown row by ~3× (the measured worst overlap source in the timeline) — and pass a `charsPerLine` you have scaled to the live panel, never the bare constant (see the width rule below).
|
|
238
|
-
- **When unsure, round UP — and never return your COMPACT height for an expanded row.** Rows are absolutely positioned, so the next row sits at your row's offset plus your CACHED height: an over-estimate leaves a gap that closes on the first measurement, an under-estimate paints the next row on top of yours. If your card can expand, branch on `disclosure` and model the expanded body too. Skipping that branch is the worst measured bug in this whole surface — a
|
|
238
|
+
- **When unsure, round UP — and never return your COMPACT height for an expanded row.** Rows are absolutely positioned, so the next row sits at your row's offset plus your CACHED height: an over-estimate leaves a gap that closes on the first measurement, an under-estimate paints the next row on top of yours. If your card can expand, branch on `disclosure` and model the expanded body too. Skipping that branch is the worst measured bug in this whole surface — a native card estimated at 114px while rendering 1479px expanded.
|
|
239
239
|
- **Scale every characters-per-line constant by `widthScale`, because "round up" depends on how wide the panel is.** A chat panel is not one width: it is split, resized, docked and widened, and a constant calibrated at one width crosses into the *under*-reserve half of the rule above at every narrower one — measured live, one real row rendered 1251px in a 350px column while the fixed estimate reserved 792px, a 459px shortfall that paints the following row on top of it. Multiply your own constant with the exported `scaleCharsPerLine(authored, widthScale)` (same import as `countRenderedLines`) and use the result everywhere you count lines. Do NOT invent your own formula from the number: it is a ratio against the width the platform's constants were authored at, and the helper carries the degenerate-width floor.
|
|
240
240
|
|
|
241
241
|
`disclosure` carries the row's remembered expand/collapse state when it differs from your default; read it only if your card renders at two heights. `widthScale` carries how wide the row surface is relative to that authored reference width (`1` IS the reference; a narrow panel is below it, a wide one above). **Both are optional and an absent `widthScale` means `1`** — an estimator that ignores it behaves exactly as it did before the field existed, so an imported card needs no changes; reading it is purely an improvement to your card's first paint.
|
|
@@ -246,7 +246,7 @@ Declaring nothing is a legitimate choice: a REGISTERED card without a model gets
|
|
|
246
246
|
|
|
247
247
|
### Task: land your card's activity on the composer's Changes & Actions panel
|
|
248
248
|
|
|
249
|
-
Above the chat composer sits the Changes & Actions panel — the per-conversation aggregation of tool activity (Changes / Todos / Actions / Delegates tabs + the post-stream summary chip of changed files). Which tab your card's activity lands on is declared by the card, through the SAME registration that carries `estimateHeight` — the aggregation surfaces iterate the card registry and hardcode nothing (the
|
|
249
|
+
Above the chat composer sits the Changes & Actions panel — the per-conversation aggregation of tool activity (Changes / Todos / Actions / Delegates tabs + the post-stream summary chip of changed files). Which tab your card's activity lands on is declared by the card, through the SAME registration that carries `estimateHeight` — the aggregation surfaces iterate the card registry and hardcode nothing (the native filesystem card declares its own contract from its own package the same way).
|
|
250
250
|
|
|
251
251
|
**Direct lane** — pass a `composer` contract in `registerCardComponent`:
|
|
252
252
|
|
|
@@ -272,7 +272,7 @@ registerCardComponent('thing.result', ThingCard, {
|
|
|
272
272
|
|
|
273
273
|
**Manifest lane** (`iframe` cards, no JS): declare the minimal subset on the card surface — `"composer": { "tab": "changes", "badgeField": "label", "pathField": "uri" }`. `tab` targets EXISTING builtin tabs only (a manifest can never create a tab); `badgeField`/`pathField` name TOP-LEVEL `structuredContent` fields (string, or string[] for `pathField`) that the host compiles into the same generic extractors. An invalid composer block is a validation ERROR that sinks the package; a `composer` on a `direct`-renderer surface is a WARNING and is **ignored** — the direct lane's contract has ONE owner, your install-file registration, never the manifest.
|
|
274
274
|
|
|
275
|
-
##
|
|
275
|
+
## Native `direct` surfaces
|
|
276
276
|
|
|
277
277
|
`component.import` resolves through a client-side host registry the package populates from its runtime UI module: declare `app.module`, build `app/host.tsx` with `neuralis-build ui`, and the host imports the module and calls each `install…HostComponents({ registry, port })` export with a registry that accepts only your own package's registrations. Every `component.import` your manifest declares must be registered somewhere under `app/`, or the surface resolves to null and shows the placeholder — no error. A module built before a platform upgrade can be refused (its record names an older host API or no React major): rebuild it with `neuralis-build ui`.
|
|
278
278
|
|
|
@@ -288,7 +288,7 @@ import { SkillLauncher } from '@neuralis/package-system/client';
|
|
|
288
288
|
<SkillLauncher
|
|
289
289
|
skill="manage-things" // the ACTIVATION id (SKILL.md frontmatter `name`)
|
|
290
290
|
packageId="@neuralis/your-package" // the REGISTRY id — manifest `neuralis.id`
|
|
291
|
-
scripts={['scripts/list.
|
|
291
|
+
scripts={['scripts/list.mjs']} // optional: prefill specific bundle scripts
|
|
292
292
|
label="Do this with the agent"
|
|
293
293
|
icon="wand"
|
|
294
294
|
variant="icon" // 'icon' | 'label' | 'row'
|
|
@@ -306,7 +306,7 @@ Four rules, each of which has already cost someone a debugging session:
|
|
|
306
306
|
|
|
307
307
|
## Verify
|
|
308
308
|
|
|
309
|
-
Run `
|
|
309
|
+
Run `node "${SKILL_DIR}/scripts/check-package.mjs" <package-root>` first: the canonical entry root, the renderer profile and the self-containment rule are all checked there, and self-containment can be checked **nowhere else** — the load-time validator receives the manifest object with no package root and no filesystem, and the platform blocks nothing at runtime.
|
|
310
310
|
|
|
311
311
|
Then fetch the runtime snapshot with the `x-project-id` header and confirm it lists **both** surfaces — the widget *and* the dock entry. Open it as the **least-privileged role that should have access**: feature-gated surfaces are hidden, never greyed out, so testing as the owner hides every gate bug. Finally look at the rendered surface itself — an unstyled panel is the signature of a subresource that was never served.
|
|
312
312
|
|
|
@@ -42,7 +42,7 @@ Discovery scans `team/*/` at package root and walks `identity/**` and `files/**`
|
|
|
42
42
|
|
|
43
43
|
That is the whole surface — **unknown keys are dropped silently.** The traps:
|
|
44
44
|
|
|
45
|
-
- **There is no top-level `icon`, `color`, `model`, `allowedTools`, `blockedTools` or `tags`.** Appearance nests under `appearance`; the model pin is `config.settings.defaultModelId`; the permission-profile default is `config.settings.guardProfile` (`safe` | `balanced` | `auto`); the loop cap is `config.settings.maxSteps` — **omit it unless this coworker must be capped BELOW the platform**, because a pin can only ever LOWER the live `maxAgentSteps` ceiling, never raise it, and an unpinned agent runs AT the ceiling (both
|
|
45
|
+
- **There is no top-level `icon`, `color`, `model`, `allowedTools`, `blockedTools` or `tags`.** Appearance nests under `appearance`; the model pin is `config.settings.defaultModelId`; the permission-profile default is `config.settings.guardProfile` (`safe` | `balanced` | `auto`); the loop cap is `config.settings.maxSteps` — **omit it unless this coworker must be capped BELOW the platform**, because a pin can only ever LOWER the live `maxAgentSteps` ceiling, never raise it, and an unpinned agent runs AT the ceiling (both native team members ship unpinned for exactly this reason); tool restriction is `config.tools` (`mode: "all" | "custom"` + `selected[]` + per-tool `overrides`; an `owners` choice is stripped from a template — which package answers a shared tool name is picked per agent, inside its project). A top-level `icon` renders the default avatar and reports **no error at all**; `appearance.icon` takes a platform icon-library name (`surfaces-and-design.md`, the icon rule under "All UI is one array").
|
|
46
46
|
- **`description` is the field the model reads; `descriptionForModel` reaches no prompt.** `description` is copied onto the provisioned agent and rendered in the workspace block's agent list, so every agent in the project sees it — write the *"pick this coworker when…"* sentence there. Fill `descriptionForModel` too (right slot, costs nothing), but never rely on it for coworker selection.
|
|
47
47
|
- **`role` is priority-checked at provisioning, not cosmetic.** A caller may only provision a role equal to or weaker than their own (lower is stronger: owner 1, admin 2, manager 10, member 20, viewer 30; a custom role declares its own number in 1..99, and an unrecognised name resolves to the weakest tier and is always assignable). Name a template role `admin` or `owner` and provisioning **throws** for every ordinary member — the package installs and its coworker cannot be hired.
|
|
48
48
|
- **`permissions` grants nothing.** `permissions.features` is a **visibility** gate — a caller lacking them never sees the member in the team catalogue; `permissions.sources` is display-only. Real scope comes from the caller's own role and features plus the package `uriPolicies`.
|
|
@@ -184,4 +184,4 @@ Installing a template is always **explicit**: it instantiates a mutable entry ow
|
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
-
Public reference: [Workflows](https://docs.neuralisapp.com/agent-core/workflows) · [Contributions, including team members](https://docs.neuralisapp.com/package-system/
|
|
187
|
+
Public reference: [Workflows](https://docs.neuralisapp.com/agent-core/workflows) · [Contributions, including team members](https://docs.neuralisapp.com/package-system/team) · [Agent files and delegation](https://docs.neuralisapp.com/agent-core/agents)
|
|
@@ -21,7 +21,7 @@ All three must be true. Two is not done.
|
|
|
21
21
|
3. **The structure is what you intended** —
|
|
22
22
|
`fs_list({ uri: "packages://<slug>", recursive: true })`.
|
|
23
23
|
|
|
24
|
-
Pre-flight, before you ship: `
|
|
24
|
+
Pre-flight, before you ship: `node "${SKILL_DIR}/scripts/check-package.mjs" <package-root>`. It picks
|
|
25
25
|
its mode from disk and prints which one it ran: a `package.json` carrying a `neuralis` block (or a
|
|
26
26
|
`neuralis.package.json`) is **manifest mode**; no manifest at all is **markdown mode** — the
|
|
27
27
|
source-package / bare-skill layout, which **is** checkable.
|
|
@@ -37,7 +37,7 @@ SKIPPED — manifest, tools, workflows, app surfaces, runtime, dist — so nothi
|
|
|
37
37
|
when it was not. Exit `0` no errors · `1` errors · `2` usage. Passing proves nothing about loading
|
|
38
38
|
or discovery, in either mode.
|
|
39
39
|
|
|
40
|
-
After the restart, rescan or sync: `
|
|
40
|
+
After the restart, rescan or sync: `node "${SKILL_DIR}/scripts/verify-loaded.mjs" <package-id>` — the
|
|
41
41
|
manifest id for (a), the instance URI for (b) and (c) (§2).
|
|
42
42
|
|
|
43
43
|
---
|
|
@@ -59,7 +59,7 @@ Three `partial` reasons exist, and only three:
|
|
|
59
59
|
valid emitted route manifest. Routes fail closed rather than being served ungated. Rebuild.
|
|
60
60
|
- **`runtime-unavailable`** — the code runtime failed to start (a sandbox load failure, or an MCP
|
|
61
61
|
subprocess that would not spawn).
|
|
62
|
-
- **`untrusted-node`** — a non-
|
|
62
|
+
- **`untrusted-node`** — a non-native package left `runtime.type` at its `node` default while
|
|
63
63
|
shipping code. Declare `"runtime": { "type": "wasm" }` and rebuild. The most misread state in the
|
|
64
64
|
system: markdown contributions still appear, so it looks like it half worked.
|
|
65
65
|
|
|
@@ -71,7 +71,7 @@ has no status, no `reason` of that kind and no load line — done-gate step 1 fo
|
|
|
71
71
|
the packages overview", not "it loaded". It is listed there under `sourcePackages[]`, keyed by its
|
|
72
72
|
instance id: the package ROOT's URI, no trailing slash, plus `?project=<p>&scope=<scope>`. A
|
|
73
73
|
package rooted at `data://acme-pack` is `data://acme-pack?project=<p>&scope=project`; one at the
|
|
74
|
-
source root itself is `data://?project=<p>&scope=project`. Pass that id to `verify-loaded.
|
|
74
|
+
source root itself is `data://?project=<p>&scope=project`. Pass that id to `verify-loaded.mjs` and it prints the root, the
|
|
75
75
|
grouping reason and the per-category file counts in place of a status.
|
|
76
76
|
|
|
77
77
|
Where to read it without server-log access:
|
|
@@ -88,7 +88,7 @@ is read — caller-supplied identity is not an auth mechanism anywhere on this p
|
|
|
88
88
|
|
|
89
89
|
Entries carry `{ packageId, status, reason?, error?, trust, registered }`. The route is gated on
|
|
90
90
|
the `core.observe` feature: **a `403` here means you lack the diagnostics capability, not that the
|
|
91
|
-
package is missing.** Treat those as two different outcomes — `verify-loaded.
|
|
91
|
+
package is missing.** Treat those as two different outcomes — `verify-loaded.mjs` wraps this exact
|
|
92
92
|
call and makes that split for you.
|
|
93
93
|
|
|
94
94
|
---
|
|
@@ -129,9 +129,9 @@ nothing:
|
|
|
129
129
|
one everybody skips.
|
|
130
130
|
5. **It activates**, and `${SKILL_DIR}` resolves to a real path. The placeholder falls back to a
|
|
131
131
|
sentinel string only when the skill has no host-side directory at all — a source-contributed
|
|
132
|
-
skill whose connector exposes no
|
|
132
|
+
skill whose connector exposes no local path. An installed package and a project-folder drop
|
|
133
133
|
both resolve real directories.
|
|
134
|
-
6. **A bundled script runs**: `
|
|
134
|
+
6. **A bundled script runs**: `node "${SKILL_DIR}/scripts/<name>.mjs"`.
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
@@ -165,7 +165,7 @@ it loads, the snapshot lists it, and the panel is blank or unstyled. Work the ma
|
|
|
165
165
|
|
|
166
166
|
| Check | How | What it catches |
|
|
167
167
|
|---|---|---|
|
|
168
|
-
| The pre-flight is clean | `
|
|
168
|
+
| The pre-flight is clean | `node "${SKILL_DIR}/scripts/check-package.mjs" <root>` | the canonical entry root, a removed renderer, a conflicting card profile, and the **self-containment** rule — which nothing else can check (see below) |
|
|
169
169
|
| The surface is in the snapshot | fetch the runtime snapshot with the `x-project-id` header | `meta.hosts` missing `neuralis-workspace`; a dropped untrusted absolute url |
|
|
170
170
|
| The widget is **reachable** | the dock rail shows the companion item and it opens the panel | a widget with no `kind: "dock"` sibling — validates, loads, opens for nobody |
|
|
171
171
|
| The card **appears** on a real tool call | run the tool in chat | the `ui.cardType` ↔ card `type` ↔ (for `direct`) registered-type triple; a mismatch silently renders the generic default card |
|
|
@@ -177,7 +177,7 @@ it loads, the snapshot lists it, and the panel is blank or unstyled. Work the ma
|
|
|
177
177
|
|
|
178
178
|
**Least trust, deliberately.** Test the surface at the tier it will actually ship on. A dropped
|
|
179
179
|
package is pinned untrusted: `direct` is unavailable to it, an absolute iframe url is dropped
|
|
180
|
-
fail-closed, and the privileged bridge is a hard validation error. Proving a surface as
|
|
180
|
+
fail-closed, and the privileged bridge is a hard validation error. Proving a surface as native
|
|
181
181
|
and shipping it untrusted is the same class of mistake as testing gates as the owner.
|
|
182
182
|
|
|
183
183
|
**Self-containment can only be proven here.** The load-time validator receives the manifest
|
|
@@ -243,7 +243,7 @@ it('denies a caller who does not — via the dispatcher, where the gate lives',
|
|
|
243
243
|
Other helpers, with the details that bite:
|
|
244
244
|
|
|
245
245
|
- `createMockInitContext({ packageSlug, dataDir?, globalDir?, projectsRoot?, logger?, hostId?, apis? })` — the
|
|
246
|
-
context is
|
|
246
|
+
context is native by default (a `globalDir` is present); pass `globalDir: null` to build a
|
|
247
247
|
project-installed package's context, which has none. The
|
|
248
248
|
key is **`packageSlug`**. A `slug` key is silently ignored and you get `'test-package'`.
|
|
249
249
|
- `createMockToolCallContext(...)` / `createDeniedToolCallContext(...)` take a **shaped override
|
|
@@ -293,7 +293,7 @@ you publish.
|
|
|
293
293
|
|
|
294
294
|
## 10. Ship it
|
|
295
295
|
|
|
296
|
-
**(a) Installed node package —
|
|
296
|
+
**(a) Installed node package — native, in-process.** Build so the compiled output exists (or
|
|
297
297
|
leave a directory to the image build, which builds it), then have an admin register it as a
|
|
298
298
|
dependency of the host — register, rebuild the image (`neuralis rebuild` installs it), verify.
|
|
299
299
|
That sequence is the **deploying admin's terminal work, not yours**; registering a directory or a
|
|
@@ -306,7 +306,7 @@ and never joins the in-process class. (Where the operator has the `neuralweb` CL
|
|
|
306
306
|
`neuralweb install <identity> --channel builtin` prints these commands with a trust notice
|
|
307
307
|
and never executes anything.) **Registering the dependency IS the trust act**: every dependency
|
|
308
308
|
whose own `package.json` carries a `neuralis` block is discovered at boot and loaded in-process
|
|
309
|
-
with host-assigned
|
|
309
|
+
with host-assigned native trust — the same trust as the platform's own packages. Do it only
|
|
310
310
|
for packages the deploying admin has vetted, exactly as any npm dependency. A dependency with no
|
|
311
311
|
`neuralis` block is skipped silently and never exists.
|
|
312
312
|
|
|
@@ -319,7 +319,7 @@ plus an undeclared runtime type is the `untrusted-node` partial from §2.
|
|
|
319
319
|
open when the other two are closed to you. It carries the five markdown categories and nothing else.
|
|
320
320
|
|
|
321
321
|
**What discovery recognises.** No marker file, no manifest and no dot-directory is required — the
|
|
322
|
-
folder layout alone decides. The platform records *why* it grouped a package, and `verify-loaded.
|
|
322
|
+
folder layout alone decides. The platform records *why* it grouped a package, and `verify-loaded.mjs`
|
|
323
323
|
prints that `reason` back to you:
|
|
324
324
|
|
|
325
325
|
| `reason` | What made it a package |
|
|
@@ -337,10 +337,10 @@ nothing.
|
|
|
337
337
|
|
|
338
338
|
**Where to write it.** A source slug *is* a URI scheme, so the address is `<slug>://<path>`. To learn
|
|
339
339
|
which slugs exist, read the `sources` list already in your `<runtime_stack>` block — one row per
|
|
340
|
-
source, carrying the URI root, the connector, the
|
|
340
|
+
source, carrying the URI root, the connector, the local path and **your own** resolved
|
|
341
341
|
`policy=read/write/exec`. That block is the only place those coordinates appear. `fs_list` with no
|
|
342
342
|
`uri` also prints a registry line per root, but it carries counts and sync status only — no
|
|
343
|
-
connector, no
|
|
343
|
+
connector, no local path, no policy — so it answers "is this indexed", never "may I write here,
|
|
344
344
|
and where does that land on disk". Every project seeds a writable `data://`.
|
|
345
345
|
|
|
346
346
|
Worked example — a one-skill package needing no other file:
|
|
@@ -354,7 +354,7 @@ data://acme-pack/skills/release-notes/SKILL.md → root data://acme-pack (ca
|
|
|
354
354
|
lands as a *pending change*, and a pending file's frontmatter is never captured: at best the skill
|
|
355
355
|
reaches the model under its directory name with no description line — the description being its
|
|
356
356
|
entire discovery surface — and under the tool's default category it is not indexed as a contribution
|
|
357
|
-
at all. Writing the file into the source's
|
|
357
|
+
at all. Writing the file into the source's local path (the `container=` value on that source's
|
|
358
358
|
`<runtime_stack>` row) has neither problem.
|
|
359
359
|
|
|
360
360
|
**Then get it indexed.** Discovery reads the sync index, never the live tree: unsynced means
|
|
@@ -377,7 +377,7 @@ invisible, with **no error anywhere** — indistinguishable from correctly hidde
|
|
|
377
377
|
Until then the files are on disk but the package will not appear."*
|
|
378
378
|
|
|
379
379
|
**Then prove it with the script** — it is the authoritative check:
|
|
380
|
-
`
|
|
380
|
+
`node "${SKILL_DIR}/scripts/verify-loaded.mjs" 'data://acme-pack?project=<p>&scope=project'`. It reads the overview surface,
|
|
381
381
|
which lists every discovered source package and reports each category as `N (M active)`. Once the
|
|
382
382
|
sync has landed the package shows on the next stream — discovery is resolved fresh each time.
|
|
383
383
|
|
|
@@ -26,7 +26,7 @@ So a `catalog_manage` with `action: ["list","update","delete"]` has one posture
|
|
|
26
26
|
|
|
27
27
|
One `.json` per tool in the package-root `tools/` folder. **The tool name is the filename minus `.json`** — there is no `name` field (MCP-first), so renaming the file renames the tool and silently breaks every skill or prompt that named the old one. House style `<family>_<action>` in snake_case, not enforced. The file **is** the input schema: everything except `x-neuralis` is taken verbatim as `inputSchema`.
|
|
28
28
|
|
|
29
|
-
**That name is GLOBAL to the deployment, and there is no per-package prefix.** If another installed package already declares it, exactly one entry owns it — the
|
|
29
|
+
**That name is GLOBAL to the deployment, and there is no per-package prefix.** If another installed package already declares it, exactly one entry owns it — the native one, otherwise the lexically-smaller `packageId` — and yours is **shadowed**: still loaded, its other tools untouched, but that name answers from the owner unless the owner is unavailable to the caller or an agent picks your package in Tool Access. Nothing is renamed for you. Pick a name nobody else will pick; `manifest-and-discovery.md` § *A tool name is global* has the rule and where the diagnostic shows up.
|
|
30
30
|
|
|
31
31
|
```json
|
|
32
32
|
{
|
|
@@ -87,7 +87,7 @@ The load check accepts the bare-`http` form with or without the trailing `#`, an
|
|
|
87
87
|
|
|
88
88
|
### Session identity is never a tool input
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Nineteen names are banned as **root-level** properties: `userId`, `projectId`, `agentId`, `conversationId`, `requestId`, `toolUseId`, `delegateRunId`, `delegateMode`, `role`, `priority`, `rolePriorities`, `grantedFeatures`, `spendLimits`, `llmRateLimitRpm`, `agentAccess`, `agentOwnership`, `displayName`, `session`, and the retired `dailyLimits`. Declaring one fails the load with `inputSchema.properties.userId is reserved session context and must not be tool input`. The fix is not to rename it — read `session.userId` in the handler. Identity always comes from the verified session, never from what the model typed.
|
|
91
91
|
|
|
92
92
|
The ban is **root-only by design**: `todos[].priority`, `members[].userId`, a message `role` are legitimate nested domain fields and none of them error. Only the top-level arguments object could be mistaken for caller identity.
|
|
93
93
|
|
|
@@ -217,7 +217,7 @@ For a sandboxed (WASM) package you write no dispatch switch — the build import
|
|
|
217
217
|
|
|
218
218
|
**Presentation.** `ui.presentation` is `inline | card | explored`. Card type resolves first-match-wins: an MCP-app marker on the result, then `ui.cardType`, then `operation`, then `structuredContent.kind`/`.type` (the literal `tool.output` ignored), then the tool name, else `default`. **`ui.cardType` must match a declared card surface's `type`** — see `references/surfaces-and-design.md`.
|
|
219
219
|
|
|
220
|
-
**Settled-header convention.** The chat card header draws ONE status icon, and its accessible name (`title` + `aria-label`) is the status word; there is no visible status pill. The settled status derives from `structuredContent`: `ok: true` (or a string `status` of `success`/`completed`/`pending_review`) → the green check; `ok: false` → the red X; any other string `status` becomes the icon's name, and its TONE is derived from the word by substring — a word containing `error` or `fail` draws the red X, `partial` the amber triangle, and a word containing `pending` draws the IN-FLIGHT SPINNER. Declaring `ok` on success payloads is the
|
|
220
|
+
**Settled-header convention.** The chat card header draws ONE status icon, and its accessible name (`title` + `aria-label`) is the status word; there is no visible status pill. The settled status derives from `structuredContent`: `ok: true` (or a string `status` of `success`/`completed`/`pending_review`) → the green check; `ok: false` → the red X; any other string `status` becomes the icon's name, and its TONE is derived from the word by substring — a word containing `error` or `fail` draws the red X, `partial` the amber triangle, and a word containing `pending` draws the IN-FLIGHT SPINNER. Declaring `ok` on success payloads is the native convention. A completed tool with NO marker at all still settles (the platform floors runtime-`completed` to committed — a foreign tool cannot be forced into the convention), but two sharp edges: never reuse `status` for a domain value (an HTTP status number belongs under `httpStatus`), and never emit a settled status word containing `pending` — `pending_review` is the one floored carve-out. A card that claims to be running forever is a lie the user cannot dismiss, and the platform cannot tell it apart from a call that really is still open. The platform's own status spinner no longer splits the timeline into compositor layers, but anything YOUR card body animates still can — the prohibition, and the primitives that replace an `animate-*` utility, are in `references/surfaces-and-design.md` §Design floor.
|
|
221
221
|
|
|
222
222
|
An unmatched `cardType` is not an error and not a blank: the result falls back to the **generic default card**, which is often deliberate (a host/builtin card type, a tool that only needs the standard rendering). The pre-flight warns rather than rejects for exactly that reason — but if you meant to ship your own card, that warning is the whole signal you will get.
|
|
223
223
|
|
|
@@ -241,9 +241,9 @@ An unmatched `cardType` is not an error and not a blank: the result falls back t
|
|
|
241
241
|
| `Tool rate limit exceeded` | trust-tier rate limit — untrusted 10/min, trusted 100/min |
|
|
242
242
|
| the handler got a number where you expected a rejected string | the dispatch validator coerces types; `"50"` becomes `50`. Guard on semantics, not on "it was validated" |
|
|
243
243
|
| tools catalogued but every call policy-denied | an untrusted package with no explicit tool allowlist gets an **empty** one until an admin elevates it |
|
|
244
|
-
| package `partial`, reason `untrusted-node` | a non-
|
|
244
|
+
| package `partial`, reason `untrusted-node` | a non-native package declaring the in-process runtime. Declare the sandboxed or MCP runtime instead |
|
|
245
245
|
|
|
246
|
-
Untrusted tools are rate-limited an order of magnitude harder and time out sooner than
|
|
246
|
+
Untrusted tools are rate-limited an order of magnitude harder and time out sooner than native ones; the full trust-ceiling table is in `references/runtime-and-build.md`.
|
|
247
247
|
|
|
248
248
|
---
|
|
249
249
|
|