@abide/abide 0.45.0 → 0.47.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/AGENTS.md +365 -320
- package/CHANGELOG.md +96 -0
- package/README.md +203 -168
- package/package.json +12 -8
- package/src/abideLsp.ts +11 -12
- package/src/abideResolverPlugin.ts +9 -4
- package/src/lib/bundle/disconnected.abide +3 -0
- package/src/lib/cli/printCommandHelp.ts +43 -0
- package/src/lib/cli/printSessionHelp.ts +2 -1
- package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
- package/src/lib/cli/runCli.ts +2 -1
- package/src/lib/mcp/buildPrompts.ts +25 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
- package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
- package/src/lib/mcp/mcpSurface.ts +13 -252
- package/src/lib/mcp/mcpTools.ts +187 -0
- package/src/lib/mcp/renderPrompt.ts +25 -0
- package/src/lib/mcp/types/McpSurface.ts +15 -0
- package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
- package/src/lib/mcp/types/PromptMessage.ts +2 -0
- package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
- package/src/lib/mcp/types/ToolResult.ts +2 -0
- package/src/lib/server/agent.ts +1 -1
- package/src/lib/server/jsonl.ts +2 -2
- package/src/lib/server/rpc/defineRpc.ts +7 -1
- package/src/lib/server/runtime/containsTraversal.ts +21 -17
- package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
- package/src/lib/server/runtime/createServer.ts +101 -71
- package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
- package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
- package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
- package/src/lib/server/runtime/finalizeResponse.ts +32 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
- package/src/lib/server/sockets/defineSocket.ts +26 -7
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
- package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
- package/src/lib/server/sse.ts +2 -2
- package/src/lib/shared/DEFER.ts +8 -0
- package/src/lib/shared/activeCacheStore.ts +2 -2
- package/src/lib/shared/activePage.ts +2 -2
- package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
- package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
- package/src/lib/shared/basePath.ts +2 -2
- package/src/lib/shared/baseSlot.ts +6 -5
- package/src/lib/shared/bodyValueForKind.ts +1 -2
- package/src/lib/shared/buildSocketOverChannel.ts +40 -4
- package/src/lib/shared/cache.ts +477 -110
- package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
- package/src/lib/shared/cacheStoreSlot.ts +9 -5
- package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
- package/src/lib/shared/createRemoteFunction.ts +58 -9
- package/src/lib/shared/createResolverSlot.ts +24 -31
- package/src/lib/shared/createSocketSubRegistry.ts +2 -2
- package/src/lib/shared/decodeRefJson.ts +4 -2
- package/src/lib/shared/decodeResponse.ts +8 -6
- package/src/lib/shared/done.ts +14 -0
- package/src/lib/shared/encodeRefJson.ts +4 -2
- package/src/lib/shared/globalCacheStoreSlot.ts +9 -6
- package/src/lib/shared/hydratingSlot.ts +12 -0
- package/src/lib/shared/isLayoutFile.ts +12 -0
- package/src/lib/shared/keyForRemoteCall.ts +2 -1
- package/src/lib/shared/keyPrefixForRemote.ts +12 -0
- package/src/lib/shared/matchRoute.ts +175 -0
- package/src/lib/shared/normalizePathname.ts +11 -0
- package/src/lib/shared/pageSlot.ts +14 -5
- package/src/lib/shared/pageUrlForFile.ts +3 -3
- package/src/lib/shared/parseRouteSegments.ts +16 -7
- package/src/lib/shared/patch.ts +41 -0
- package/src/lib/shared/peek.ts +35 -0
- package/src/lib/shared/prepareRemoteExport.ts +40 -0
- package/src/lib/shared/prepareRpcModule.ts +98 -16
- package/src/lib/shared/prepareSocketModule.ts +11 -15
- package/src/lib/shared/queryStringFromArgs.ts +11 -0
- package/src/lib/shared/reachable.ts +102 -0
- package/src/lib/shared/refresh.ts +22 -0
- package/src/lib/shared/requestScopeSlot.ts +10 -7
- package/src/lib/shared/routeParamsShape.ts +9 -9
- package/src/lib/shared/rpcErrorRegistry.ts +69 -0
- package/src/lib/shared/selectorPrefix.ts +3 -10
- package/src/lib/shared/setOwnProperty.ts +19 -0
- package/src/lib/shared/snippet.ts +1 -1
- package/src/lib/shared/subscribableProbes.ts +111 -0
- package/src/lib/shared/tailProbeSlot.ts +8 -1
- package/src/lib/shared/types/CacheEntry.ts +9 -15
- package/src/lib/shared/types/CacheOptions.ts +8 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
- package/src/lib/shared/types/RemoteCallable.ts +25 -7
- package/src/lib/shared/types/RemoteFunction.ts +48 -13
- package/src/lib/shared/types/ResolverSlot.ts +7 -4
- package/src/lib/shared/types/RpcError.ts +26 -0
- package/src/lib/shared/types/SmartReadOptions.ts +32 -0
- package/src/lib/shared/types/Socket.ts +54 -0
- package/src/lib/shared/types/SsrBootState.ts +27 -0
- package/src/lib/shared/types/SsrPayload.ts +21 -0
- package/src/lib/shared/types/Subscribable.ts +13 -13
- package/src/lib/shared/types/TailHooks.ts +2 -1
- package/src/lib/shared/url.ts +29 -14
- package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createScriptedSurface.ts +1 -1
- package/src/lib/test/createTestApp.ts +2 -2
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
- package/src/lib/ui/compile/analyzeComponent.ts +28 -6
- package/src/lib/ui/compile/compileModule.ts +137 -11
- package/src/lib/ui/compile/compileShadow.ts +101 -84
- package/src/lib/ui/compile/desugarSignals.ts +120 -107
- package/src/lib/ui/compile/generateBuild.ts +47 -11
- package/src/lib/ui/compile/generateSSR.ts +117 -14
- package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -4
- package/src/lib/ui/compile/lowerScript.ts +72 -5
- package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
- package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
- package/src/lib/ui/compile/signalCallee.ts +24 -0
- package/src/lib/ui/compile/stripEffects.ts +14 -11
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
- package/src/lib/ui/dom/appendStatic.ts +9 -1
- package/src/lib/ui/dom/appendText.ts +15 -3
- package/src/lib/ui/dom/assertClaimedText.ts +20 -0
- package/src/lib/ui/dom/awaitBlock.ts +17 -56
- package/src/lib/ui/dom/bindSelectValue.ts +48 -0
- package/src/lib/ui/dom/each.ts +12 -1
- package/src/lib/ui/dom/hydrate.ts +10 -0
- package/src/lib/ui/effect.ts +4 -3
- package/src/lib/ui/installHotBridge.ts +4 -2
- package/src/lib/ui/remoteProxy.ts +18 -2
- package/src/lib/ui/renderToStream.ts +9 -13
- package/src/lib/ui/resumeSeedScript.ts +2 -2
- package/src/lib/ui/router.ts +21 -2
- package/src/lib/ui/runtime/RESUME.ts +0 -6
- package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
- package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
- package/src/lib/ui/scope.ts +6 -3
- package/src/lib/ui/seedBootState.ts +53 -0
- package/src/lib/ui/socketChannel.ts +2 -2
- package/src/lib/ui/socketProxy.ts +9 -3
- package/src/lib/ui/startClient.ts +16 -30
- package/src/lib/ui/state.ts +44 -13
- package/src/lib/ui/sync.ts +11 -5
- package/src/lib/ui/tryEncodeResume.ts +2 -5
- package/src/lib/ui/types/Scope.ts +14 -10
- package/src/lib/ui/watch.ts +140 -0
- package/src/serverEntry.ts +6 -6
- package/template/CLAUDE.md +1 -1
- package/template/package.json +1 -1
- package/template/src/server/rpc/getHello.ts +3 -3
- package/template/src/ui/pages/page.abide +2 -2
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/reachable.ts +0 -45
- package/src/lib/server/sockets/types/Socket.ts +0 -23
- package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
- package/src/lib/shared/baseResolver.ts +0 -10
- package/src/lib/shared/cacheKeyOf.ts +0 -7
- package/src/lib/shared/cacheKeyStore.ts +0 -8
- package/src/lib/shared/cacheStoreResolver.ts +0 -12
- package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
- package/src/lib/shared/pageResolver.ts +0 -16
- package/src/lib/shared/recordCacheKey.ts +0 -6
- package/src/lib/shared/requestScopeResolver.ts +0 -12
- package/src/lib/shared/setBaseResolver.ts +0 -4
- package/src/lib/shared/setCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setPageResolver.ts +0 -4
- package/src/lib/shared/setRequestScopeResolver.ts +0 -4
- package/src/lib/shared/toBunRoutePattern.ts +0 -28
- package/src/lib/shared/types/TailOptions.ts +0 -10
- package/src/lib/ui/deferResume.ts +0 -29
- package/src/lib/ui/matchRoute.ts +0 -106
- package/src/lib/ui/tail.ts +0 -324
- /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
- /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
package/src/abideLsp.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
2
1
|
import ts from 'typescript'
|
|
3
2
|
import { ABIDE_SEMANTIC_TOKENS_LEGEND } from './lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts'
|
|
4
3
|
import type {
|
|
@@ -77,7 +76,7 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
77
76
|
jsonrpc: '2.0',
|
|
78
77
|
method: 'textDocument/publishDiagnostics',
|
|
79
78
|
params: {
|
|
80
|
-
uri: pathToFileURL(path).href,
|
|
79
|
+
uri: Bun.pathToFileURL(path).href,
|
|
81
80
|
diagnostics: serviceFor(path)
|
|
82
81
|
.diagnostics(path)
|
|
83
82
|
.map((diagnostic) => toLspDiagnostic(text, diagnostic)),
|
|
@@ -109,7 +108,7 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
109
108
|
case 'textDocument/didOpen': {
|
|
110
109
|
const { uri, text } = message.params.textDocument
|
|
111
110
|
if (isAbide(uri)) {
|
|
112
|
-
const path = fileURLToPath(uri)
|
|
111
|
+
const path = Bun.fileURLToPath(uri)
|
|
113
112
|
documentText.set(path, text)
|
|
114
113
|
serviceFor(path).update(path, text)
|
|
115
114
|
publish(path)
|
|
@@ -119,7 +118,7 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
119
118
|
case 'textDocument/didChange': {
|
|
120
119
|
const { uri } = message.params.textDocument
|
|
121
120
|
if (isAbide(uri)) {
|
|
122
|
-
const path = fileURLToPath(uri)
|
|
121
|
+
const path = Bun.fileURLToPath(uri)
|
|
123
122
|
const text = message.params.contentChanges.at(-1)?.text ?? ''
|
|
124
123
|
documentText.set(path, text)
|
|
125
124
|
serviceFor(path).update(path, text)
|
|
@@ -130,7 +129,7 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
130
129
|
case 'textDocument/didSave': {
|
|
131
130
|
const { uri } = message.params.textDocument
|
|
132
131
|
if (isAbide(uri)) {
|
|
133
|
-
publish(fileURLToPath(uri))
|
|
132
|
+
publish(Bun.fileURLToPath(uri))
|
|
134
133
|
}
|
|
135
134
|
return
|
|
136
135
|
}
|
|
@@ -138,10 +137,10 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
138
137
|
/* A request: always answer (null when there's nothing to show) so
|
|
139
138
|
the editor isn't left waiting. */
|
|
140
139
|
const { uri } = message.params.textDocument
|
|
141
|
-
const text = isAbide(uri) ? (documentText.get(fileURLToPath(uri)) ?? '') : ''
|
|
140
|
+
const text = isAbide(uri) ? (documentText.get(Bun.fileURLToPath(uri)) ?? '') : ''
|
|
142
141
|
const info = isAbide(uri)
|
|
143
|
-
? serviceFor(fileURLToPath(uri)).quickInfo(
|
|
144
|
-
fileURLToPath(uri),
|
|
142
|
+
? serviceFor(Bun.fileURLToPath(uri)).quickInfo(
|
|
143
|
+
Bun.fileURLToPath(uri),
|
|
145
144
|
positionToOffset(text, message.params.position),
|
|
146
145
|
)
|
|
147
146
|
: undefined
|
|
@@ -156,9 +155,9 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
156
155
|
const { uri } = message.params.textDocument
|
|
157
156
|
const data = isAbide(uri)
|
|
158
157
|
? componentSemanticTokens(
|
|
159
|
-
serviceFor(fileURLToPath(uri)),
|
|
160
|
-
fileURLToPath(uri),
|
|
161
|
-
documentText.get(fileURLToPath(uri)) ?? '',
|
|
158
|
+
serviceFor(Bun.fileURLToPath(uri)),
|
|
159
|
+
Bun.fileURLToPath(uri),
|
|
160
|
+
documentText.get(Bun.fileURLToPath(uri)) ?? '',
|
|
162
161
|
)
|
|
163
162
|
: []
|
|
164
163
|
send({ jsonrpc: '2.0', id: message.id, result: { data } })
|
|
@@ -167,7 +166,7 @@ export async function abideLsp({ cwd }: { cwd: string }): Promise<void> {
|
|
|
167
166
|
case 'textDocument/didClose': {
|
|
168
167
|
const { uri } = message.params.textDocument
|
|
169
168
|
if (isAbide(uri)) {
|
|
170
|
-
const path = fileURLToPath(uri)
|
|
169
|
+
const path = Bun.fileURLToPath(uri)
|
|
171
170
|
serviceFor(path).close(path)
|
|
172
171
|
documentText.delete(path)
|
|
173
172
|
send({
|
|
@@ -381,11 +381,16 @@ export function abideResolverPlugin({
|
|
|
381
381
|
so page imports resolve identically on both sides.
|
|
382
382
|
*/
|
|
383
383
|
if (target === 'client') {
|
|
384
|
-
/*
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
/* Build-time flags on the client proxy stub: `outbox: true` parks
|
|
385
|
+
unreachable calls; `streaming: true` (handler returns jsonl()/sse())
|
|
386
|
+
makes the bare call return the Subscribable directly. */
|
|
387
|
+
const flags = [
|
|
388
|
+
prepared.durable ? 'outbox: true' : undefined,
|
|
389
|
+
prepared.streaming ? 'streaming: true' : undefined,
|
|
390
|
+
].filter(Boolean)
|
|
391
|
+
const optsArg = flags.length > 0 ? `, { ${flags.join(', ')} }` : ''
|
|
387
392
|
const contents = `import { remoteProxy as __abideRemoteProxy__ } from '${importName}/ui/remoteProxy';
|
|
388
|
-
export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.method)}, ${JSON.stringify(url)}${
|
|
393
|
+
export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.method)}, ${JSON.stringify(url)}${optsArg});
|
|
389
394
|
`
|
|
390
395
|
return { contents, loader: 'ts' }
|
|
391
396
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { state } from '@abide/abide/ui/state'
|
|
3
|
+
import { effect } from '@abide/abide/ui/effect'
|
|
4
|
+
|
|
2
5
|
/*
|
|
3
6
|
Default bundle connect screen (abide-ui). The launcher serves this (logo baked in
|
|
4
7
|
at build time, app title injected at runtime) instead of a blank window; override
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CliManifest } from './types/CliManifest.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Per-command help (`<cmd> --help`): the command's method/url, its description, and
|
|
5
|
+
the flags derived from its JSON Schema. Output goes to stdout; the caller exits
|
|
6
|
+
zero after printing. Top-level help (the command listing) lives in
|
|
7
|
+
`printTopLevelHelp`.
|
|
8
|
+
*/
|
|
9
|
+
export function printCommandHelp(programName: string, name: string, manifest: CliManifest): void {
|
|
10
|
+
const entry = manifest[name]
|
|
11
|
+
if (!entry) {
|
|
12
|
+
console.log(`unknown command: ${name}`)
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
console.log(`usage: ${programName} ${name} [--flags]\n`)
|
|
16
|
+
console.log(` ${entry.method} ${entry.url}\n`)
|
|
17
|
+
const schema = entry.jsonSchema
|
|
18
|
+
const commandDescription = schema?.description as string | undefined
|
|
19
|
+
if (commandDescription) {
|
|
20
|
+
console.log(`${commandDescription}\n`)
|
|
21
|
+
}
|
|
22
|
+
const properties =
|
|
23
|
+
(schema?.properties as
|
|
24
|
+
| Record<string, { type?: string; description?: string }>
|
|
25
|
+
| undefined) ?? {}
|
|
26
|
+
const required = new Set((schema?.required as string[] | undefined) ?? [])
|
|
27
|
+
if (Object.keys(properties).length === 0) {
|
|
28
|
+
console.log('flags: (none)')
|
|
29
|
+
} else {
|
|
30
|
+
console.log('flags:')
|
|
31
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
32
|
+
const tag =
|
|
33
|
+
value.type === 'boolean'
|
|
34
|
+
? `--${key} / --no-${key}`
|
|
35
|
+
: `--${key} <${value.type ?? 'value'}>`
|
|
36
|
+
const requiredTag = required.has(key) ? ' (required)' : ''
|
|
37
|
+
const description = value.description ? ` — ${value.description}` : ''
|
|
38
|
+
console.log(` ${tag.padEnd(28)}${requiredTag}${description}`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log('\n --json <object> full args bag as JSON (overrides flags)')
|
|
42
|
+
console.log(' (stdin) pipe a JSON object as the args bag')
|
|
43
|
+
}
|
|
@@ -26,10 +26,9 @@ function flagSignature(jsonSchema: CliManifestEntry['jsonSchema']): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/*
|
|
29
|
-
Top-level help (no subcommand) lists every available command with a
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
cases; the caller exits zero after printing.
|
|
29
|
+
Top-level help (no subcommand) lists every available command with a one-line
|
|
30
|
+
summary. Per-command help lives in `printCommandHelp`. Output goes to stdout;
|
|
31
|
+
the caller exits zero after printing.
|
|
33
32
|
*/
|
|
34
33
|
export function printTopLevelHelp(
|
|
35
34
|
programName: string,
|
|
@@ -81,39 +80,3 @@ export function printTopLevelHelp(
|
|
|
81
80
|
printTrimmed(footer)
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
|
-
|
|
85
|
-
export function printCommandHelp(programName: string, name: string, manifest: CliManifest): void {
|
|
86
|
-
const entry = manifest[name]
|
|
87
|
-
if (!entry) {
|
|
88
|
-
console.log(`unknown command: ${name}`)
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
console.log(`usage: ${programName} ${name} [--flags]\n`)
|
|
92
|
-
console.log(` ${entry.method} ${entry.url}\n`)
|
|
93
|
-
const schema = entry.jsonSchema
|
|
94
|
-
const commandDescription = schema?.description as string | undefined
|
|
95
|
-
if (commandDescription) {
|
|
96
|
-
console.log(`${commandDescription}\n`)
|
|
97
|
-
}
|
|
98
|
-
const properties =
|
|
99
|
-
(schema?.properties as
|
|
100
|
-
| Record<string, { type?: string; description?: string }>
|
|
101
|
-
| undefined) ?? {}
|
|
102
|
-
const required = new Set((schema?.required as string[] | undefined) ?? [])
|
|
103
|
-
if (Object.keys(properties).length === 0) {
|
|
104
|
-
console.log('flags: (none)')
|
|
105
|
-
} else {
|
|
106
|
-
console.log('flags:')
|
|
107
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
108
|
-
const tag =
|
|
109
|
-
value.type === 'boolean'
|
|
110
|
-
? `--${key} / --no-${key}`
|
|
111
|
-
: `--${key} <${value.type ?? 'value'}>`
|
|
112
|
-
const requiredTag = required.has(key) ? ' (required)' : ''
|
|
113
|
-
const description = value.description ? ` — ${value.description}` : ''
|
|
114
|
-
console.log(` ${tag.padEnd(28)}${requiredTag}${description}`)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
console.log('\n --json <object> full args bag as JSON (overrides flags)')
|
|
118
|
-
console.log(' (stdin) pipe a JSON object as the args bag')
|
|
119
|
-
}
|
package/src/lib/cli/runCli.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { messageFromError } from '../shared/messageFromError.ts'
|
|
|
4
4
|
import { connectToServer } from './connectToServer.ts'
|
|
5
5
|
import { dispatchCommand } from './dispatchCommand.ts'
|
|
6
6
|
import { loadEnvFromBinaryDir } from './loadEnvFromBinaryDir.ts'
|
|
7
|
-
import { printCommandHelp
|
|
7
|
+
import { printCommandHelp } from './printCommandHelp.ts'
|
|
8
|
+
import { printTopLevelHelp } from './printTopLevelHelp.ts'
|
|
8
9
|
import { printTrimmed } from './printTrimmed.ts'
|
|
9
10
|
import { resolveCliTarget } from './resolveCliTarget.ts'
|
|
10
11
|
import { runSession } from './runSession.ts'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { promptRegistry } from '../server/prompts/promptRegistry.ts'
|
|
2
|
+
import type { PromptDescriptor } from './types/PromptDescriptor.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
MCP prompts derived from src/mcp/prompts. Arguments come from the JSON
|
|
6
|
+
Schema the resolver built from each prompt's frontmatter `arguments` list
|
|
7
|
+
(top-level properties + required flags); the model fills them in and the
|
|
8
|
+
framework interpolates them into the body on getPrompt.
|
|
9
|
+
*/
|
|
10
|
+
export function buildPrompts(): PromptDescriptor[] {
|
|
11
|
+
return Array.from(promptRegistry.values()).map((entry) => {
|
|
12
|
+
const jsonSchema = entry.jsonSchema ?? {}
|
|
13
|
+
const properties = (jsonSchema.properties ?? {}) as Record<string, { description?: string }>
|
|
14
|
+
const required = new Set((jsonSchema.required as string[] | undefined) ?? [])
|
|
15
|
+
return {
|
|
16
|
+
name: entry.prompt.name,
|
|
17
|
+
...(entry.prompt.description ? { description: entry.prompt.description } : {}),
|
|
18
|
+
arguments: Object.entries(properties).map(([argName, prop]) => ({
|
|
19
|
+
name: argName,
|
|
20
|
+
...(prop?.description ? { description: prop.description } : {}),
|
|
21
|
+
required: required.has(argName),
|
|
22
|
+
})),
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ensureRegistriesLoaded } from '../server/runtime/registryManifests.ts'
|
|
2
2
|
import { messageFromError } from '../shared/messageFromError.ts'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { buildPrompts } from './buildPrompts.ts'
|
|
4
|
+
import { mcpResourceServerSlot } from './mcpResourceServerSlot.ts'
|
|
5
|
+
import { mcpTools } from './mcpTools.ts'
|
|
6
|
+
import { renderPrompt } from './renderPrompt.ts'
|
|
5
7
|
import type { JsonRpcRequest } from './types/JsonRpcRequest.ts'
|
|
6
8
|
import type { JsonRpcResponse } from './types/JsonRpcResponse.ts'
|
|
7
9
|
import type { McpServerOptions } from './types/McpServerOptions.ts'
|
|
@@ -108,7 +110,7 @@ async function dispatchMethod(
|
|
|
108
110
|
case 'ping':
|
|
109
111
|
return jsonRpcOk(id, {})
|
|
110
112
|
case 'tools/list':
|
|
111
|
-
return jsonRpcOk(id, { tools:
|
|
113
|
+
return jsonRpcOk(id, { tools: mcpTools.list() })
|
|
112
114
|
case 'tools/call': {
|
|
113
115
|
const params = envelope.params as
|
|
114
116
|
| { name?: string; arguments?: Record<string, unknown> }
|
|
@@ -116,10 +118,10 @@ async function dispatchMethod(
|
|
|
116
118
|
if (!params?.name) {
|
|
117
119
|
return jsonRpcError(id, -32602, 'Missing tool name')
|
|
118
120
|
}
|
|
119
|
-
return jsonRpcOk(id, await
|
|
121
|
+
return jsonRpcOk(id, await mcpTools.call(params.name, params.arguments, request))
|
|
120
122
|
}
|
|
121
123
|
case 'resources/list': {
|
|
122
|
-
const resourceServer =
|
|
124
|
+
const resourceServer = mcpResourceServerSlot.server
|
|
123
125
|
return jsonRpcOk(id, {
|
|
124
126
|
resources: resourceServer ? await resourceServer.list() : [],
|
|
125
127
|
})
|
|
@@ -129,7 +131,7 @@ async function dispatchMethod(
|
|
|
129
131
|
if (!params?.uri) {
|
|
130
132
|
return jsonRpcError(id, -32602, 'Missing resource uri')
|
|
131
133
|
}
|
|
132
|
-
const resourceServer =
|
|
134
|
+
const resourceServer = mcpResourceServerSlot.server
|
|
133
135
|
const contents = resourceServer ? await resourceServer.read(params.uri) : undefined
|
|
134
136
|
if (!contents) {
|
|
135
137
|
return jsonRpcError(id, -32602, `unknown resource: ${params.uri}`)
|
|
@@ -3,16 +3,11 @@ import type { McpResourceServer } from './types/McpResourceServer.ts'
|
|
|
3
3
|
/*
|
|
4
4
|
Process-wide slot for the MCP resource server. createServer assigns it at
|
|
5
5
|
boot; dispatchMcpRequest reads it on resources/list + resources/read. Mirrors
|
|
6
|
-
the
|
|
7
|
-
|
|
6
|
+
the other `*Slot` seams (e.g. logTapSlot) — a single object named after the
|
|
7
|
+
file with a mutable field — because the default MCP server is constructed in
|
|
8
|
+
the abide:mcp virtual with no args, so the resource server (which needs the
|
|
8
9
|
project's resourcesDir + embedded map) is injected out of band.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export function setMcpResourceServer(value: McpResourceServer): void {
|
|
13
|
-
resourceServer = value
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getMcpResourceServer(): McpResourceServer | undefined {
|
|
17
|
-
return resourceServer
|
|
11
|
+
export const mcpResourceServerSlot: { server: McpResourceServer | undefined } = {
|
|
12
|
+
server: undefined,
|
|
18
13
|
}
|
|
@@ -1,267 +1,28 @@
|
|
|
1
|
-
import { promptRegistry } from '../server/prompts/promptRegistry.ts'
|
|
2
|
-
import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
|
|
3
|
-
import { findRpcByCommandName } from '../server/rpc/findRpcByCommandName.ts'
|
|
4
|
-
import { rpcRegistry } from '../server/rpc/rpcRegistry.ts'
|
|
5
|
-
import { socketOperations } from '../server/sockets/socketOperations.ts'
|
|
6
|
-
import { socketRegistry } from '../server/sockets/socketRegistry.ts'
|
|
7
1
|
import { abideLog } from '../shared/abideLog.ts'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import type { McpResourceContents } from './types/McpResourceContents.ts'
|
|
16
|
-
import type { McpResourceDescriptor } from './types/McpResourceDescriptor.ts'
|
|
2
|
+
import { buildPrompts } from './buildPrompts.ts'
|
|
3
|
+
import { mcpResourceServerSlot } from './mcpResourceServerSlot.ts'
|
|
4
|
+
import { mcpTools } from './mcpTools.ts'
|
|
5
|
+
import { renderPrompt } from './renderPrompt.ts'
|
|
6
|
+
import type { McpSurface } from './types/McpSurface.ts'
|
|
7
|
+
import type { PromptDescriptor } from './types/PromptDescriptor.ts'
|
|
8
|
+
import type { ToolDescriptor } from './types/ToolDescriptor.ts'
|
|
17
9
|
|
|
18
10
|
/*
|
|
19
11
|
The app's MCP surface, projected for in-process consumers. This is the single
|
|
20
12
|
source of truth dispatchMcpRequest (the JSON-RPC-over-HTTP transport) and the
|
|
21
13
|
in-app agent loop (abide/server/agent) both build on — the same tool/prompt/
|
|
22
14
|
resource derivation, so a model reaching the app over HTTP and a model driven
|
|
23
|
-
in-process can't drift on what's exposed.
|
|
15
|
+
in-process can't drift on what's exposed. The individual derivations live in
|
|
16
|
+
sibling modules (mcpTools / buildPrompts / renderPrompt).
|
|
24
17
|
|
|
25
18
|
Internal: there is no package export. The public entry is `agent()`, which
|
|
26
19
|
calls `mcpSurface(request)` to hand an engine the gated tool set.
|
|
27
20
|
*/
|
|
28
21
|
|
|
29
|
-
export type ToolDescriptor = {
|
|
30
|
-
name: string
|
|
31
|
-
description: string
|
|
32
|
-
inputSchema: Record<string, unknown>
|
|
33
|
-
outputSchema?: Record<string, unknown>
|
|
34
|
-
annotations?: Record<string, boolean>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type PromptDescriptor = {
|
|
38
|
-
name: string
|
|
39
|
-
description?: string
|
|
40
|
-
arguments: Array<{ name: string; description?: string; required: boolean }>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// The MCP tools/call result shape (a text content block plus optional structuredContent).
|
|
44
|
-
export type ToolResult = Record<string, unknown>
|
|
45
|
-
|
|
46
|
-
// A prompt rendered to plain messages — the user turn(s) that seed a conversation.
|
|
47
|
-
export type PromptMessage = { role: 'user'; text: string }
|
|
48
|
-
|
|
49
|
-
export type McpSurface = {
|
|
50
|
-
tools: ToolDescriptor[]
|
|
51
|
-
call(name: string, args: Record<string, unknown> | undefined): Promise<ToolResult>
|
|
52
|
-
prompts: PromptDescriptor[]
|
|
53
|
-
getPrompt(name: string, args?: Record<string, unknown>): PromptMessage[]
|
|
54
|
-
listResources(): Promise<McpResourceDescriptor[]>
|
|
55
|
-
readResource(uri: string): Promise<McpResourceContents | undefined>
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
Builds the array of MCP tool descriptors.
|
|
60
|
-
|
|
61
|
-
RPCs: every rpc with clients.mcp=true becomes one tool named after the
|
|
62
|
-
export's URL (folder segments joined with `-`). The HTTP method feeds the
|
|
63
|
-
tool's annotations (readOnlyHint / destructiveHint / idempotentHint) so
|
|
64
|
-
a model can tell a read from a write; reads auto-expose while mutating
|
|
65
|
-
rpcs require an explicit clients.mcp (see resolveClientFlags). When the
|
|
66
|
-
rpc declares an `outputSchema` it's advertised as the tool outputSchema.
|
|
67
|
-
|
|
68
|
-
Sockets: every socket with clients.mcp=true contributes a `<base>-tail`
|
|
69
|
-
read tool (recent buffered messages) and, when clientPublish is set, a
|
|
70
|
-
`<base>-publish` tool.
|
|
71
|
-
*/
|
|
72
|
-
export function buildTools(): ToolDescriptor[] {
|
|
73
|
-
const tools: ToolDescriptor[] = []
|
|
74
|
-
for (const entry of rpcRegistry.values()) {
|
|
75
|
-
if (!entry.clients.mcp) {
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
/*
|
|
79
|
-
Tool description favours the schema's top-level description (the
|
|
80
|
-
vendor's JSON Schema conversion carries `.describe(...)` through),
|
|
81
|
-
falling back to `method url` so the tool is still labelled when
|
|
82
|
-
the schema has none.
|
|
83
|
-
*/
|
|
84
|
-
const inputSchema = jsonSchemaForSchema(entry.inputSchema)
|
|
85
|
-
const tool: ToolDescriptor = {
|
|
86
|
-
name: commandNameForUrl(entry.remote.url),
|
|
87
|
-
description:
|
|
88
|
-
(inputSchema.description as string | undefined) ??
|
|
89
|
-
`${entry.remote.method} ${entry.remote.url}`,
|
|
90
|
-
inputSchema,
|
|
91
|
-
annotations: annotationsForMethod(entry.remote.method),
|
|
92
|
-
}
|
|
93
|
-
if (entry.outputSchema) {
|
|
94
|
-
tool.outputSchema = jsonSchemaForSchema(entry.outputSchema)
|
|
95
|
-
}
|
|
96
|
-
tools.push(tool)
|
|
97
|
-
}
|
|
98
|
-
for (const entry of socketRegistry.values()) {
|
|
99
|
-
if (!entry.clients.mcp) {
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
const payloadSchema = jsonSchemaForSchema(entry.schema)
|
|
103
|
-
for (const operation of socketOperations(entry)) {
|
|
104
|
-
if (operation.kind === 'tail') {
|
|
105
|
-
tools.push({
|
|
106
|
-
name: operation.name,
|
|
107
|
-
description: `Read recent messages from the "${operation.socketName}" socket`,
|
|
108
|
-
inputSchema: {
|
|
109
|
-
type: 'object',
|
|
110
|
-
properties: {
|
|
111
|
-
count: { type: 'number', description: 'max recent messages to return' },
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
outputSchema: {
|
|
115
|
-
type: 'object',
|
|
116
|
-
properties: { frames: { type: 'array', items: payloadSchema } },
|
|
117
|
-
},
|
|
118
|
-
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
119
|
-
})
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
tools.push({
|
|
123
|
-
name: operation.name,
|
|
124
|
-
description: `Publish a message to the "${operation.socketName}" socket`,
|
|
125
|
-
inputSchema: payloadSchema,
|
|
126
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return tools
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/*
|
|
134
|
-
MCP prompts derived from src/mcp/prompts. Arguments come from the JSON
|
|
135
|
-
Schema the resolver built from each prompt's frontmatter `arguments` list
|
|
136
|
-
(top-level properties + required flags); the model fills them in and the
|
|
137
|
-
framework interpolates them into the body on getPrompt.
|
|
138
|
-
*/
|
|
139
|
-
export function buildPrompts(): PromptDescriptor[] {
|
|
140
|
-
return Array.from(promptRegistry.values()).map((entry) => {
|
|
141
|
-
const jsonSchema = entry.jsonSchema ?? {}
|
|
142
|
-
const properties = (jsonSchema.properties ?? {}) as Record<string, { description?: string }>
|
|
143
|
-
const required = new Set((jsonSchema.required as string[] | undefined) ?? [])
|
|
144
|
-
return {
|
|
145
|
-
name: entry.prompt.name,
|
|
146
|
-
...(entry.prompt.description ? { description: entry.prompt.description } : {}),
|
|
147
|
-
arguments: Object.entries(properties).map(([argName, prop]) => ({
|
|
148
|
-
name: argName,
|
|
149
|
-
...(prop?.description ? { description: prop.description } : {}),
|
|
150
|
-
required: required.has(argName),
|
|
151
|
-
})),
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
22
|
/* MCP tool-dispatch spans, opt-in via DEBUG=abide:mcp — a model's tool call,
|
|
157
23
|
wrapping the underlying rpc dispatch in the same trace. */
|
|
158
24
|
const mcpLog = abideLog.channel('abide:mcp')
|
|
159
25
|
|
|
160
|
-
function textResult(text: string, isError = false): ToolResult {
|
|
161
|
-
return { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) }
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/*
|
|
165
|
-
Dispatches the socket tail / publish tools by matching the tool name
|
|
166
|
-
against each mcp-exposed socket's operations (socketOperations is the same
|
|
167
|
-
projection tools/list advertised, so the publish op only exists when the
|
|
168
|
-
socket allows it). tail returns the retained tail (request/response
|
|
169
|
-
can't hold a live subscription); publish validates against the socket
|
|
170
|
-
schema and fans out. Returns undefined when the name isn't a known socket
|
|
171
|
-
tool so callTool can fall through to "unknown tool".
|
|
172
|
-
*/
|
|
173
|
-
function callSocketTool(
|
|
174
|
-
toolName: string,
|
|
175
|
-
args: Record<string, unknown> | undefined,
|
|
176
|
-
): ToolResult | undefined {
|
|
177
|
-
for (const entry of socketRegistry.values()) {
|
|
178
|
-
if (!entry.clients.mcp) {
|
|
179
|
-
continue
|
|
180
|
-
}
|
|
181
|
-
const operation = socketOperations(entry).find((op) => op.name === toolName)
|
|
182
|
-
if (!operation) {
|
|
183
|
-
continue
|
|
184
|
-
}
|
|
185
|
-
if (operation.kind === 'tail') {
|
|
186
|
-
const count = typeof args?.count === 'number' ? args.count : undefined
|
|
187
|
-
const frames = entry.snapshotTail(count)
|
|
188
|
-
return {
|
|
189
|
-
content: [{ type: 'text', text: frames.map((f) => JSON.stringify(f)).join('\n') }],
|
|
190
|
-
structuredContent: { frames },
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
// publish() validates the payload against the socket schema and throws on failure.
|
|
195
|
-
entry.socket.publish(args)
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return textResult(messageFromError(error), true)
|
|
198
|
-
}
|
|
199
|
-
return textResult('ok')
|
|
200
|
-
}
|
|
201
|
-
return undefined
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/*
|
|
205
|
-
Tool dispatch. RPC tools synthesize a Request (with forwarded auth
|
|
206
|
-
headers from `inbound`) and pipe it through rpc.fetch inside the request
|
|
207
|
-
scope — the same seam the HTTP router crosses, so validation, the handler,
|
|
208
|
-
and the request-scoped helpers (per-call cache(), cookies(), request())
|
|
209
|
-
behave identically. A handler throw is caught by the scope and framed as
|
|
210
|
-
an isError tool result (via the 500 response) rather than escaping. The
|
|
211
|
-
response (buffered or streaming) is framed by toolResultFromResponse.
|
|
212
|
-
Socket tools (`<base>-tail` / `<base>-publish`) fall through to the socket
|
|
213
|
-
dispatcher.
|
|
214
|
-
*/
|
|
215
|
-
export async function callTool(
|
|
216
|
-
toolName: string,
|
|
217
|
-
args: Record<string, unknown> | undefined,
|
|
218
|
-
inbound: Request,
|
|
219
|
-
): Promise<ToolResult> {
|
|
220
|
-
const entry = findRpcByCommandName(toolName)
|
|
221
|
-
if (entry) {
|
|
222
|
-
/* A rpc owns this name. If it isn't mcp-exposed it's still unavailable —
|
|
223
|
-
don't fall through to a socket op that happens to share the name. */
|
|
224
|
-
if (!entry.clients.mcp) {
|
|
225
|
-
throw new Error(`unknown tool: ${toolName}`)
|
|
226
|
-
}
|
|
227
|
-
const response = await dispatchRpcInProcess({
|
|
228
|
-
remote: entry.remote,
|
|
229
|
-
args,
|
|
230
|
-
baseUrl: `${new URL(inbound.url).origin}/`,
|
|
231
|
-
headers: forwardHeaders(inbound.headers),
|
|
232
|
-
})
|
|
233
|
-
return toolResultFromResponse(response)
|
|
234
|
-
}
|
|
235
|
-
const socketResult = callSocketTool(toolName, args)
|
|
236
|
-
if (socketResult) {
|
|
237
|
-
return socketResult
|
|
238
|
-
}
|
|
239
|
-
throw new Error(`unknown tool: ${toolName}`)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/*
|
|
243
|
-
Renders a prompt: looks it up, interpolates the caller's args, and returns its
|
|
244
|
-
optional description plus the message(s) that seed a conversation. A markdown
|
|
245
|
-
prompt is a single user turn whose text is the interpolated template. Throws on
|
|
246
|
-
an unknown prompt name. The one place prompt rendering lives — dispatchMcpRequest
|
|
247
|
-
wraps this in the prompts/get wire shape, the agent loop reads the messages plain.
|
|
248
|
-
*/
|
|
249
|
-
export function renderPrompt(
|
|
250
|
-
name: string,
|
|
251
|
-
args?: Record<string, unknown>,
|
|
252
|
-
): { description?: string; messages: PromptMessage[] } {
|
|
253
|
-
const entry = promptRegistry.get(name)
|
|
254
|
-
if (!entry) {
|
|
255
|
-
throw new Error(`unknown prompt: ${name}`)
|
|
256
|
-
}
|
|
257
|
-
return {
|
|
258
|
-
...(entry.prompt.description ? { description: entry.prompt.description } : {}),
|
|
259
|
-
messages: [
|
|
260
|
-
{ role: 'user', text: entry.prompt.render((args ?? {}) as Record<string, string>) },
|
|
261
|
-
],
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
26
|
/*
|
|
266
27
|
Projects the app's MCP surface for an in-process consumer bound to `request`
|
|
267
28
|
— tool calls forward that request's auth headers into the rpc handler, so
|
|
@@ -274,22 +35,22 @@ export function mcpSurface(request: Request): McpSurface {
|
|
|
274
35
|
let prompts: PromptDescriptor[] | undefined
|
|
275
36
|
return {
|
|
276
37
|
get tools() {
|
|
277
|
-
tools ??=
|
|
38
|
+
tools ??= mcpTools.list()
|
|
278
39
|
return tools
|
|
279
40
|
},
|
|
280
41
|
get prompts() {
|
|
281
42
|
prompts ??= buildPrompts()
|
|
282
43
|
return prompts
|
|
283
44
|
},
|
|
284
|
-
call: (name, args) => mcpLog.trace(`mcp ${name}`, () =>
|
|
45
|
+
call: (name, args) => mcpLog.trace(`mcp ${name}`, () => mcpTools.call(name, args, request)),
|
|
285
46
|
/* The conversation-seeding messages, without the wire-shape wrapping. */
|
|
286
47
|
getPrompt: (name, args) => renderPrompt(name, args).messages,
|
|
287
48
|
async listResources() {
|
|
288
|
-
const server =
|
|
49
|
+
const server = mcpResourceServerSlot.server
|
|
289
50
|
return server ? server.list() : []
|
|
290
51
|
},
|
|
291
52
|
async readResource(uri) {
|
|
292
|
-
const server =
|
|
53
|
+
const server = mcpResourceServerSlot.server
|
|
293
54
|
return server ? server.read(uri) : undefined
|
|
294
55
|
},
|
|
295
56
|
}
|