@bdayadev/flutter-ultra-mcp 0.0.0 → 1.0.1
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/.claude-plugin/marketplace.json +35 -0
- package/.claude-plugin/plugin.json +14 -14
- package/.mcp.json +67 -67
- package/CODE_OF_CONDUCT.md +83 -83
- package/CONTRIBUTING.md +108 -108
- package/LICENSE +201 -201
- package/README.md +77 -77
- package/SECURITY.md +19 -19
- package/dart/ultra_flutter/CHANGELOG.md +34 -34
- package/dart/ultra_flutter/EXTENSIONS.md +61 -61
- package/dart/ultra_flutter/README.md +109 -109
- package/dart/ultra_flutter/pubspec.yaml +37 -37
- package/dart/ultra_flutter_devtools/README.md +7 -7
- package/dart/ultra_flutter_devtools/pubspec.yaml +27 -27
- package/docs/UPSTREAM-PATROL-PRS.md +5 -5
- package/docs/UPSTREAM-SENTRY-PR.md +62 -62
- package/docs/discovery-empirics.md +435 -435
- package/examples/counter-app/README.md +24 -24
- package/examples/counter-app/pubspec.yaml +23 -23
- package/examples/oidc-app/README.md +48 -48
- package/examples/oidc-app/pubspec.yaml +24 -24
- package/package.json +82 -82
- package/packages/flutter-ultra-browser/README.md +29 -29
- package/packages/flutter-ultra-browser/package.json +39 -39
- package/packages/flutter-ultra-build/README.md +60 -60
- package/packages/flutter-ultra-build/package.json +38 -38
- package/packages/flutter-ultra-devtools/README.md +7 -7
- package/packages/flutter-ultra-devtools/package.json +36 -36
- package/packages/flutter-ultra-gesture/README.md +51 -51
- package/packages/flutter-ultra-gesture/package.json +58 -58
- package/packages/flutter-ultra-native-desktop/README.md +131 -131
- package/packages/flutter-ultra-native-desktop/package.json +81 -81
- package/packages/flutter-ultra-native-mobile/README.md +103 -103
- package/packages/flutter-ultra-native-mobile/package.json +72 -72
- package/packages/flutter-ultra-patrol/README.md +40 -40
- package/packages/flutter-ultra-patrol/package.json +38 -38
- package/packages/flutter-ultra-runtime/README.md +63 -63
- package/packages/flutter-ultra-runtime/package.json +69 -69
- package/shared/contracts/package.json +31 -31
- package/shared/device-router/README.md +51 -51
- package/shared/device-router/package.json +62 -62
- package/shared/keyring/README.md +7 -7
- package/shared/keyring/package.json +24 -24
- package/shared/mcp-runtime/README.md +116 -116
- package/shared/mcp-runtime/package.json +58 -58
- package/shared/state-store/README.md +66 -66
- package/shared/state-store/package.json +60 -60
- package/shared/vm-service-client/README.md +135 -135
- package/shared/vm-service-client/package.json +62 -62
- package/skills/_internal-on-tool-failure/SKILL.md +13 -13
- package/skills/_internal-session-bootstrap/SKILL.md +18 -18
- package/skills/debug/SKILL.md +20 -20
- package/skills/devtools/SKILL.md +21 -21
- package/skills/drive/SKILL.md +20 -20
- package/skills/scaffold/SKILL.md +21 -21
- package/skills/setup/SKILL.md +26 -26
- package/skills/test/SKILL.md +19 -19
- package/skills/tour/SKILL.md +22 -22
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
# @flutter-ultra/state-store
|
|
2
|
-
|
|
3
|
-
File-based IPC for the flutter-ultra-mcp plugin's 8 servers. Atomic
|
|
4
|
-
read-modify-write of JSON state files via [`proper-lockfile`](https://www.npmjs.com/package/proper-lockfile),
|
|
5
|
-
plus an append-only JSONL writer for streams (log tails, screencast frames).
|
|
6
|
-
|
|
7
|
-
## Why files, not a daemon
|
|
8
|
-
|
|
9
|
-
Each MCP server is a separate process spawned by Claude Code. A daemon would
|
|
10
|
-
add another moving part with its own failure surface; the file model means:
|
|
11
|
-
|
|
12
|
-
- **No bootstrap order**: servers start independently.
|
|
13
|
-
- **Survives restarts**: state persists when a single server crashes and is
|
|
14
|
-
re-spawned.
|
|
15
|
-
- **Cross-tool inspection**: `cat ${CLAUDE_PLUGIN_DATA}/state/sessions.json`
|
|
16
|
-
works as a debugging primitive.
|
|
17
|
-
|
|
18
|
-
## Storage layout
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
${CLAUDE_PLUGIN_DATA}/state/
|
|
22
|
-
sessions.json ← runtime server writes, others read
|
|
23
|
-
jobs/<jobId>.json ← split-tool job state (build_runner_build, etc.)
|
|
24
|
-
streams/<streamId>.jsonl ← append-only event streams (tail_logs, etc.)
|
|
25
|
-
locks/ ← proper-lockfile metadata
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`CLAUDE_PLUGIN_DATA` is set by Claude Code; otherwise we fall back to
|
|
29
|
-
`%LOCALAPPDATA%/flutter-ultra-mcp` (Windows),
|
|
30
|
-
`~/Library/Application Support/flutter-ultra-mcp` (macOS),
|
|
31
|
-
`$XDG_DATA_HOME/flutter-ultra-mcp` (Linux).
|
|
32
|
-
|
|
33
|
-
## API
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
import { stateUpdate, stateRead, sessionsFilePath } from '@flutter-ultra/state-store';
|
|
37
|
-
import { SessionsFileSchema, emptySessionsFile } from '@flutter-ultra/mcp-runtime';
|
|
38
|
-
|
|
39
|
-
// Read with default fallback
|
|
40
|
-
const file = await stateRead(sessionsFilePath(), emptySessionsFile(), SessionsFileSchema);
|
|
41
|
-
|
|
42
|
-
// Lock-guarded mutation
|
|
43
|
-
await stateUpdate(sessionsFilePath(), emptySessionsFile(), SessionsFileSchema, (current) => ({
|
|
44
|
-
...current,
|
|
45
|
-
sessions: [...current.sessions, newSession],
|
|
46
|
-
}));
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
For continuous streams (log tails):
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
import { appendJsonl, readJsonl, streamFilePath } from '@flutter-ultra/state-store';
|
|
53
|
-
|
|
54
|
-
await appendJsonl(
|
|
55
|
-
streamFilePath('s1'),
|
|
56
|
-
{ ts: Date.now(), level: 'info', msg: '…' },
|
|
57
|
-
{ maxLines: 10_000 },
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// Reader polls with a cursor
|
|
61
|
-
const { entries, cursor } = await readJsonl(streamFilePath('s1'), (raw) => raw, /*afterCursor*/ 0);
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## License
|
|
65
|
-
|
|
66
|
-
Apache-2.0. See [LICENSE](../../LICENSE).
|
|
1
|
+
# @flutter-ultra/state-store
|
|
2
|
+
|
|
3
|
+
File-based IPC for the flutter-ultra-mcp plugin's 8 servers. Atomic
|
|
4
|
+
read-modify-write of JSON state files via [`proper-lockfile`](https://www.npmjs.com/package/proper-lockfile),
|
|
5
|
+
plus an append-only JSONL writer for streams (log tails, screencast frames).
|
|
6
|
+
|
|
7
|
+
## Why files, not a daemon
|
|
8
|
+
|
|
9
|
+
Each MCP server is a separate process spawned by Claude Code. A daemon would
|
|
10
|
+
add another moving part with its own failure surface; the file model means:
|
|
11
|
+
|
|
12
|
+
- **No bootstrap order**: servers start independently.
|
|
13
|
+
- **Survives restarts**: state persists when a single server crashes and is
|
|
14
|
+
re-spawned.
|
|
15
|
+
- **Cross-tool inspection**: `cat ${CLAUDE_PLUGIN_DATA}/state/sessions.json`
|
|
16
|
+
works as a debugging primitive.
|
|
17
|
+
|
|
18
|
+
## Storage layout
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
${CLAUDE_PLUGIN_DATA}/state/
|
|
22
|
+
sessions.json ← runtime server writes, others read
|
|
23
|
+
jobs/<jobId>.json ← split-tool job state (build_runner_build, etc.)
|
|
24
|
+
streams/<streamId>.jsonl ← append-only event streams (tail_logs, etc.)
|
|
25
|
+
locks/ ← proper-lockfile metadata
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`CLAUDE_PLUGIN_DATA` is set by Claude Code; otherwise we fall back to
|
|
29
|
+
`%LOCALAPPDATA%/flutter-ultra-mcp` (Windows),
|
|
30
|
+
`~/Library/Application Support/flutter-ultra-mcp` (macOS),
|
|
31
|
+
`$XDG_DATA_HOME/flutter-ultra-mcp` (Linux).
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { stateUpdate, stateRead, sessionsFilePath } from '@flutter-ultra/state-store';
|
|
37
|
+
import { SessionsFileSchema, emptySessionsFile } from '@flutter-ultra/mcp-runtime';
|
|
38
|
+
|
|
39
|
+
// Read with default fallback
|
|
40
|
+
const file = await stateRead(sessionsFilePath(), emptySessionsFile(), SessionsFileSchema);
|
|
41
|
+
|
|
42
|
+
// Lock-guarded mutation
|
|
43
|
+
await stateUpdate(sessionsFilePath(), emptySessionsFile(), SessionsFileSchema, (current) => ({
|
|
44
|
+
...current,
|
|
45
|
+
sessions: [...current.sessions, newSession],
|
|
46
|
+
}));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For continuous streams (log tails):
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { appendJsonl, readJsonl, streamFilePath } from '@flutter-ultra/state-store';
|
|
53
|
+
|
|
54
|
+
await appendJsonl(
|
|
55
|
+
streamFilePath('s1'),
|
|
56
|
+
{ ts: Date.now(), level: 'info', msg: '…' },
|
|
57
|
+
{ maxLines: 10_000 },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Reader polls with a cursor
|
|
61
|
+
const { entries, cursor } = await readJsonl(streamFilePath('s1'), (raw) => raw, /*afterCursor*/ 0);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
Apache-2.0. See [LICENSE](../../LICENSE).
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@flutter-ultra/state-store",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private":
|
|
5
|
-
"description": "File-based IPC for cross-server shared state. Atomic read-modify-write via proper-lockfile.",
|
|
6
|
-
"license": "Apache-2.0",
|
|
7
|
-
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/state-store",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
-
"directory": "shared/state-store"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"mcp",
|
|
18
|
-
"flutter-ultra",
|
|
19
|
-
"ipc",
|
|
20
|
-
"state"
|
|
21
|
-
],
|
|
22
|
-
"type": "module",
|
|
23
|
-
"main": "dist/index.js",
|
|
24
|
-
"module": "dist/index.js",
|
|
25
|
-
"types": "dist/index.d.ts",
|
|
26
|
-
"exports": {
|
|
27
|
-
".": {
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"import": "./dist/index.js"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"sideEffects": false,
|
|
33
|
-
"files": [
|
|
34
|
-
"dist",
|
|
35
|
-
"README.md"
|
|
36
|
-
],
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsc -b",
|
|
39
|
-
"lint": "eslint src",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"typecheck": "tsc -b --noEmit",
|
|
42
|
-
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"proper-lockfile": "^4.1.2",
|
|
46
|
-
"zod": "^3.23.8"
|
|
47
|
-
},
|
|
48
|
-
"devDependencies": {
|
|
49
|
-
"@types/proper-lockfile": "^4.1.4",
|
|
50
|
-
"rimraf": "^6.0.0",
|
|
51
|
-
"typescript": "^5.6.0",
|
|
52
|
-
"vitest": "^2.1.0"
|
|
53
|
-
},
|
|
54
|
-
"engines": {
|
|
55
|
-
"node": ">=20"
|
|
56
|
-
},
|
|
57
|
-
"publishConfig": {
|
|
58
|
-
"access": "public"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/state-store",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "File-based IPC for cross-server shared state. Atomic read-modify-write via proper-lockfile.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/state-store",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "shared/state-store"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"flutter-ultra",
|
|
19
|
+
"ipc",
|
|
20
|
+
"state"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"module": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -b",
|
|
39
|
+
"lint": "eslint src",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"typecheck": "tsc -b --noEmit",
|
|
42
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"proper-lockfile": "^4.1.2",
|
|
46
|
+
"zod": "^3.23.8"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
50
|
+
"rimraf": "^6.0.0",
|
|
51
|
+
"typescript": "^5.6.0",
|
|
52
|
+
"vitest": "^2.1.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
# @flutter-ultra/vm-service-client
|
|
2
|
-
|
|
3
|
-
TypeScript Dart VM Service client with DDS multi-client coordination. Backs the
|
|
4
|
-
`flutter-ultra-runtime`, `flutter-ultra-gesture`, `flutter-ultra-devtools`, and
|
|
5
|
-
`flutter-ultra-patrol` MCP servers in the [flutter-ultra-mcp](https://github.com/Bdaya-Dev/flutter-ultra-mcp)
|
|
6
|
-
plugin.
|
|
7
|
-
|
|
8
|
-
Ports the subset of [`package:vm_service`](https://pub.dev/packages/vm_service)
|
|
9
|
-
those servers actually call (~15 methods) plus the two DDS extensions that make
|
|
10
|
-
coexistence with VS Code's Dart debugger safe.
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm install @flutter-ultra/vm-service-client
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Quick start
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
import { VmServiceClient } from '@flutter-ultra/vm-service-client';
|
|
22
|
-
|
|
23
|
-
const client = new VmServiceClient('ws://127.0.0.1:8181/abc/ws', {
|
|
24
|
-
// DDS multi-client identity. Format recommendation: flutter-ultra/<server>/<pid>
|
|
25
|
-
clientName: `flutter-ultra/runtime/${process.pid}`,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
await client.connect();
|
|
29
|
-
|
|
30
|
-
const vm = await client.getVM();
|
|
31
|
-
console.log(`pid=${vm.pid} isolates=${vm.isolates.length}`);
|
|
32
|
-
|
|
33
|
-
await client.streamListen('Logging');
|
|
34
|
-
client.on('loggingEvent', (event) => {
|
|
35
|
-
console.log('log:', event);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await client.dispose();
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Connection target also accepts `{host, port, ws_path}`:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
const client = new VmServiceClient({ host: '127.0.0.1', port: 8181, ws_path: 'abc/ws' });
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Ported method surface
|
|
48
|
-
|
|
49
|
-
| Group | RPCs |
|
|
50
|
-
| ---------------- | ----------------------------------------------------------------------------- |
|
|
51
|
-
| Inspection | `getVM`, `getIsolate`, `getObject`, `getFlagList`, `getInstances`, `getStack` |
|
|
52
|
-
| Evaluation | `evaluate`, `evaluateInFrame`, `callServiceExtension` |
|
|
53
|
-
| Streams | `streamListen`, `streamCancel` |
|
|
54
|
-
| Execution | `pause`, `resume`, `setLibraryDebuggable` |
|
|
55
|
-
| DDS coordination | `setClientName`, `getStreamHistory` |
|
|
56
|
-
|
|
57
|
-
Plus typed event subscriptions:
|
|
58
|
-
|
|
59
|
-
- `client.on('isolateEvent' | 'extensionEvent' | 'loggingEvent' | 'stdoutEvent' | 'stderrEvent' | 'vmEvent' | 'debugEvent' | 'serviceEvent' | 'timelineEvent', (event) => …)`
|
|
60
|
-
- `for await (const event of client.onIsolateEvent()) { … }` (AsyncIterable)
|
|
61
|
-
- Catch-all: `client.on('event', (streamId, event) => …)`
|
|
62
|
-
|
|
63
|
-
## DDS coexistence (plan §7.2)
|
|
64
|
-
|
|
65
|
-
The client is designed to share a DDS instance with VS Code's Dart debugger
|
|
66
|
-
without conflict. Two rules:
|
|
67
|
-
|
|
68
|
-
1. Always set `clientName` to something unique-per-process. DDS uses the name
|
|
69
|
-
to identify cooperating clients for resume coordination.
|
|
70
|
-
2. **Never** call `requirePermissionToResume(...)` — VS Code's debugger must
|
|
71
|
-
remain the sole resume authority. This client deliberately does NOT expose
|
|
72
|
-
that DDS RPC.
|
|
73
|
-
|
|
74
|
-
## Polymorphic responses
|
|
75
|
-
|
|
76
|
-
`evaluate` and `evaluateInFrame` return one of three shapes — narrow on
|
|
77
|
-
`result.type`:
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
const result = await client.evaluate(isolateId, targetId, 'someExpression');
|
|
81
|
-
switch (result.type) {
|
|
82
|
-
case '@Instance':
|
|
83
|
-
console.log('value =', result.valueAsString);
|
|
84
|
-
break;
|
|
85
|
-
case '@Error':
|
|
86
|
-
console.log('eval failed:', result.message);
|
|
87
|
-
break;
|
|
88
|
-
case 'Sentinel':
|
|
89
|
-
console.log('isolate state changed:', result.kind);
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`getIsolate` / `getObject` throw `SentinelException` instead of returning a
|
|
95
|
-
sentinel union, since the caller almost always wants to re-discover and retry
|
|
96
|
-
rather than handle the sentinel inline.
|
|
97
|
-
|
|
98
|
-
## Reconnect
|
|
99
|
-
|
|
100
|
-
Auto-reconnect is on by default with exponential backoff
|
|
101
|
-
(`[500, 1000, 2000, 4000, 8000, 14500] ms`). Tune via:
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
new VmServiceClient(uri, {
|
|
105
|
-
autoReconnect: true,
|
|
106
|
-
reconnectDelaysMs: [500, 1_000, 2_000, 5_000],
|
|
107
|
-
});
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Pending in-flight requests reject with `ConnectionDisposedError` on disconnect;
|
|
111
|
-
listen for the `disconnect` event on the client (and the `reconnected` event
|
|
112
|
-
on `client.transport`) to drive a retry loop in your tool layer.
|
|
113
|
-
|
|
114
|
-
## Error model
|
|
115
|
-
|
|
116
|
-
| Class | Thrown on |
|
|
117
|
-
| ------------------------- | --------------------------------------------------------------------------------------------- |
|
|
118
|
-
| `RpcError` | JSON-RPC error response (carries `code`, `message`, optional `data`, originating method name) |
|
|
119
|
-
| `SentinelException` | `getIsolate` / `getObject` got back a `Sentinel` instead of the requested type |
|
|
120
|
-
| `ConnectionDisposedError` | Transport closed before the request completed |
|
|
121
|
-
| `ConnectionTimeoutError` | WS connect or per-request timeout fired |
|
|
122
|
-
|
|
123
|
-
`RpcErrorCode` exports the documented VM service + DDS error codes
|
|
124
|
-
(`-32601` `MethodNotFound`, `106` `IsolateMustBePaused`, etc.).
|
|
125
|
-
|
|
126
|
-
## Strict typing
|
|
127
|
-
|
|
128
|
-
All response shapes are validated at runtime via Zod (`.strict()` everywhere,
|
|
129
|
-
no `z.any()`/`z.record()` in the schema graph), then surfaced as inferred
|
|
130
|
-
TypeScript types. Schema drift in the VM service protocol fails loudly rather
|
|
131
|
-
than silently corrupting downstream consumers.
|
|
132
|
-
|
|
133
|
-
## License
|
|
134
|
-
|
|
135
|
-
Apache-2.0. See [LICENSE](../../LICENSE).
|
|
1
|
+
# @flutter-ultra/vm-service-client
|
|
2
|
+
|
|
3
|
+
TypeScript Dart VM Service client with DDS multi-client coordination. Backs the
|
|
4
|
+
`flutter-ultra-runtime`, `flutter-ultra-gesture`, `flutter-ultra-devtools`, and
|
|
5
|
+
`flutter-ultra-patrol` MCP servers in the [flutter-ultra-mcp](https://github.com/Bdaya-Dev/flutter-ultra-mcp)
|
|
6
|
+
plugin.
|
|
7
|
+
|
|
8
|
+
Ports the subset of [`package:vm_service`](https://pub.dev/packages/vm_service)
|
|
9
|
+
those servers actually call (~15 methods) plus the two DDS extensions that make
|
|
10
|
+
coexistence with VS Code's Dart debugger safe.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @flutter-ultra/vm-service-client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { VmServiceClient } from '@flutter-ultra/vm-service-client';
|
|
22
|
+
|
|
23
|
+
const client = new VmServiceClient('ws://127.0.0.1:8181/abc/ws', {
|
|
24
|
+
// DDS multi-client identity. Format recommendation: flutter-ultra/<server>/<pid>
|
|
25
|
+
clientName: `flutter-ultra/runtime/${process.pid}`,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await client.connect();
|
|
29
|
+
|
|
30
|
+
const vm = await client.getVM();
|
|
31
|
+
console.log(`pid=${vm.pid} isolates=${vm.isolates.length}`);
|
|
32
|
+
|
|
33
|
+
await client.streamListen('Logging');
|
|
34
|
+
client.on('loggingEvent', (event) => {
|
|
35
|
+
console.log('log:', event);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await client.dispose();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Connection target also accepts `{host, port, ws_path}`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const client = new VmServiceClient({ host: '127.0.0.1', port: 8181, ws_path: 'abc/ws' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Ported method surface
|
|
48
|
+
|
|
49
|
+
| Group | RPCs |
|
|
50
|
+
| ---------------- | ----------------------------------------------------------------------------- |
|
|
51
|
+
| Inspection | `getVM`, `getIsolate`, `getObject`, `getFlagList`, `getInstances`, `getStack` |
|
|
52
|
+
| Evaluation | `evaluate`, `evaluateInFrame`, `callServiceExtension` |
|
|
53
|
+
| Streams | `streamListen`, `streamCancel` |
|
|
54
|
+
| Execution | `pause`, `resume`, `setLibraryDebuggable` |
|
|
55
|
+
| DDS coordination | `setClientName`, `getStreamHistory` |
|
|
56
|
+
|
|
57
|
+
Plus typed event subscriptions:
|
|
58
|
+
|
|
59
|
+
- `client.on('isolateEvent' | 'extensionEvent' | 'loggingEvent' | 'stdoutEvent' | 'stderrEvent' | 'vmEvent' | 'debugEvent' | 'serviceEvent' | 'timelineEvent', (event) => …)`
|
|
60
|
+
- `for await (const event of client.onIsolateEvent()) { … }` (AsyncIterable)
|
|
61
|
+
- Catch-all: `client.on('event', (streamId, event) => …)`
|
|
62
|
+
|
|
63
|
+
## DDS coexistence (plan §7.2)
|
|
64
|
+
|
|
65
|
+
The client is designed to share a DDS instance with VS Code's Dart debugger
|
|
66
|
+
without conflict. Two rules:
|
|
67
|
+
|
|
68
|
+
1. Always set `clientName` to something unique-per-process. DDS uses the name
|
|
69
|
+
to identify cooperating clients for resume coordination.
|
|
70
|
+
2. **Never** call `requirePermissionToResume(...)` — VS Code's debugger must
|
|
71
|
+
remain the sole resume authority. This client deliberately does NOT expose
|
|
72
|
+
that DDS RPC.
|
|
73
|
+
|
|
74
|
+
## Polymorphic responses
|
|
75
|
+
|
|
76
|
+
`evaluate` and `evaluateInFrame` return one of three shapes — narrow on
|
|
77
|
+
`result.type`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const result = await client.evaluate(isolateId, targetId, 'someExpression');
|
|
81
|
+
switch (result.type) {
|
|
82
|
+
case '@Instance':
|
|
83
|
+
console.log('value =', result.valueAsString);
|
|
84
|
+
break;
|
|
85
|
+
case '@Error':
|
|
86
|
+
console.log('eval failed:', result.message);
|
|
87
|
+
break;
|
|
88
|
+
case 'Sentinel':
|
|
89
|
+
console.log('isolate state changed:', result.kind);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`getIsolate` / `getObject` throw `SentinelException` instead of returning a
|
|
95
|
+
sentinel union, since the caller almost always wants to re-discover and retry
|
|
96
|
+
rather than handle the sentinel inline.
|
|
97
|
+
|
|
98
|
+
## Reconnect
|
|
99
|
+
|
|
100
|
+
Auto-reconnect is on by default with exponential backoff
|
|
101
|
+
(`[500, 1000, 2000, 4000, 8000, 14500] ms`). Tune via:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
new VmServiceClient(uri, {
|
|
105
|
+
autoReconnect: true,
|
|
106
|
+
reconnectDelaysMs: [500, 1_000, 2_000, 5_000],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Pending in-flight requests reject with `ConnectionDisposedError` on disconnect;
|
|
111
|
+
listen for the `disconnect` event on the client (and the `reconnected` event
|
|
112
|
+
on `client.transport`) to drive a retry loop in your tool layer.
|
|
113
|
+
|
|
114
|
+
## Error model
|
|
115
|
+
|
|
116
|
+
| Class | Thrown on |
|
|
117
|
+
| ------------------------- | --------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `RpcError` | JSON-RPC error response (carries `code`, `message`, optional `data`, originating method name) |
|
|
119
|
+
| `SentinelException` | `getIsolate` / `getObject` got back a `Sentinel` instead of the requested type |
|
|
120
|
+
| `ConnectionDisposedError` | Transport closed before the request completed |
|
|
121
|
+
| `ConnectionTimeoutError` | WS connect or per-request timeout fired |
|
|
122
|
+
|
|
123
|
+
`RpcErrorCode` exports the documented VM service + DDS error codes
|
|
124
|
+
(`-32601` `MethodNotFound`, `106` `IsolateMustBePaused`, etc.).
|
|
125
|
+
|
|
126
|
+
## Strict typing
|
|
127
|
+
|
|
128
|
+
All response shapes are validated at runtime via Zod (`.strict()` everywhere,
|
|
129
|
+
no `z.any()`/`z.record()` in the schema graph), then surfaced as inferred
|
|
130
|
+
TypeScript types. Schema drift in the VM service protocol fails loudly rather
|
|
131
|
+
than silently corrupting downstream consumers.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
Apache-2.0. See [LICENSE](../../LICENSE).
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@flutter-ultra/vm-service-client",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private":
|
|
5
|
-
"description": "TypeScript Dart VM Service client with DDS multi-client coordination. Backs the runtime, gesture, devtools, and patrol MCP servers.",
|
|
6
|
-
"license": "Apache-2.0",
|
|
7
|
-
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/vm-service-client",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
-
"directory": "shared/vm-service-client"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"dart",
|
|
18
|
-
"flutter",
|
|
19
|
-
"vm-service",
|
|
20
|
-
"dds",
|
|
21
|
-
"debugger",
|
|
22
|
-
"mcp"
|
|
23
|
-
],
|
|
24
|
-
"type": "module",
|
|
25
|
-
"main": "dist/index.js",
|
|
26
|
-
"module": "dist/index.js",
|
|
27
|
-
"types": "dist/index.d.ts",
|
|
28
|
-
"exports": {
|
|
29
|
-
".": {
|
|
30
|
-
"types": "./dist/index.d.ts",
|
|
31
|
-
"import": "./dist/index.js"
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"sideEffects": false,
|
|
35
|
-
"files": [
|
|
36
|
-
"dist",
|
|
37
|
-
"README.md"
|
|
38
|
-
],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc -b",
|
|
41
|
-
"lint": "eslint src",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
-
"typecheck": "tsc -b --noEmit",
|
|
44
|
-
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"ws": "^8.18.0",
|
|
48
|
-
"zod": "^3.23.8"
|
|
49
|
-
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
"@types/ws": "^8.5.13",
|
|
52
|
-
"rimraf": "^6.0.0",
|
|
53
|
-
"typescript": "^5.6.0",
|
|
54
|
-
"vitest": "^2.1.0"
|
|
55
|
-
},
|
|
56
|
-
"engines": {
|
|
57
|
-
"node": ">=20"
|
|
58
|
-
},
|
|
59
|
-
"publishConfig": {
|
|
60
|
-
"access": "public"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/vm-service-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "TypeScript Dart VM Service client with DDS multi-client coordination. Backs the runtime, gesture, devtools, and patrol MCP servers.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/vm-service-client",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "shared/vm-service-client"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dart",
|
|
18
|
+
"flutter",
|
|
19
|
+
"vm-service",
|
|
20
|
+
"dds",
|
|
21
|
+
"debugger",
|
|
22
|
+
"mcp"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -b",
|
|
41
|
+
"lint": "eslint src",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"typecheck": "tsc -b --noEmit",
|
|
44
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"ws": "^8.18.0",
|
|
48
|
+
"zod": "^3.23.8"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/ws": "^8.5.13",
|
|
52
|
+
"rimraf": "^6.0.0",
|
|
53
|
+
"typescript": "^5.6.0",
|
|
54
|
+
"vitest": "^2.1.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|