@neuralis/brain-core 0.3.1 → 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 +88 -1417
- package/app/README.md +4 -4
- 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 +24 -2
- package/app/filesystem/README.md +1 -1
- package/app/filesystem/api/README.md +3 -3
- package/app/filesystem/api/index.ts +38 -14
- package/app/filesystem/api/projectPackages.ts +13 -102
- package/app/filesystem/domain/types.ts +30 -10
- package/app/filesystem/hooks/README.md +4 -4
- package/app/filesystem/hooks/useDiscoverableSources.ts +19 -30
- package/app/filesystem/hooks/useFilesystemSSE.ts +42 -2
- package/app/filesystem/host/README.md +6 -4
- package/app/filesystem/host/buildFilesystemHostValue.ts +28 -2
- package/app/filesystem/host/installFilesystemHostComponents.tsx +1 -1
- package/app/filesystem/lib/sourceKindForm.ts +210 -27
- package/app/filesystem/lib/treeVisualState.tsx +98 -4
- package/app/filesystem/store/README.md +1 -0
- package/app/filesystem/store/filesystemStore.ts +99 -8
- 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/DiscoverDock.tsx +5 -1
- package/app/filesystem/ui/FileEditor.tsx +15 -10
- package/app/filesystem/ui/FileTree.tsx +75 -24
- package/app/filesystem/ui/FilesystemWidget.tsx +10 -2
- package/app/filesystem/ui/FolderPicker.tsx +251 -0
- package/app/filesystem/ui/GitControlPanel.tsx +7 -2
- package/app/filesystem/ui/PackagesPanel.tsx +52 -374
- package/app/filesystem/ui/PendingChangesPanel.tsx +1 -1
- package/app/filesystem/ui/PolicyEditor.tsx +109 -24
- package/app/filesystem/ui/README.md +30 -23
- package/app/filesystem/ui/RawMediaPreview.tsx +58 -0
- package/app/filesystem/ui/SourceBranchHeader.tsx +4 -4
- package/app/filesystem/ui/SourcesPanel.tsx +502 -342
- package/app/filesystem/ui/Toolbar.tsx +1 -2
- package/app/filesystem/ui/VectorHealthPanel.tsx +2 -2
- package/app/shared/filesystem.png +0 -0
- package/dist/app/host.js +58 -46
- 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/channels/filesystem.d.ts +1 -1
- package/dist/src/channels/filesystem.js +1 -1
- 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 +11 -19
- package/dist/src/domain/mime.d.ts.map +1 -1
- package/dist/src/domain/mime.js +13 -102
- 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/scopeAccessCtx.d.ts +2 -2
- package/dist/src/domain/scopeAccessCtx.js +2 -2
- 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 +32 -2
- 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/events/pending.d.ts +1 -1
- package/dist/src/events/pending.js +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 +33 -21
- package/dist/src/git/sourceGitControl.d.ts.map +1 -1
- package/dist/src/git/sourceGitControl.js +211 -95
- 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 +9 -12
- 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/approve.d.ts +1 -1
- package/dist/src/routes/approve.js +1 -1
- package/dist/src/routes/events.d.ts +1 -1
- package/dist/src/routes/events.js +1 -1
- package/dist/src/routes/git.d.ts +7 -7
- package/dist/src/routes/git.d.ts.map +1 -1
- package/dist/src/routes/git.js +201 -72
- package/dist/src/routes/git.js.map +1 -1
- package/dist/src/routes/health.d.ts +6 -6
- package/dist/src/routes/health.js +12 -12
- package/dist/src/routes/helpers/sourceNotConfigured.d.ts +1 -1
- package/dist/src/routes/helpers/sourceNotConfigured.js +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 +11 -4
- package/dist/src/routes/helpers.d.ts.map +1 -1
- package/dist/src/routes/helpers.js +13 -6
- package/dist/src/routes/helpers.js.map +1 -1
- package/dist/src/routes/list.d.ts +1 -1
- package/dist/src/routes/list.d.ts.map +1 -1
- package/dist/src/routes/list.js +3 -4
- package/dist/src/routes/list.js.map +1 -1
- package/dist/src/routes/pending.d.ts +4 -4
- package/dist/src/routes/pending.js +9 -9
- package/dist/src/routes/policies.d.ts +10 -8
- package/dist/src/routes/policies.d.ts.map +1 -1
- package/dist/src/routes/policies.js +31 -25
- package/dist/src/routes/policies.js.map +1 -1
- package/dist/src/routes/raw.d.ts +1 -1
- package/dist/src/routes/raw.d.ts.map +1 -1
- package/dist/src/routes/raw.js +66 -12
- package/dist/src/routes/raw.js.map +1 -1
- package/dist/src/routes/read.d.ts +1 -1
- package/dist/src/routes/read.js +1 -1
- package/dist/src/routes/search.d.ts +1 -1
- package/dist/src/routes/search.js +1 -1
- package/dist/src/routes/sources.d.ts +63 -6
- package/dist/src/routes/sources.d.ts.map +1 -1
- package/dist/src/routes/sources.js +494 -382
- package/dist/src/routes/sources.js.map +1 -1
- package/dist/src/routes/upload.d.ts +1 -1
- package/dist/src/routes/upload.js +1 -1
- package/dist/src/routes/write.d.ts +2 -2
- package/dist/src/routes/write.js +2 -2
- package/dist/src/services/BridgeService.d.ts +21 -35
- package/dist/src/services/BridgeService.d.ts.map +1 -1
- package/dist/src/services/BridgeService.js +42 -88
- package/dist/src/services/BridgeService.js.map +1 -1
- package/dist/src/services/FilesystemService.d.ts +4 -3
- package/dist/src/services/FilesystemService.d.ts.map +1 -1
- package/dist/src/services/FilesystemService.js +112 -34
- 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 +132 -43
- package/dist/src/services/declaredSources.d.ts.map +1 -1
- package/dist/src/services/declaredSources.js +118 -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/tools/execCtx.d.ts +2 -2
- package/dist/src/tools/execCtx.js +2 -2
- package/dist/src/tools/fs_read.d.ts.map +1 -1
- package/dist/src/tools/fs_read.js +5 -0
- package/dist/src/tools/fs_read.js.map +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/embeddingBacklog.js +1 -1
- package/dist/src/usecases/embeddingBacklog.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 +99 -1
- 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/syncPointWrites.d.ts +2 -0
- package/dist/src/usecases/syncPointWrites.d.ts.map +1 -1
- package/dist/src/usecases/syncPointWrites.js +5 -1
- package/dist/src/usecases/syncPointWrites.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/usecases/vectorMaintenance.js +1 -1
- package/dist/src/usecases/vectorMaintenance.js.map +1 -1
- package/dist/src/usecases/write/content.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/dist/tools/fs_list.json +1 -1
- package/dist/tools/fs_read.json +1 -1
- package/dist/tools/fs_search.json +1 -1
- package/dist/tools/fs_write.json +1 -1
- package/instructions/brain-model.md +1 -1
- package/package.json +212 -114
- package/rules/brain-rules.md +2 -2
- package/skills/brain-start/SKILL.md +1 -1
- package/skills/connect-business-data/SKILL.md +3 -3
- package/skills/create-package/SKILL.md +26 -26
- package/skills/create-package/references/config-and-credentials.md +16 -13
- package/skills/create-package/references/connectors-and-uri-policies.md +39 -25
- package/skills/create-package/references/manifest-and-discovery.md +12 -12
- package/skills/create-package/references/runtime-and-build.md +19 -19
- package/skills/create-package/references/skills-and-context.md +36 -20
- 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 +21 -21
- package/skills/create-package/references/tools-and-schemas.md +5 -5
- package/skills/create-package/scripts/{check-package.sh → check-package.mjs} +79 -65
- package/skills/create-package/scripts/{verify-loaded.sh → verify-loaded.mjs} +156 -162
- package/skills/git/SKILL.md +53 -51
- package/skills/manage-files/SKILL.md +22 -25
- package/skills/manage-sources/SKILL.md +128 -87
- package/skills/manage-uri-policy/SKILL.md +18 -15
- package/skills/organize/SKILL.md +1 -1
- package/skills/sync/SKILL.md +3 -3
- package/tools/fs_list.json +1 -1
- package/tools/fs_read.json +1 -1
- package/tools/fs_search.json +1 -1
- package/tools/fs_write.json +1 -1
- package/workflows/sources-digest.json +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/docs/drive-overview.md +0 -59
- 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
package/app/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Brain UI
|
|
2
2
|
|
|
3
|
-
React surface contributed by `@neuralis/brain-core`: the Files widget, the `filesystem.change` card, and the host integration entry points the workspace uses to mount them.
|
|
3
|
+
React surface contributed by `@neuralis/brain-core`: the Files widget, the `filesystem.change` card, and the host integration entry points the workspace uses to mount them. The Filesystem dock declares `app/shared/filesystem.png`, retaining Folder as its glyph fallback.
|
|
4
4
|
|
|
5
5
|
## Responsibility
|
|
6
6
|
|
|
@@ -23,9 +23,9 @@ React surface contributed by `@neuralis/brain-core`: the Files widget, the `file
|
|
|
23
23
|
|
|
24
24
|
## Security & scope
|
|
25
25
|
|
|
26
|
-
- Every API call flows through `PackageApiProvider`, which the host scopes by `(projectId, agentId, userId)` from the workspace session. The brain-core route layer enforces feature grants — `
|
|
27
|
-
- File marks and SSE event subscriptions are per-agent. Cross-agent visibility in the tree requires the same feature grants the route layer enforces (`
|
|
28
|
-
- The `filesystem.change` card reads tool input from agent-core's chat store; it never re-issues writes. Approval flows go through the `approve` route, gated by `
|
|
26
|
+
- Every API call flows through `PackageApiProvider`, which the host scopes by `(projectId, agentId, userId)` from the workspace session. The brain-core route layer enforces feature grants — `files.read`, `files.write`, `files.search`, `sources.sync`, `sources.mount` (plus the in-handler escalation `sources.privileged` for privileged roots — a source on a paired device is attached only by its owner under `device.own` — and `sources.policy` for the URI-policy patch), together with `files.readOthers` / `files.writeOthers` for cross-scope access — and source-scope access. The UI is not authoritative.
|
|
27
|
+
- File marks and SSE event subscriptions are per-agent. Cross-agent visibility in the tree requires the same feature grants the route layer enforces (`files.readOthers`).
|
|
28
|
+
- The `filesystem.change` card reads tool input from agent-core's chat store; it never re-issues writes. Approval flows go through the `approve` route, gated by `files.write`.
|
|
29
29
|
|
|
30
30
|
## Gotchas
|
|
31
31
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import React, { memo, useCallback, useMemo } from 'react';
|
|
14
14
|
import { FileText } from 'lucide-react';
|
|
15
15
|
import { CardShell, ToolStatusIcon } from '@neuralis/agent-core/app/chat';
|
|
16
|
-
import { computeLineCounts, isContentAction, parseToolInput } from './diffUtils';
|
|
16
|
+
import { computeLineCounts, computeStreamingHunks, extractContentString, isContentAction, parseToolInput, streamingWriteVerb } from './diffUtils';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The chrome this card actually renders: its OWN `renderHeader` (a single `px-3 py-1` row
|
|
@@ -77,8 +77,11 @@ function cacheEntryFor(tool: object, status: unknown): FsHeightCacheEntry {
|
|
|
77
77
|
* `FilesystemChangeCard` (the estimate has to agree with the renderer or it is
|
|
78
78
|
* guessing):
|
|
79
79
|
*
|
|
80
|
-
* - pending
|
|
81
|
-
*
|
|
80
|
+
* - pending content → the 90 CEILING: a content write renders its diff rows
|
|
81
|
+
* while the arguments stream, growing toward the 90 cap
|
|
82
|
+
* every delta, so the reserve is the cap from the first
|
|
83
|
+
* delta that names the verb (over-reserve, the cheap side).
|
|
84
|
+
* - pending, otherwise → the "Running…" mount state / one-line preview (32).
|
|
82
85
|
* - non-content op → `NonContentOpView`, one text row (32).
|
|
83
86
|
* - content MODIFY → the 90 CEILING, deliberately (H2 pre-reserve): the real
|
|
84
87
|
* baseline→current LCS diff arrives from an async fetch and
|
|
@@ -101,7 +104,14 @@ function filesystemCompactBodyHeight(
|
|
|
101
104
|
|
|
102
105
|
let body: number;
|
|
103
106
|
if (isPending) {
|
|
104
|
-
|
|
107
|
+
if (!isPendingContentWrite(tool.input)) {
|
|
108
|
+
// Not cached while the input still streams: the verb can arrive on a later
|
|
109
|
+
// delta under the same status, and a cached 32 would then under-reserve.
|
|
110
|
+
if (typeof tool.input === 'string') return COMPACT_FALLBACK_ROW_PX;
|
|
111
|
+
body = COMPACT_FALLBACK_ROW_PX;
|
|
112
|
+
} else {
|
|
113
|
+
body = FILESYSTEM_CARD_COMPACT_BODY_PX;
|
|
114
|
+
}
|
|
105
115
|
} else {
|
|
106
116
|
const input = parseToolInput(tool.input);
|
|
107
117
|
const action = input && typeof input.action === 'string' ? input.action : undefined;
|
|
@@ -127,14 +137,42 @@ function filesystemCompactBodyHeight(
|
|
|
127
137
|
return body;
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
/**
|
|
141
|
+
* The pending card renders a diff (see `FilesystemChangeCard` `pendingHunks`): a
|
|
142
|
+
* streaming string whose `fs_write` content verb has opened, or a settled input
|
|
143
|
+
* that is a content action or a create.
|
|
144
|
+
*/
|
|
145
|
+
function isPendingContentWrite(raw: unknown): boolean {
|
|
146
|
+
if (streamingWriteVerb(raw)) return true;
|
|
147
|
+
const input = parseToolInput(raw);
|
|
148
|
+
if (!input) return false;
|
|
149
|
+
const action = typeof input.action === 'string' ? input.action : undefined;
|
|
150
|
+
return action ? isContentAction(action) : extractContentString(input) !== null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The expanded card's row cap — `FilesystemChangeCard`'s `EXPANDED_MAX_DIFF_LINES`. */
|
|
154
|
+
const EXPANDED_MAX_DIFF_LINES = 200;
|
|
155
|
+
|
|
130
156
|
/**
|
|
131
157
|
* EXPANDED body — the cap is gone and the body is the whole diff. This branch is not an
|
|
132
158
|
* optimization — it is the single largest measured defect in the model. Live-measured
|
|
133
159
|
* 2026-08-02: this card estimated **114px** while rendering **1479px** expanded, a
|
|
134
160
|
* **−1353px** under-reserve, which paints the following row directly on top of it. Line
|
|
135
161
|
* counts come from the SAME `computeLineCounts` the header badge uses.
|
|
162
|
+
*
|
|
163
|
+
* A STREAMING content write (the input is still the delta string) renders the TAIL of
|
|
164
|
+
* the rows `computeStreamingHunks` builds, up to the 200-row cap plus the "lines skipped"
|
|
165
|
+
* row — so it is estimated from the same rows, input-only and exact (no context
|
|
166
|
+
* allowance), and NEVER cached: the next delta grows it under the same status.
|
|
136
167
|
*/
|
|
137
168
|
function filesystemExpandedBodyHeight(tool: { input?: unknown }, status: unknown): number {
|
|
169
|
+
const streaming = typeof tool.input === 'string' ? computeStreamingHunks(tool.input) : null;
|
|
170
|
+
if (streaming) {
|
|
171
|
+
let rows = 0;
|
|
172
|
+
for (const hunk of streaming) rows += hunk.lines.length;
|
|
173
|
+
const shown = rows > EXPANDED_MAX_DIFF_LINES ? EXPANDED_MAX_DIFF_LINES + 1 : rows;
|
|
174
|
+
return Math.max(FILESYSTEM_CARD_COMPACT_BODY_PX, DIFF_BODY_PADDING_PX + shown * DIFF_LINE_PX);
|
|
175
|
+
}
|
|
138
176
|
const entry = cacheEntryFor(tool as object, status);
|
|
139
177
|
if (entry.expanded !== undefined) return entry.expanded;
|
|
140
178
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
22
22
|
import type { CardProps } from '@neuralis/agent-core/app/chat';
|
|
23
|
-
import { previewInput, extractPathsFromToolCall } from '@neuralis/agent-core/app/chat';
|
|
23
|
+
import { previewInput, extractPathsFromToolCall, extractStreamingField } from '@neuralis/agent-core/app/chat';
|
|
24
24
|
import { useChatHost, useHasRowIdentity, useRowDisclosureValue } from '@neuralis/agent-core/app/chat';
|
|
25
25
|
import { useWorkspaceChatStore } from '@neuralis/agent-core/app/chat/store';
|
|
26
26
|
import { readFiles } from '../filesystem/api';
|
|
@@ -36,12 +36,14 @@ import {
|
|
|
36
36
|
computeTextInsertDiff,
|
|
37
37
|
computeLineCounts,
|
|
38
38
|
computeLcsDiff,
|
|
39
|
+
computeStreamingHunks,
|
|
39
40
|
countHunks,
|
|
40
41
|
detectLanguage,
|
|
41
42
|
isContentAction,
|
|
42
43
|
extractContentString,
|
|
43
44
|
extractResultVersionDiff,
|
|
44
45
|
parseToolInput,
|
|
46
|
+
tailDiffHunks,
|
|
45
47
|
} from './diffUtils';
|
|
46
48
|
import type { DiffHunk } from './diffUtils';
|
|
47
49
|
|
|
@@ -91,9 +93,15 @@ export const FilesystemChangeCard = memo(function FilesystemChangeCard({ tool, s
|
|
|
91
93
|
const maxDiffLines = isCollapsedRow ? COMPACT_MAX_DIFF_LINES : EXPANDED_MAX_DIFF_LINES;
|
|
92
94
|
|
|
93
95
|
const input = useMemo(() => parseToolInput(tool.input), [tool.input]);
|
|
96
|
+
// While the arguments stream, `tool.input` is the partial STRING: the uri is
|
|
97
|
+
// read out of it (head-windowed, trailing-partial) so the header names the file
|
|
98
|
+
// before the call settles.
|
|
94
99
|
const uri = useMemo(() => {
|
|
95
100
|
const paths = extractPathsFromToolCall(tool);
|
|
96
|
-
return paths[0]
|
|
101
|
+
return paths[0]
|
|
102
|
+
|| (input ? (strField(input, 'uri') || strField(input, 'path')) : undefined)
|
|
103
|
+
|| extractStreamingField(tool.input, 'uri')
|
|
104
|
+
|| extractStreamingField(tool.input, 'path');
|
|
97
105
|
}, [tool, input]);
|
|
98
106
|
|
|
99
107
|
const normalizedStatus = String(status || '').toLowerCase();
|
|
@@ -129,7 +137,7 @@ export const FilesystemChangeCard = memo(function FilesystemChangeCard({ tool, s
|
|
|
129
137
|
|
|
130
138
|
const hunks = useMemo((): DiffHunk[] => {
|
|
131
139
|
if (!input) return [];
|
|
132
|
-
if (isPending) return []; //
|
|
140
|
+
if (isPending) return []; // the pending branch renders `pendingHunks`
|
|
133
141
|
return computeHunks(action, input);
|
|
134
142
|
}, [action, input, isPending]);
|
|
135
143
|
|
|
@@ -139,14 +147,25 @@ export const FilesystemChangeCard = memo(function FilesystemChangeCard({ tool, s
|
|
|
139
147
|
host.openFilesystemWidget?.(agentId, clickedUri);
|
|
140
148
|
}, [conversationAgentId, host]);
|
|
141
149
|
|
|
142
|
-
// Streaming
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
150
|
+
// Streaming diff — while the call runs, a CONTENT write renders its red/green
|
|
151
|
+
// rows from the input as it arrives: a settled input through the same builders
|
|
152
|
+
// as the completed card, a partial string through `computeStreamingHunks`
|
|
153
|
+
// (head-windowed carrier reads, O(window) per delta, never the LCS). The rows
|
|
154
|
+
// follow the text being written (`tailDiffHunks`). The input itself is never
|
|
155
|
+
// truncated. `null` = no content verb (yet): the plain `previewInput` preview.
|
|
156
|
+
const pendingHunks = useMemo((): DiffHunk[] | null => {
|
|
157
|
+
if (!isPending) return null;
|
|
158
|
+
if (input) return !action || isContentAction(action) ? computeHunks(action, input) : null;
|
|
159
|
+
return computeStreamingHunks(tool.input);
|
|
160
|
+
}, [isPending, input, action, tool.input]);
|
|
146
161
|
const streamContent = useMemo(() => {
|
|
147
|
-
if (!isPending) return undefined;
|
|
162
|
+
if (!isPending || pendingHunks) return undefined;
|
|
148
163
|
return previewInput(tool.input, true);
|
|
149
|
-
}, [isPending, tool.input]);
|
|
164
|
+
}, [isPending, pendingHunks, tool.input]);
|
|
165
|
+
const pendingView = useMemo(
|
|
166
|
+
() => (pendingHunks && pendingHunks.length > 0 ? tailDiffHunks(pendingHunks, maxDiffLines) : null),
|
|
167
|
+
[pendingHunks, maxDiffLines],
|
|
168
|
+
);
|
|
150
169
|
|
|
151
170
|
// --- Real baseline→current diff for completed MODIFY (not create) ---
|
|
152
171
|
// The tool result carries only `version.diff` COUNTS, not the old content, so
|
|
@@ -227,7 +246,7 @@ export const FilesystemChangeCard = memo(function FilesystemChangeCard({ tool, s
|
|
|
227
246
|
const displayHunks = realHunks ?? hunks;
|
|
228
247
|
const displayCounts = realHunks ? countHunks(realHunks) : lineCounts;
|
|
229
248
|
|
|
230
|
-
// --- Streaming state:
|
|
249
|
+
// --- Streaming state: the input-only diff, or the plain preview ---
|
|
231
250
|
if (isPending) {
|
|
232
251
|
return (
|
|
233
252
|
<FilesystemCardShell
|
|
@@ -239,7 +258,11 @@ export const FilesystemChangeCard = memo(function FilesystemChangeCard({ tool, s
|
|
|
239
258
|
isInterrupted={false}
|
|
240
259
|
onUriClick={uri ? handleOpenUri : undefined}
|
|
241
260
|
>
|
|
242
|
-
|
|
261
|
+
{pendingView ? (
|
|
262
|
+
<DiffView hunks={pendingView} language={language} maxLines={maxDiffLines} />
|
|
263
|
+
) : (
|
|
264
|
+
<StreamingContent content={streamContent} />
|
|
265
|
+
)}
|
|
243
266
|
</FilesystemCardShell>
|
|
244
267
|
);
|
|
245
268
|
}
|
package/app/cards/README.md
CHANGED
|
@@ -10,15 +10,16 @@ Filesystem agent-card: an IDE-style diff view rendered for `fs_write` tool calls
|
|
|
10
10
|
|
|
11
11
|
## Public Surface
|
|
12
12
|
|
|
13
|
-
- `FilesystemChangeCard` — `(props: CardProps) => JSX.Element`. Memoised; reads its
|
|
14
|
-
- **
|
|
13
|
+
- `FilesystemChangeCard` — `(props: CardProps) => JSX.Element`. Memoised; reads paths via `extractPathsFromToolCall` and, while the call streams, its uri via `extractStreamingField` and its rows via `computeStreamingHunks` (below), all through `@neuralis/agent-core/app/chat`.
|
|
14
|
+
- **The pending card renders the diff while the arguments stream.** A content `fs_write` (`edit` / `insert` / `lines` / `overwrite` / `create`) shows the uri in its header and red/green rows from the partial input: the closed `edit[]` elements (`extractSettledArrayItems` + `extractJsonStringField`) plus the still-open tail element (`extractStreamingArrayTail` + `extractStreamingField`), or the verb's one text field. The rows are the input-only line split — `computeLcsDiff` never runs while pending — cut to the LAST `maxDiffLines` rows (`tailDiffHunks`, the dropped head counted) so the card follows the text being written; the `+N/−M` badge stays off until the call settles. A structural verb (move / copy / …) and a call whose verb has not arrived keep the one-line `previewInput` preview. No animation, no remount: the row is the same card, re-rendered per delta.
|
|
15
|
+
- **Never re-scan the whole growing input on every `tool_use_delta`.** While the tool is streaming, `tool.input` is a raw accumulating JSON STRING, and both readers ran over ALL of it per delta — O(n²) across a large `fs_create`, and one of the per-commit costs behind the React #185 `rate × saturation` class (see agent-core `app/chat/ui/timeline/README.md`). Every pending read is an O(1)/O(window) guard and none truncates the input — both now backed by agent-core's shared streaming-helper carrier (`domain/toolPresentation.ts`, streaming parity): `parseToolInput` (in `diffUtils.ts` — the fs-specific NAME, DELEGATING to the shared `parseSettledObject`, the ONE `}`-tail-gate implementation; still the one owner shared with the height model so estimate and render discriminate variants from the SAME parse) skips the guaranteed-to-throw parse of partial JSON while a completed object still parses in full, the streaming rows read only the 32 000-char `LIVE_PARSE_WINDOW` head (a longer call stops at the window; nothing past it is parsed), and `streamContent` is the shared `previewInput(tool.input, true)` (the same head window over `extractToolDetails`, which caps its own output at 20 000 chars — the preview is byte-identical; the former private copy of this window here is gone). Do not remove a guard, do not re-inline a private copy, and do not "extend" them into truncating the stored input.
|
|
15
16
|
- **The compact diff mount is CLAMPED to 8 rows** (`COMPACT_MAX_DIFF_LINES`): the compact body clips at 90px, so at most ~6 rows are ever visible, and mounting the full 200-row `DiffLineRow` set into a clipped body was pure per-mount cost on the scroll path (H6, residual-slide audit 2026-08-07). The collapsed state is read via agent-core's READ-ONLY `useRowDisclosureValue('card')` + `useHasRowIdentity()` (never a second `useRowDisclosure` — that would fight the owning `CardShell` mirror); outside the virtualized timeline (transcript overlays, the Pending tab) there is no row identity and the full 200-line render stands. The clamp never changes the clipped visible height (8 × 18px ≥ 90px cap).
|
|
16
17
|
- `FilesystemCardShell` — filesystem-specific header atop the unified `CardShell`. Two status inputs that must never be conflated: `isPending` is the tool CALL running (the ONLY spinner source) and `reviewPending` is the RESULT having landed `pending_review` — a governance state frozen in the result snapshot, rendered as the settled check's accessible name (`pending review`, `ToolStatusIcon statusLabel`) on every op alike. `CardShell` receives `committed` for such a result: a `pending_*` word there would re-arm its stream-follow pin and, through `resolveCardTone`'s substring match, the spinner.
|
|
17
|
-
- `estimateFilesystemChangeCardHeight` (exported from `FilesystemCardShell.tsx`) — the card's OWN row-height model, handed to agent-core's card registry at install time. agent-core's timeline asks the registry for it before a row has been measured; a card with no model falls back to a generic constant. It is declared next to the chrome it describes because it drifted badly when it lived in the timeline: the host-side table once claimed 240 against a real ~117 — a live-measured `first-measure` delta of **−181px**, the worst of any builtin. The **COMPACT branch is variant-aware** (H1, residual-slide audit 2026-08-07): the body is content-height clipped at `maxHeight: 90`, NOT a constant, so the model is `27 + min(90, bodyContent)` where `bodyContent` mirrors the render dispatch — `18 × diffLines` for a CREATE's input-derived rows, `32` for the one-line fallbacks (pending
|
|
18
|
+
- `estimateFilesystemChangeCardHeight` (exported from `FilesystemCardShell.tsx`) — the card's OWN row-height model, handed to agent-core's card registry at install time. agent-core's timeline asks the registry for it before a row has been measured; a card with no model falls back to a generic constant. It is declared next to the chrome it describes because it drifted badly when it lived in the timeline: the host-side table once claimed 240 against a real ~117 — a live-measured `first-measure` delta of **−181px**, the worst of any builtin. The **COMPACT branch is variant-aware** (H1, residual-slide audit 2026-08-07): the body is content-height clipped at `maxHeight: 90`, NOT a constant, so the model is `27 + min(90, bodyContent)` where `bodyContent` mirrors the render dispatch — `18 × diffLines` for a CREATE's input-derived rows, `32` for the one-line fallbacks (a pending call with no content verb yet, "Done"/"Operation failed", non-content ops), the **90 CEILING for a PENDING content write** (its rows grow toward the cap every delta; decided from the same `streamingWriteVerb` / `parseToolInput` the render reads, and a still-undecided streaming estimate is never memoized), and the **90 CEILING for a completed content MODIFY deliberately** (H2 pre-reserve: the real LCS diff arrives from an async fetch and adds context rows the input-derived count cannot see — ceiling bias accepted so the arrival does not resize the row in view). The previous constant-117 model over-reserved every short-bodied variant by +18…+72. The **EXPANDED branch** models the diff body from `computeLineCounts` (the same helper the header badge uses) with a deliberate upward margin, and is not optional: returning the compact height for a row the disclosure store says is expanded measured **−1353px** live (114 estimated against 1479 rendered), and an under-reserve does not leave a gap — it paints the next row on top of this one. The memo is keyed by the tool object **with the status it computed at** — the tool mutates by reference across pending→settle, so a reference-only memo served the pending height after settle (the H3 class). Keep both branches in step with `renderHeader` / `compactHeight` / `DiffLineRow`'s `h-[18px]`, and keep the export a module-level reference (the registry compares estimator identity). Contract: agent-core `app/chat/cards/README.md` → "Card height model".
|
|
18
19
|
- `DiffView` — green/red line renderer with single-column line numbers, truncation and skipped-context markers.
|
|
19
20
|
- `NonContentOpView` — summary renderer for `set_fields`, `move`, `delete`, `revert`.
|
|
20
21
|
- `diffLine.tsx` — the **shared single-column diff-line primitive**: `DiffLineRow({ lineNo?, type, content })` + the `DIFF_LINE_BG` palette (`add` emerald / `remove` red / `context` colourless). ONE owning file for the green/red/colourless look + the line-number gutter. Its only consumer today is this card's `DiffView` — the Files-widget review diff moved to `@codemirror/merge` (`ReviewDiffView`) and the old hand-rolled `app/filesystem/ui/DiffView.tsx` was deleted. Keep it a standalone primitive: it is still the shared shape any future single-column diff renderer should reuse. No `+`/`-` prefix char — the row background conveys add/remove. `DiffView` owns one shared content-width wrapper; every row fills it so shorter coloured lines also cover the horizontal scroll extent. **Gutter is git / VS Code unified-diff style:** every row numbered — REMOVE rows show their OLD (pre-edit) file line number, ADD + CONTEXT show the NEW one (so the column intentionally jumps back at a block replacement: old positions then new).
|
|
21
|
-
- Diff utilities (`diffUtils.ts`): input-derived hunk builders (`computeCreateDiff`, `computeReplaceTextDiff`, `computeMultiEditDiff`, `computeUpdateDiff`, `computeTextInsertDiff`, `computeLineCounts`) AND the real baseline→current LCS (`computeLcsDiff` + `countHunks`) shared with Changes. Callers must bound the O(n·m) matrix before invoking `computeLcsDiff`; pending previews use `pendingDiffWithinBudget`, because a byte cap alone does not bound newline-dense text. Plus `detectLanguage`, `isContentAction`, `extractContentString`, and `parseToolInput` (the streaming-tolerant `}`-tail-checked input parse — the fs-specific name for agent-core's shared `parseSettledObject`; ONE owner within brain-core for the card AND the height model) — which now also runs `normalizeCardInput`, the ONE place two write vocabularies are reconciled. `fs_write` carries its intent in KEYS (`edit`, `insert`, `lines`, `overwrite`, `create`, `fields`, `move`, `copy`, `mkdir`, `revert`, `delete`); the old pair carried it in `action` plus a flat field per shape. A verb-key input is PROJECTED onto the shape the views already read (`edit[].old_string/new_string` → the `replace_text` `edits[]` pair, so the red/green hunk appears before the tool result lands and a pending approval renders the PROPOSITION), and a row written by `fs_create`/`fs_modify` passes through untouched — which is what keeps the stored history rendering. A composed call previews the most informative verb (`edit` before `lines`, `insert`, `overwrite`, `create`). Pinned by `test/ui/verbKeyCardInput.test.ts`. Types: `DiffHunk`, `DiffLine`, `DiffLineType`.
|
|
22
|
+
- Diff utilities (`diffUtils.ts`): input-derived hunk builders (`computeCreateDiff`, `computeReplaceTextDiff`, `computeMultiEditDiff`, `computeUpdateDiff`, `computeTextInsertDiff`, `computeLineCounts`) AND the real baseline→current LCS (`computeLcsDiff` + `countHunks`) shared with Changes. Callers must bound the O(n·m) matrix before invoking `computeLcsDiff`; pending previews use `pendingDiffWithinBudget`, because a byte cap alone does not bound newline-dense text. Plus `detectLanguage`, `isContentAction`, `extractContentString`, and `parseToolInput` (the streaming-tolerant `}`-tail-checked input parse — the fs-specific name for agent-core's shared `parseSettledObject`; ONE owner within brain-core for the card AND the height model) — which now also runs `normalizeCardInput`, the ONE place two write vocabularies are reconciled. `fs_write` carries its intent in KEYS (`edit`, `insert`, `lines`, `overwrite`, `create`, `fields`, `move`, `copy`, `mkdir`, `revert`, `delete`); the old pair carried it in `action` plus a flat field per shape. A verb-key input is PROJECTED onto the shape the views already read (`edit[].old_string/new_string` → the `replace_text` `edits[]` pair, so the red/green hunk appears before the tool result lands and a pending approval renders the PROPOSITION), and a row written by `fs_create`/`fs_modify` passes through untouched — which is what keeps the stored history rendering. A composed call previews the most informative verb (`edit` before `lines`, `insert`, `overwrite`, `create`). Pinned by `test/ui/verbKeyCardInput.test.ts`. The STREAMING twin of that projection is `streamingWriteVerb` (the first `fs_write` content-verb key whose value has opened in the partial string's head window — the card's pending branch AND the height model decide from it) + `computeStreamingHunks` (the input-only builders over the carrier reads, `null` when no content verb has arrived) + `tailDiffHunks`. Types: `DiffHunk`, `DiffLine`, `DiffLineType`, `StreamingWriteVerb`.
|
|
22
23
|
- Composer-aggregation ownership (`diffUtils.ts`, the composer-contract inversion): `extractResultVersionDiff` (the ONE `structuredContent.version.diff` reader — the card's `+N/−M` header badge AND the composer extractor share it; moved here from both its former private copy in `FilesystemChangeCard` and agent-core's deleted `extractResultDiff`), `extractFilesystemComposerChanges` (paths via agent-core's shared `extractPathsFromToolCall` + the former `deriveOpenPath` input-uri fallback for `ok:false` results; diff on the first entry), and `FILESYSTEM_COMPOSER_CONTRACT` (`{ tab: 'changes', changes }`, module-level stable) — registered in `installFilesystemHostComponents.tsx` so agent-core's Changes tab + post-stream summary learn about fs changes from the card REGISTRY, never from literals. Pinned by `test/ui/composerContract.test.ts` (parity: success uri+diff, diff-less results, `ok:false` input fallback, files[] multi-path, empty, and the reducer's string-input forms — a COMPLETE JSON string parses via the settled-object gate, a PARTIAL in-flight string yields nothing and is covered by agent-core's aggregate `openPath || label` fallback row).
|
|
23
24
|
|
|
24
25
|
## How it plugs into the package-system kernel
|
|
@@ -44,4 +45,4 @@ Filesystem agent-card: an IDE-style diff view rendered for `fs_write` tool calls
|
|
|
44
45
|
|
|
45
46
|
## Tests
|
|
46
47
|
|
|
47
|
-
`test/ui/filesystemCardHeight.test.ts` pins the card's height model; `test/ui/filesystemCardPendingReview.test.tsx` renders the card (jsdom, a cast `ChatHostProvider` stub) and pins the `pending_review`-is-a-name floor with an in-flight control; `test/ui/composerContract.test.ts` pins the composer extractor. The chat registry and the settled-row animation guard live in agent-core (`app/chat/ui/timeline/__tests__/settledRowsNoInfiniteAnimation.test.tsx`). Diff utilities have inline unit coverage.
|
|
48
|
+
`test/ui/filesystemCardHeight.test.ts` pins the card's height model; `test/ui/filesystemCardStreaming.test.tsx` renders a streaming `fs_write` edit at three cut points (uri + red/green rows), a >32K create stopping at the parse window, the pending estimate against the rendered rows and a structural-verb control; `test/ui/filesystemCardPendingReview.test.tsx` renders the card (jsdom, a cast `ChatHostProvider` stub) and pins the `pending_review`-is-a-name floor with an in-flight control; `test/ui/composerContract.test.ts` pins the composer extractor. The chat registry and the settled-row animation guard live in agent-core (`app/chat/ui/timeline/__tests__/settledRowsNoInfiniteAnimation.test.tsx`). Diff utilities have inline unit coverage.
|
package/app/cards/diffUtils.ts
CHANGED
|
@@ -16,7 +16,15 @@
|
|
|
16
16
|
* Changes tab AND the chat card's completed-MODIFY branch, mirroring the file editor.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
extractJsonStringField,
|
|
21
|
+
extractPathsFromToolCall,
|
|
22
|
+
extractSettledArrayItems,
|
|
23
|
+
extractStreamingArrayTail,
|
|
24
|
+
extractStreamingField,
|
|
25
|
+
LIVE_PARSE_WINDOW,
|
|
26
|
+
parseSettledObject,
|
|
27
|
+
} from '@neuralis/agent-core/app/chat';
|
|
20
28
|
import { splitDiffLines } from '@neuralis/brain-core/domain/pending-change';
|
|
21
29
|
import type {
|
|
22
30
|
CardComposerContract,
|
|
@@ -185,6 +193,96 @@ export function parseToolInput(raw: unknown): Record<string, unknown> | null {
|
|
|
185
193
|
return normalizeCardInput(parseSettledObject(raw));
|
|
186
194
|
}
|
|
187
195
|
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// The STREAMING preview — `fs_write` while its arguments still arrive
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
/** The `fs_write` content verbs a streaming card can preview, in schema order. */
|
|
201
|
+
export type StreamingWriteVerb = 'edit' | 'insert' | 'lines' | 'overwrite' | 'create';
|
|
202
|
+
|
|
203
|
+
const STREAMING_VERB_RE = /"(edit|insert|lines|overwrite|create)"\s*:\s*[[{]/;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The content verb of an `fs_write` call whose input is still the accumulating
|
|
207
|
+
* `tool_use_delta` STRING — the first verb KEY whose value has opened, read from
|
|
208
|
+
* the head window. A key inside a string value cannot match (its quotes arrive
|
|
209
|
+
* escaped). `undefined` until one arrives, for a structural verb, and for any
|
|
210
|
+
* non-string input (a settled input is read through `parseToolInput`).
|
|
211
|
+
*
|
|
212
|
+
* ONE owner: the card's pending branch and the height model both decide from it.
|
|
213
|
+
*/
|
|
214
|
+
export function streamingWriteVerb(input: unknown): StreamingWriteVerb | undefined {
|
|
215
|
+
if (typeof input !== 'string') return undefined;
|
|
216
|
+
const head = input.length > LIVE_PARSE_WINDOW ? input.slice(0, LIVE_PARSE_WINDOW) : input;
|
|
217
|
+
return STREAMING_VERB_RE.exec(head)?.[1] as StreamingWriteVerb | undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Line-split hunks for a STREAMING `fs_write` content verb, or `null` when no
|
|
222
|
+
* content verb has arrived (the caller keeps its plain preview). Edit pairs come
|
|
223
|
+
* from the closed `edit[]` elements plus the still-open tail element; the other
|
|
224
|
+
* verbs stream their one text field. Every read is head-windowed
|
|
225
|
+
* (`LIVE_PARSE_WINDOW`), never a `JSON.parse`, and the hunks use the input-only
|
|
226
|
+
* builders — never `computeLcsDiff`, whose O(n·m) matrix has no place per delta.
|
|
227
|
+
*/
|
|
228
|
+
export function computeStreamingHunks(input: unknown): DiffHunk[] | null {
|
|
229
|
+
const verb = streamingWriteVerb(input);
|
|
230
|
+
if (!verb) return null;
|
|
231
|
+
if (verb === 'edit') {
|
|
232
|
+
const edits: Array<{ find: string; replace: string }> = [];
|
|
233
|
+
for (const item of extractSettledArrayItems(input, 'edit')) {
|
|
234
|
+
edits.push({
|
|
235
|
+
find: extractJsonStringField(item, 'old_string') ?? '',
|
|
236
|
+
replace: extractJsonStringField(item, 'new_string') ?? '',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const tail = extractStreamingArrayTail(input, 'edit');
|
|
240
|
+
if (tail) {
|
|
241
|
+
edits.push({ find: readStreamingString(tail, 'old_string'), replace: readStreamingString(tail, 'new_string') });
|
|
242
|
+
}
|
|
243
|
+
return computeMultiEditDiff(edits);
|
|
244
|
+
}
|
|
245
|
+
if (verb === 'insert') return computeTextInsertDiff(readStreamingString(input, 'insert_text'));
|
|
246
|
+
if (verb === 'lines') return computeUpdateDiff(readStreamingString(input, 'content'));
|
|
247
|
+
return computeCreateDiff(readStreamingString(input, 'content'));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* `extractStreamingField`, tolerant of a delta that ended inside an escape: an odd
|
|
252
|
+
* trailing backslash has no complete capture, so the value would vanish for one
|
|
253
|
+
* delta and the rows would flicker. The half escape is left for the next delta.
|
|
254
|
+
*/
|
|
255
|
+
function readStreamingString(text: unknown, field: string): string {
|
|
256
|
+
const value = extractStreamingField(text, field);
|
|
257
|
+
if (value !== undefined || typeof text !== 'string' || !text.endsWith('\\')) return value ?? '';
|
|
258
|
+
return extractStreamingField(text.slice(0, -1), field) ?? '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The LAST `maxLines` rows of `hunks` — a streaming card follows the text being
|
|
263
|
+
* written, so the cut drops the HEAD and says how many rows it dropped (DiffView's
|
|
264
|
+
* own `maxLines` cut drops the tail, right for a settled card).
|
|
265
|
+
*/
|
|
266
|
+
export function tailDiffHunks(hunks: DiffHunk[], maxLines: number): DiffHunk[] {
|
|
267
|
+
let total = 0;
|
|
268
|
+
for (const h of hunks) total += h.lines.length;
|
|
269
|
+
if (total <= maxLines) return hunks;
|
|
270
|
+
const kept: DiffHunk[] = [];
|
|
271
|
+
let budget = maxLines;
|
|
272
|
+
for (let i = hunks.length - 1; i >= 0 && budget > 0; i--) {
|
|
273
|
+
const h = hunks[i];
|
|
274
|
+
if (h.lines.length <= budget) {
|
|
275
|
+
kept.unshift(h);
|
|
276
|
+
budget -= h.lines.length;
|
|
277
|
+
} else {
|
|
278
|
+
kept.unshift({ skippedBefore: 0, lines: h.lines.slice(h.lines.length - budget) });
|
|
279
|
+
budget = 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
kept[0] = { ...kept[0], skippedBefore: total - maxLines };
|
|
283
|
+
return kept;
|
|
284
|
+
}
|
|
285
|
+
|
|
188
286
|
// ---------------------------------------------------------------------------
|
|
189
287
|
// Real baseline → current diff (LCS) — shared by the Changes tab + the chat card
|
|
190
288
|
// ---------------------------------------------------------------------------
|
|
@@ -31,6 +31,13 @@ export interface FilesystemHostValue {
|
|
|
31
31
|
onEvent: (event: unknown) => void,
|
|
32
32
|
onConnChange?: () => void,
|
|
33
33
|
): () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to paired-device state TRANSITIONS (`devices` channel of the same
|
|
36
|
+
* hub): the frame is `{deviceId, status}` only — the subscriber re-reads its
|
|
37
|
+
* own lists (source summaries, a device `select`). The hub decides who hears
|
|
38
|
+
* which device. Optional: a host without it gives no live device updates.
|
|
39
|
+
*/
|
|
40
|
+
subscribeDeviceEvents?(projectId: string, onEvent: (event: unknown) => void): () => void;
|
|
34
41
|
isDocker?: boolean;
|
|
35
42
|
/**
|
|
36
43
|
* Optional: the signed-in user's identity, used by the Sources "Add Source"
|
|
@@ -40,8 +47,8 @@ export interface FilesystemHostValue {
|
|
|
40
47
|
currentUser?: { name?: string; email?: string };
|
|
41
48
|
/**
|
|
42
49
|
* R2b — the signed-in user's role + ordinal priority within the active
|
|
43
|
-
* project. Used to HIDE owner/admin-only controls (the
|
|
44
|
-
*
|
|
50
|
+
* project. Used to HIDE owner/admin-only controls (the package-folders field,
|
|
51
|
+
* the package access-feature override) for non-owner callers.
|
|
45
52
|
* UX-only: the server route is always the enforcement floor. Omitted ⇒ the
|
|
46
53
|
* controls hide (deny-by-default). Owner/admin-effective = priority ≤ 2.
|
|
47
54
|
*/
|
|
@@ -59,6 +66,21 @@ export interface FilesystemHostValue {
|
|
|
59
66
|
* greyed-out or locked row). Omitted / empty ⇒ hide, deny-by-default.
|
|
60
67
|
*/
|
|
61
68
|
grantedFeatures?: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Open Admin → Packages (project scope) for this agent's window — where a
|
|
71
|
+
* package manager changes a package's trust, builds it or gates it with a
|
|
72
|
+
* feature. Optional: a host without it shows no link.
|
|
73
|
+
*/
|
|
74
|
+
openAdminPackages?(agentId: string): void;
|
|
75
|
+
/**
|
|
76
|
+
* The folder picker's device asks, both agent-core routes: record a folder on
|
|
77
|
+
* the caller's OWN device for its owner to allow there (`POST
|
|
78
|
+
* devices/:id/requests`), and how many asks that device holds for this
|
|
79
|
+
* project (`GET devices/:id/requests`; `null` = could not ask). The grant
|
|
80
|
+
* stays on the machine. Optional: a host without them shows the CLI line only.
|
|
81
|
+
*/
|
|
82
|
+
requestDeviceFolder?(input: { deviceId: string; path: string; list: 'read' | 'write'; projectId: string; agentId: string }): Promise<{ recorded: boolean; reason?: string }>;
|
|
83
|
+
countDeviceFolderRequests?(input: { deviceId: string; projectId: string; agentId: string }): Promise<number | null>;
|
|
62
84
|
/**
|
|
63
85
|
* Optional: resolve a package's dock-icon by slug. Used by the filetree
|
|
64
86
|
* to show the package icon on `packages://<slug>/` and `data://<slug>/`
|
package/app/filesystem/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Files widget package — the React surface mounted by the host as the brain-core
|
|
|
34
34
|
|
|
35
35
|
## Pending Changes tab
|
|
36
36
|
|
|
37
|
-
`ui/PendingChangesPanel.tsx` is the **Pending Changes** view in the Files widget — a journal of revertible filesystem changes that an agent has made (the durable backend is `src/store/PendingChangeStore.ts`; the route is `pending`, see `src/routes/README.md`). It offers two genuinely distinct views: a **compact list** (filename + full folder path, no content fetch) and **diff cards** (the default — opened inline, each card loads its hunks lazily from ONE fresh `preview` (baseline + current state, the record's own fingerprint) through `hooks/useVisibleDiffCards.ts`; on a source-revision change or a virtualizer remount the card keeps its last preview on screen while the fresh one loads, and the index badge shows only for the current key's answer; `+N/−M` counts are computed from the loaded hunks when the record carries none). Records are ordered **oldest-first** so the review walks first→last. A focused-card cursor (◄/► header buttons, plus `↑`/`↓`/`←`/`→`, `a`=approve, `r`=revert, `x`=dismiss when the panel is focused) an **Approve N** (two-step confirm; only records that need no acknowledgement) button and an **M need confirmation** button make a full sweep fast — the latter opens `PendingReviewDialog`, grouped by reason, 100 records per batch; a finished batch disables its group buttons and offers **Next batch (K left)**; approving advances to the next change. Each live (non-orphaned) row also carries a neutral-tone **Dismiss** (x) button — "accept-and-stop-reviewing": the change is kept exactly as it is (a present file stays on disk + indexed, an agent's delete stays deleted) and only the review entry resolves, with no connector write (the `pending` route's `dismiss` action; see `src/routes/README.md`). Clicking a filename opens the file in the tree (never replacing the panel). A filter bar narrows by source / agent / conversation, and the conversation chip is **clearable** (the `×` resets `pendingPanelFilter`). The panel is registered as `WorkspacePanelId += 'pending'` in the store, a Toolbar button, and a `FileEditor` tab + render branch — `FileEditor`'s `showFilePane` **excludes** the pending tab so the file header / amber pending banner / file-content layer never overlay the panel (they share one flex container). The tab is gated by `
|
|
37
|
+
`ui/PendingChangesPanel.tsx` is the **Pending Changes** view in the Files widget — a journal of revertible filesystem changes that an agent has made (the durable backend is `src/store/PendingChangeStore.ts`; the route is `pending`, see `src/routes/README.md`). It offers two genuinely distinct views: a **compact list** (filename + full folder path, no content fetch) and **diff cards** (the default — opened inline, each card loads its hunks lazily from ONE fresh `preview` (baseline + current state, the record's own fingerprint) through `hooks/useVisibleDiffCards.ts`; on a source-revision change or a virtualizer remount the card keeps its last preview on screen while the fresh one loads, and the index badge shows only for the current key's answer; `+N/−M` counts are computed from the loaded hunks when the record carries none). Records are ordered **oldest-first** so the review walks first→last. A focused-card cursor (◄/► header buttons, plus `↑`/`↓`/`←`/`→`, `a`=approve, `r`=revert, `x`=dismiss when the panel is focused) an **Approve N** (two-step confirm; only records that need no acknowledgement) button and an **M need confirmation** button make a full sweep fast — the latter opens `PendingReviewDialog`, grouped by reason, 100 records per batch; a finished batch disables its group buttons and offers **Next batch (K left)**; approving advances to the next change. Each live (non-orphaned) row also carries a neutral-tone **Dismiss** (x) button — "accept-and-stop-reviewing": the change is kept exactly as it is (a present file stays on disk + indexed, an agent's delete stays deleted) and only the review entry resolves, with no connector write (the `pending` route's `dismiss` action; see `src/routes/README.md`). Clicking a filename opens the file in the tree (never replacing the panel). A filter bar narrows by source / agent / conversation, and the conversation chip is **clearable** (the `×` resets `pendingPanelFilter`). The panel is registered as `WorkspacePanelId += 'pending'` in the store, a Toolbar button, and a `FileEditor` tab + render branch — `FileEditor`'s `showFilePane` **excludes** the pending tab so the file header / amber pending banner / file-content layer never overlay the panel (they share one flex container). The tab is gated by `files.read` (visible to every drive user), while the *content* is filtered per record server-side (owner isolation + uri-policy on the `pending` route). The chat composer's "Open in Files" button (agent-core Changes&Actions) opens this tab scoped to a conversation; this is navigation only and is distinct from the chat approve/deny `PendingInteraction` card. A notification row opens the widget through the same one-shot `applyNav`: `open-pending` (the pending summary), `open-sources` (a source's sync/embedding outcome) and `open-vector-health` (an operator's collection action) — navigation only, every gate re-applied by the panel's own reads.
|
|
38
38
|
|
|
39
39
|
## Open-to-file tree hydration
|
|
40
40
|
|
|
@@ -6,7 +6,7 @@ Typed client for brain-core package routes plus a small set of host-side helpers
|
|
|
6
6
|
|
|
7
7
|
- Wrap a kernel `PackageApi` into typed functions that mirror every brain-core route; each takes that client as its LAST argument.
|
|
8
8
|
- Build raw-file URLs (`getRawFileUrl`) for the `<img>` / `<video>` preview elements. **Not for an iframe or any other document embed** — the byte lane serves the executable document class (SVG, HTML, XML) as `content-disposition: attachment`, so a document embed against it renders nothing, and making it render again would re-open a stored-XSS path. There is exactly one call site today (`FileEditor`).
|
|
9
|
-
- Provide the host-side
|
|
9
|
+
- Provide the host-side reads (`projectPackages.ts`) used by the Packages tab — the host's scoped snapshot `/api/packages/runtime` (with the window's `x-agent-id`) and agent-core's diagnostics, NOT brain-core.
|
|
10
10
|
|
|
11
11
|
## Public Surface
|
|
12
12
|
|
|
@@ -14,13 +14,13 @@ Typed client for brain-core package routes plus a small set of host-side helpers
|
|
|
14
14
|
- Pending changes (`index.ts`): `listPendingChanges` (with `include_orphans` it returns `orphanIds` beside the rows, from the server's SAME listing pass — the panel needs both on every mount, and two calls ran the whole dual-source listing twice), `previewPendingChange` (authoritative current/baseline states and fingerprint), `revertPendingChange`, `scanOrphanedPending` (the complete scanned page in `changes`, its orphan subset in `orphans`, and the same continuation), `discardPendingChange`, `dismissPendingChange`, `discardOrphanedPending`. Pending-backed approve/dismiss/revert carry the fresh `expectedFingerprint` and any required acknowledgement; a stale intent returns409. Types: `PendingChangeRecord`, `PendingChangesQuery`, `PendingPreview`, `PendingReviewIntent`. Pending clients preserve the page envelope and URI/cursor filters; count returns `{count, exact, complete, partial}` and safe unavailable branches; the store preserves its previous count when both branches fail.
|
|
15
15
|
- Git (`index.ts`) — the `/git/*` client behind the Git Control panel. Reads: `getGitRepos`, `getGitStatus`, `getGitBranches`, `getGitDiff`, `getGitLog`, `getGitLogStats`, `getGitCommitFiles`, `getGitOutgoing`, `getGitShow`. Writes: `stagePaths`, `unstagePaths`, `stageHunk`, `unstageHunk`, `discardPaths`, `commitGit`, `createGitBranch`, `deleteGitBranch`, `restoreGit`, `checkoutGitBranch`. Remote (uri-policy `exec`): `fetchGit`, `pullGit`, `pushGit`. Type: `GitRescue` — the typed carrier a destructive op returns and `restoreGit` echoes back.
|
|
16
16
|
- Sources (`index.ts`): `listSourceConfigs`, `updateSourceConfig`, `createSource`, `deleteSource`, `triggerSourceSync`, `getSyncStatus`, `cancelSync`, `resetSync`, `reindexSource` ("Rescan changes"), `estimateReembed` / `startReembed` (the same route with a `folder` / `uri` scope and `force: true` — a re-embed with the active model; the estimate answers before any side effect, a scope with nothing readable queues nothing), `purgeSourceIndex` (source/folder/file estimate or apply; keeps source and recovery content), `getSourcesHealth`, `browseDirectories`, `listSourceKinds`, `getSourceKindSchema`, `setSourceEnabled` (W3K), `listDiscoverableSources` (W3E). Types: `ReembedScope`, `ReembedEstimate`, `SourceKindRecord`, `SourceKindSchema`, `ResolveListOpts`, `DiscoverableSourceSuggestion`.
|
|
17
|
-
- Project packages (`projectPackages.ts`): `listProjectPackages
|
|
17
|
+
- Project packages (`projectPackages.ts`): `listProjectPackages(projectId, agentId?)` (what the agent gets, native packages left out), `getPackagesDiagnostics` (W3F — also consumed by chat-config Packages Overview through the agent-core ports). Types: `ProjectPackageRow`, `PackageDiagnosticEntry`, `PackagesDiagnosticsResponse`. Trust, Build and the access feature are Admin → Packages calls, not here.
|
|
18
18
|
|
|
19
19
|
## How it plugs into the package-system kernel
|
|
20
20
|
|
|
21
21
|
- A Files window passes the client its store was created with — `createPackageApi` over the window's project and BOUND agent — so the X-Project-Id / X-Agent-Id headers are the window's own, fixed before any await, whatever other window is mounted beside it; a body may still name another agent (a file read through its writing agent). `readFiles` alone also runs without a client, for the chat change card, which is not a Files window and resolves the first registered `brain-core` provider.
|
|
22
22
|
- The `PackageApi` provided by `@neuralis/package-system/client` translates `.get(path, qs)`, `.post(path, body)`, `.patch`, `.del`, `.upload` into authenticated requests against `/api/packages/brain-core/<path>` with the active session attached by the host.
|
|
23
|
-
- Project-package
|
|
23
|
+
- Project-package reads go directly to the host's `/api/packages/runtime` — NOT a package route and not part of the brain-core surface; the helper lives here only because the Files widget's Packages tab consumes it.
|
|
24
24
|
|
|
25
25
|
## Security & scope
|
|
26
26
|
|
|
@@ -153,6 +153,8 @@ export async function triggerSourceSync(params: {
|
|
|
153
153
|
agentId: string;
|
|
154
154
|
projectId?: string;
|
|
155
155
|
source: string;
|
|
156
|
+
/** One full pass may tombstone a source whose root now holds nothing (`root_empty`). */
|
|
157
|
+
confirmEmpty?: boolean;
|
|
156
158
|
}, client: PackageApi): Promise<TriggerSourceSyncResponse> {
|
|
157
159
|
return client.post('sources/sync', params);
|
|
158
160
|
}
|
|
@@ -297,6 +299,7 @@ export async function getGitRepos(params: {
|
|
|
297
299
|
* Whole-repo change-set + current branch for the focused git-repo source.
|
|
298
300
|
* `status.upstream`/`ahead`/`behind` (C3) are present only when the branch has
|
|
299
301
|
* an upstream — the branch-bar `↑N ↓M` chips (local refs; fetch lands in G4).
|
|
302
|
+
* A checkout git could not read answers `ok:false` with a fixed `error`.
|
|
300
303
|
*/
|
|
301
304
|
export async function getGitStatus(params: {
|
|
302
305
|
agentId: string;
|
|
@@ -304,12 +307,12 @@ export async function getGitStatus(params: {
|
|
|
304
307
|
source: string;
|
|
305
308
|
repo?: string;
|
|
306
309
|
}, client: PackageApi): Promise<{
|
|
307
|
-
ok:
|
|
310
|
+
ok: true;
|
|
308
311
|
status: GitControlStatus;
|
|
309
312
|
/** Bucket `+/-` totals for the section headers, summed server-side over the
|
|
310
313
|
* rows in `status` — see `GitDiffTotals`. Absent on an older server. */
|
|
311
314
|
totals?: { changes: GitDiffTotals; staged: GitDiffTotals };
|
|
312
|
-
}> {
|
|
315
|
+
} | { ok: false; code: 'unavailable'; error: string }> {
|
|
313
316
|
const qs: Record<string, string> = { agentId: params.agentId, source: params.source, ...repoQuery(params.repo) };
|
|
314
317
|
if (params.projectId) qs.projectId = params.projectId;
|
|
315
318
|
return client.get('git/status', qs);
|
|
@@ -552,7 +555,7 @@ export async function getGitShow(params: {
|
|
|
552
555
|
|
|
553
556
|
// ---------------------------------------------------------------------------
|
|
554
557
|
// Git Control WRITE fetchers (G2.3 / spellbound-heron) — the HUMAN face of the
|
|
555
|
-
// git engine. All POST; the server additionally gates `
|
|
558
|
+
// git engine. All POST; the server additionally gates `files.write` + a
|
|
556
559
|
// uri-policy `write` capability (uniform 403 on missing feature, 404 on scope,
|
|
557
560
|
// 403 `uri_policy_denied` on path deny), unconditionally auto-snapshots before a
|
|
558
561
|
// destructive op (Q6), and publishes the in-app badge with the actor's REAL
|
|
@@ -732,7 +735,7 @@ export async function deleteGitBranch(params: {
|
|
|
732
735
|
* One-click UNDO of a destructive op (G2.5) — POST the typed `rescue` a prior
|
|
733
736
|
* `discardPaths` / `deleteGitBranch` returned. The server re-snapshots first
|
|
734
737
|
* (a restore is itself a mutation), then `git stash apply` (discard) or
|
|
735
|
-
* `git branch <name> <ref>` (branch). `
|
|
738
|
+
* `git branch <name> <ref>` (branch). `files.write` + uri-policy `write`.
|
|
736
739
|
*/
|
|
737
740
|
export async function restoreGit(params: {
|
|
738
741
|
agentId: string;
|
|
@@ -771,7 +774,7 @@ export async function checkoutGitBranch(params: {
|
|
|
771
774
|
}
|
|
772
775
|
|
|
773
776
|
/**
|
|
774
|
-
* Remote git ops (G4 keyring-kestrel Slice 2) — `
|
|
777
|
+
* Remote git ops (G4 keyring-kestrel Slice 2) — `files.write` + uri-policy
|
|
775
778
|
* `exec`. The server derives the remote host, reads the caller's OWN connected
|
|
776
779
|
* binding, resolves their OWN push token, and runs the op with a host-matched
|
|
777
780
|
* credential helper (HTTPS-only, no force-push). `fetch`/`pull` echo the fresh
|
|
@@ -1253,23 +1256,38 @@ export async function getSourcesHealth(params: {
|
|
|
1253
1256
|
return client.get('sources/health', qs);
|
|
1254
1257
|
}
|
|
1255
1258
|
|
|
1259
|
+
/** One level of a folder listing (`GET sources/browse`). */
|
|
1260
|
+
export type BrowseFoldersResult = {
|
|
1261
|
+
entries: Array<{ name: string; path: string; type: 'directory'; access?: 'read' | 'write' }>;
|
|
1262
|
+
atRoot?: boolean;
|
|
1263
|
+
/** Device only: the listing is the folders that device allows this host (empty path). */
|
|
1264
|
+
roots?: boolean;
|
|
1265
|
+
/** Device only: `ok`, or the device's fixed class (`ceiling_denied`, `device_offline`, `broker_outdated`, …). */
|
|
1266
|
+
state?: string;
|
|
1267
|
+
truncated?: boolean;
|
|
1268
|
+
/** Device only: linked folders, counted and not followed. */
|
|
1269
|
+
symlinks?: number;
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1256
1272
|
/**
|
|
1257
|
-
* One level of
|
|
1273
|
+
* One level of folders before attaching a source — on this server, or with
|
|
1274
|
+
* `device` on the caller's OWN paired device (empty path there = the folders
|
|
1275
|
+
* the device allows this host).
|
|
1258
1276
|
*
|
|
1259
|
-
* `agentId` is REQUIRED
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
* filesystem root and the protected host zones — so a caller who cannot attach
|
|
1264
|
-
* a source gets a 403 here too.
|
|
1277
|
+
* `agentId` is REQUIRED: the route runs `ensureAccess` like every sibling
|
|
1278
|
+
* handler. On this server the gate is `sources.mount` (`sources.privileged` for
|
|
1279
|
+
* the filesystem root and the protected host zones); on a device it is the
|
|
1280
|
+
* device's owner with `device.own` — anyone else gets one fixed 403.
|
|
1265
1281
|
*/
|
|
1266
1282
|
export async function browseDirectories(params: {
|
|
1267
1283
|
path: string;
|
|
1268
1284
|
agentId: string;
|
|
1269
1285
|
projectId?: string;
|
|
1270
|
-
|
|
1286
|
+
device?: string;
|
|
1287
|
+
}, client: PackageApi): Promise<BrowseFoldersResult> {
|
|
1271
1288
|
const qs: Record<string, string> = { path: params.path, agentId: params.agentId };
|
|
1272
1289
|
if (params.projectId) qs.projectId = params.projectId;
|
|
1290
|
+
if (params.device) qs.device = params.device;
|
|
1273
1291
|
return client.get('sources/browse', qs);
|
|
1274
1292
|
}
|
|
1275
1293
|
|
|
@@ -1279,8 +1297,14 @@ export type SourceKindRecord = {
|
|
|
1279
1297
|
description: string | null;
|
|
1280
1298
|
icon: string | null;
|
|
1281
1299
|
packageId: string;
|
|
1282
|
-
trust: '
|
|
1300
|
+
trust: 'native' | 'trusted' | 'untrusted';
|
|
1283
1301
|
capabilities: string[];
|
|
1302
|
+
/** A shell can run in this kind's sources (the host kind's planes, or a connector declaring `exec`) — the Exec policy bit applies. */
|
|
1303
|
+
shell?: boolean;
|
|
1304
|
+
/** The entry features a shell on this kind needs (an `exec` connector) — tooltip information, never a gate. */
|
|
1305
|
+
execRequires?: string[];
|
|
1306
|
+
/** The kind serves only while its runtime runs (the Machine desktop). */
|
|
1307
|
+
runtimeGated?: boolean;
|
|
1284
1308
|
urlScheme: string;
|
|
1285
1309
|
configSchema: Record<string, unknown>;
|
|
1286
1310
|
defaultScope: 'project' | 'user' | 'agent';
|